一.函数指针与指针函数的区别

    1. 指针函数是指带指针的函数,本质上是一个函数,函数返回类型是某一类型的指针,其形式一般如下:

          类型标识符 *函数名(参数列表)

    例如:int *f(x,y),它的意思是声明一个函数f(x,y),该函数返回类型为int型指针。

    2.函数指针是指向函数的指针变量,即本质上是一个指针变量,表示的是一个指针,它指向的是一个函数。其形式一般如下:

        类型说明符 (*函数名)(参数)

        例如,int (*pf)(int x),它的意思是声明一个函数指针,而pf=func则是将func函数的首地址赋值给指针。

二.函数指针

    简单声明一个函数指针并不意味着它马上就可以使用。和其他指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。下面的代码说明了一种初始化函数指针的方法:

        int  f(int );

        int  (*pf)(int )=&f;

    第二个声明创建了函数指针pf,并把它初始化为指向函数f。函数指针的初始化也可以通过一条赋值语句来完成。在函数指针的初始化之前具有f的原型是很重要的,否则编译器就无法检查f的类型释放与pf所指向的类型一致。

    初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。&操作符只是显示的说明了编译器将隐式执行的任务。

    在函数指针被声明并且初始化之后,可以使用三种方式调用函数:

    int  ans;

    ans=f(25);

    ans=(*pf)(25);

    ans=pf(25);

    第1条语句简单的使用名字调用函数f,但它的执行过程是:函数名f首先被转换为一个函数指针,该指针指定函数在内存中的位置。然后,函数调用操作符调用该函数,执行开始于这个地址的代码。

    第2条语句对pf执行间接访问操作,它把函数指针转换为一个函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第1条语句是完全一样的。

    第3条语句和前两句的效果是一样的。间接访问操作并非必须,因为编译器需要的是一个函数指针。

三.函数指针的用途

    函数指针最常见的用途是作为参数传递给另一个函数和转换表。下面使用回调函数和转移表举例。

四.回调函数

    回调函数就是被调用者回头调用的函数,它是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,此时就称之为回调函数。

    使用回调函数实际上就是在调用某个函数时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个被调用函数。而该被调用函数在需要的时候,利用传递的地址调用回调函数。

    下面是一个在单链表查找一个值的程序(适用于值为任意类型)。

//与类型无关的链表查找

    #include<stdio.h>

    #include"node.h"

    Node *search_list(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;

    }

    这个函数将向下面这样使用:

    disired_node=search_list(root,&desired_value,compare_ints);

    注意强制类型转换:比较函数的参数必须声明为void *以匹配查找函数的原型,然后再强制转换为int *类型,用于比较整型值。

    如果你希望在一个字符串链表中进行查找,下面的代码可以完成任务:

    desired_node=search_list(root,"desired_value",strcmp);

    碰巧,库函数strcmp所执行的比较和我们需要的完全一样,不过有些编译器会发出警告信息,因为它的参数被声明为char *而不是void *。

五.转移表

    下面的代码取自于一个用于实现袖珍版计算器的程序,程序的其他部分已经读入两个数(op1和op2)和一个操作符(oper)。下面的代码对操作符进行测试,然后决定调用哪个函数。

    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语句,表示操作符的代码必须是整数。如果它们是从0开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。

    创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。

    double add(double,doule);

    double sub(double,doule);

    double mul(double,doule);

    double div(double,doule);

    ...

    double (*oper_func[])(double,double)={add,sub,mul,div,...};

    初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子假定ADD是0,SUB是1,MUL是2,接下去以此类推。

    第2个步骤是用下面这条语句替换前面整条switch语句!

    result=oper_func[oper](op1,op2);

        oper从数组中 选择正确的函数指针,而函数调用操作符将执行这个程序。