陈中正的网络日志

『转』函数指针及应用

我们先来看一下以下的声明:

int f(int);

int  (*pf)(int)=&f;//&操作符可选;因为函数名被使用时总是由编译器把它

                           //转换为函数指针;

int ans;

ans=f(25);

ans=(*pf)(25);

ans=pf(25);//间接访问操作并非必需,因为编译器需要的是一个函数指针;

**********************************************************************************

两个最常见的用途是把函数指针作为参数传递给函数以及用于转换表!

1.回调函数

这里有一个简单的函数,它用于在一个单链表中查找一个值,它的参数是一个指向

链表第一个节点的指针以及那个需要查找的值.

Node* search_list(Node* node,int const value)

{   while(node!=NULL)

  {    if(node->value==value)

                break;

       node=node->link;

   }

    return node;

}

       这个函数看上去相当简单,但它只适用于值为整数的链表,如果你需要在一个

字符串链表中查找,你不得不另外编写一个函数,这个函数和上面那个函数的绝大

部分代码相同,只是第二个参数的类型以及节点值的比较方法不同.

       一种更为通用的方法是查找函数与类型无关,这样它就能用于任何类型的值

的链表,我们必须对函数的两个方面进行修改,使它与类型无关.首先我们必须改变

比较的执行方式,这样函数就可以对任何类型的值进行比较.这个目标听上去好象

不可能,如果你编写语句用于比较整型值,它怎么还可能用于其他类型如字符串

的比较呢?解决方案就是使用函数指针,调用者编写一个函数,用于比较两个值,然后

把一个指向这个函数的指针作为参数传递给查找函数.然后查找函数调用这

个函数来执行值的比较,使用这种方法,任何类型的值都可以进行比较.

       我们必须修改的第二个方面是向函数传递一个指向值的指针而不是本身.

函数由一个void *形参,用于接收这个参数,然后指向这个值的指针便传递给比较

函数,这个修改使字符串和数组对象也可以被使用,字符串和数组无法作为参数传

递给函数,但指向它们的指针可以.

      使用这种技巧的函数叫"回调函数"(callback function);因为用户把一个函数指

针作为参数传递给其他函数,后者将"回调"用户的函数.任何时候,如果你所编写的

函数必须能够在不同的时刻执行不同类型的工作或执行只能由函数调用者定义

的工作,你都可以使用这个技巧.许多窗口系统使用回调函数连接多个动作,

如拖拽鼠标和点击按钮来指定用户程序中的某个特定函数.

      我们无法在这个上下文环境中为回调函数编写一个准确的原型,因为我们并

不知道进行比较的值的类型.事实上,我们需要查找函数能作用于任何类型的值,

解决这个难题的方法是把参数类型声明为"void *",表示"一个指向未知类型

的指针".

   

#include

#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;

}

      同时注意虽然函数不会修改参数node所指向的任何节点,但node并未声明

为const。如果node被声明为const,函数不得不返回一个const结果,这将限制

调用程序,它便无法修改查找函数所找到的节点。

      在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并

把指向该函数的指针和指向需要查找的值的指针传递给查找函数。例如,下面

是一个比较函数,它用于在一个整数链表中进行查找。

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);

2.转换表(jump table)

    转移表最好用个例子来解释。下面的代码段取自一个程序,它用于实现

一个袖珍式计算器。程序的其他部分已经读入两个数(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语句,表示操作符的代码必须是整数。如果它们是从零开始

连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针

数组。

      创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一

需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。

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,接下去以此类推。

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

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

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

 

 

本文章迁移自http://blog.csdn.net/timberwolf_2012/article/details/8743628

Categories:  C/C++ 

« HDU OJ 4505 小Q系列故事——电梯里的爱情 HDU OF 4509 湫湫系列故事——减肥记II »