【实验1:回调机制的实现】

  • 实验目的:通过3门编程语言实现回调机制,理解什么是回调机制以及各种编程语言采用了什么方式来实现回调机制
  • 实验内容:下层模块/框架中需要设计一个op函数,对两个参数值进行“某种”操作后返回一个值。

1.C语言实现回调机制

C语言中经典回调机制的例子,是其标准库qsort函数,其函数原型为:

void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ) );

它对数组按照“某种”比较规则进行排序。

打开Pelles c开发环境,通过菜单File→New→Project...打开对话框,创建一个Win64控制台程序(Win64 program(exe))项目,输入项目name如op,输入项目loaction指定为D:\clang\ood。完成本步骤,在D:\clang文件夹下,有子文件夹ood。Clang用于存放各种C语言项目,如对C语言进行回顾地学习项目,而ood是学习本课程所有C语言源代码集中的项目。目前,文件夹ood下有ood.ppj和ood.ppx两个项目管理文件。

注:完成本实验最好的方式是将op函数放入一个lib或dll项目中。这里仅仅将op函数和应用程序以不同文件分开。

通过File→New→Source code在环境中创建一个未命名(untitled)的文件,将本实验的op函数(简化起见,假定对两个int参数进行某种操作)保存为op.c文件;再创建一个源文件,保存为ood.h文件(此后该头文件将中容纳多个c文件的函数原型的声明)。只要在某个源文件中使用了ood.h,Pelles C将ood.h关联到环境中的Include文件夹下(注意,Pelles C项目中的Include文件夹仅仅是Pelles C对项目进行组织/管理的方式,而操作系统的文件系统中并没有创建Include这个文件夹。)

//op.c文件
typedef  int (*How_op)( int, int); //函数指针的种类
int op(int a, int b, How_op  how_op){ // call using function pointer
	return 2* how_op (a, b);    //2*运算暗示op函数体可以很复杂
}

//ood.h文件
//op.c用
typedef  int (*How_op)( int, int); //函数指针的种类
int op(int a, int b, How_op  how_op);

应用程序/上层模块想调用函数op进行相加运算,于是编写函数plus和main,保存在main.c文件中。为了只写一个main,对op的使用放在testCallback函数中。

//上层模块,main.c文件
#include <stdio.h>
#include "ood.h"
int plus(int a,int b) {
	return a+b; 
}

void testCallback(void){
	int d =op(2, 5,  &plus);//
	printf("%d",d); //输出2*(2+5)
}

int main(void){	
	testCallback();
    return 0;
}

执行输出14。

以上层模块的testCallback为出发点,testCallback调用了下层的函数op,并在调用时将一个函数plus的指针作为实参传递给op的how_op形参;而下层的函数op执行过程中将“回过头来调用”上层定义的函数plus。某种程度上,在C语言中,回调机制/Call back用“回过头来调用”,非常形象和直观。

如果应用程序改变主意,想调用函数op进行乘法运算,就必须编写一个乘法函数并传递给op函数。

★C语言采用函数指针来实现回调机制。

而op中的引人注目的第三个参数,即指针int (*How_op)( int, int),它指定了底层函数op能够使用的函数的种类,表示为(int, int)→int。可以将各种函数名赋值给一个函数指针,但这些函数必须具有规定的形参列表和返回值类型,这里称之为函数指针的种类,因为它不是一个类型。

2. Scheme语言实现回调机制

函数式编程语言如Scheme,将函数作为一个数据类型。

★以函数作为参数或返回值的函数,被称为高阶函数(Higher-Order function)

预先创建的D:\SchemeLang文件夹,搜集所有本课程使用的Scheme代码。打开DrRacket开发环境,通过File→New定义函数op并保存为D:\SchemeLang\op.rkt,其代码和C语言相似,也需要第三个参数来刻画进行“何种”具体的操作,而op就是一个以函数how-op为形参的高阶函数。

再创建一个main.rkt测试对op的调用。

;;;op.rkt
(define (op a b how-op)
   ( how-op a b) )

;;;上层模块,main.rkt
(load  "op.rkt")
;;;define function plus
(define (plus a b )
  (+ a b))
;;;Pass a Function as an Argument
(op 1 3 plus)
(op 2 3 (lambda(x y)(* x y)));;;Pass a lambda  as an Argument

(op  1+2i  3-4i *)
(op  (list 1 2 3) (list 4 5 6) append ) 

执行输出:

4
6
11+2i
(1 2 3 4 5 6)

在Scheme语言中,由于高阶函数无处不在、高阶函数是该语言的基本特点,它通常不需要回调机制、回调函数这些术语。Scheme程序员在应用函数op时,如(op 1 3 plus),不屑说:“上层模块call op,而op call back plus”。

注:当使用DrRacket进行编程时,请选择R5RS,即Scheme语言标准第5修改稿。

3. Java语言实现回调机制

具有动态绑定能力的面向对象的Java语言,不同于C和Scheme,不需要第三个参数来刻画进行“何种”具体的操作,因为抽象函数op本身就意味着对两个值进行“某种”操作。在此请停下脚步,体会一下面向对象语言的强大之处:定义函数op时,不需要第三个参数来指定进行“何种”具体的操作。

预先创建的D:\JavaLang文件夹,搜集所有本课程使用的Java代码。打开BlueJ开发环境,通过Project→New创建ood项目,loaction指定为D:\ JavaLang。通过Edit→new package创建chap1至chap5多个包;双击chap1图标进入chap1包并创建callBack子包,在callBack包中点击new class创建BinaryOP接口,抽象函数op由二元操作/BinaryOP封装。BinaryOP的实现类如AddOp,指定进行何种具体的操作——如加法。

package chap1.callBack;
@FunctionalInterface public interface BinaryOP { //二元操作
      int op(int a, int b);
}

package chap1.callBack;
public class AddOp implements BinaryOP {
    @Override public int op(int a,int b){  
        return a+b;  
    }
}


//上层模块
package chap1;
import static yqj2065.util.Print.*;//pln
import chap1.callBack.*;

public class Test{
    public static void test(){
        int d = new AddOp().op(1, 2);// new 实现类()
        pln(d);        
    }    
}

Java可以通过BinaryOP的子类型提供支持代码,可以把AddOp看成框架开发者提供的示例,应用程序开发者也可以编写类似AddOp的独立类文件;而更多的提供支持代码的方式,将在[1.1.4匿名类和λ表达式]中介绍。

可以把AddOp看成框架开发者提供的示例,应用程序开发者也可以编写类似AddOp的BinaryOP的子类型的独立类文件;而更多的提供支持代码的方式,将在[1.1.4匿名类和λ表达式]中介绍。

本节主要目的是通过3门编程语言实现回调机制,上下层的区分仅以文件不同表示。在下一节将以Java为例,将下层/底层模块打包,更清楚地区分上下层模块。

从实现回调机制的角度看,各语言使用的技术、术语如下:

  • C,函数指针
  • Scheme,高阶函数
  • Java,动态绑定/多态
上一篇:从SOD到OOD(axilite_in模块与axilite_out模块)


下一篇:【论文泛读150】面试官 - 候选人角色扮演:走向开发真实世界的 NLP 系统