一、函数指针
简单声明一个函数指针并不意味着它马上就可以使用,和其它指针一样,对函数指针执行简接访问之前必须把它初始化为指向某一个函数。
int f(int);
int (*pf)(int)=&f;
第二个声明创建了函数指针pf,并把它初始化为指向函数f。函数指针的初始化也可以通过一条赋值语句完成。在函数指针的初始化之前具有f的原型是很重要的,否则编译器就无法检查f的类型是否与pf所指向的类型一致。
初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。&操作符只是显示地说明了编译器将隐式执行的任务。
在函数指针被声明并且初始化之后,我们就可以使用三种方式调用函数:
int ret;
ret=f(30);
ret=(*pf)(30);
ret=pf(30);
第一条语句简单的使用名字调用函数f,但它的执行过程可能和我们想的不太一样。函数名f首先被转换为一个函数指针,该指针指定函数在内存中的位置。然后,函数调用操作符调用该函数,执行开始于这个地址的代码。
第二条语句对pf执行简接访问操作,它把函数指针转换为一个函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第一条语句是完全一样的。
第三条语句和前两条语句的效果是一样的。简接访问并非必需,因为编译器需要的是一个函数指针。这个例子显示了函数指针通常是如何使用的。
那么什么时候我们应该使用函数指针呢。两个最常见的用途是把函数指针作为参数传递给函数以及用于转换表。
二、回调函数
回调函数,顾名思义,就是使用者自己定义一个函数,使用者自己实现这个函数的程序内容,然后把这个函数作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数。
在系统编程的角度:当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。
使用函数指针的一个例子就是回调函数。
例如如下函数:
Node* search_ist(Node *node,void const *value,int (*compare)(void const *,void const *))
{
while(node(!=NULL){
if(compare(&node->value,value) ==0)
{
break;
}
node=node->link;
}
return node;
}
这是一个类型无关的链表查找,其中参数函数指针所指向的函数用于比较存储于链表中类型的值。
在上边的函数中我们把一个函数指针作为参数传递给其它函数,后者将回调用户的函数。任何时候,如果我们所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者的工作,都可以使用这个技巧。许多窗口系统使用回调函数链接多个动作,如拖曳鼠标和点击按钮来指定用户程序中某个特定函数。
同样也可以这样使用,在一个特定的链表中进行查找:
int compare_ints(void const *a,void const *b)
{
if(*(int)a == *(int *)b)
return 0;
else
return 1;
}
这个函数可以像下面这样使用:
desired_node=search_list(root,&desired_value,compare_ints);
如果想在一个字符串链表中进行查找,也可以这样:
desired_node=search_list(root,"desired_value",strcmp);
不过有的编译器可能会发出警告,因为strcmp的参数被声明为char*而不是void*;
三、转换表
如果有如下代码:
switch(oper){
case ADD:
result=add(op1,op2);
break;
case SUB:
result=sub(op1,op2);
break;
case MUL:
result=mul(op1,op2);
break;
case DIV:
result=div(op1,op2);
break;
...
对于一个具有上百个操作符计算器而言,这条switch语句将会非常之长。
为了使用switch语句,表示操作符的代码必须是整数。如果它们是从零开始的整数,我们可以使用转换表来完成相同的任务。转换表就是一个函数指针数组。
创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。函数的原型必须出现在这个数组的声明之前。
double add{double,double};
double sub{double,double};
double mul{double,double};
double div{double,double};
...
double (*oper_func[])(double,double)={
add,sub,mul,div,...
};
初始化列表中各个函数的正确顺序取决于程序中用于表示每个操作符的整形代码。这个例子中假定ADD是0,SUB是1,MUL是2,DIV是3,以此类推。
第二个步骤是用下面这条语句替换前面整条switch语句。
result=oper_func[oper](op1,op2);
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。