回调函数.docx

上传人:b****5 文档编号:4646139 上传时间:2022-12-07 格式:DOCX 页数:10 大小:21.34KB
下载 相关 举报
回调函数.docx_第1页
第1页 / 共10页
回调函数.docx_第2页
第2页 / 共10页
回调函数.docx_第3页
第3页 / 共10页
回调函数.docx_第4页
第4页 / 共10页
回调函数.docx_第5页
第5页 / 共10页
点击查看更多>>
下载资源
资源描述

回调函数.docx

《回调函数.docx》由会员分享,可在线阅读,更多相关《回调函数.docx(10页珍藏版)》请在冰豆网上搜索。

回调函数.docx

回调函数

c|c++声明函数指针;回调函数;调用规范;函数指针实现回调

2009-03-3122:

35

调函数

程序员常常需要实现回调。

本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。

注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。

声明函数指针

回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。

要实现回调,必须首先定义函数指针。

尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。

请看下面的例子:

voidf();//函数原型

上面的语句声明了一个函数,没有输入参数并返回void。

那么函数指针的声明方法如下:

void(*)();

让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。

另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。

注意本例中还没有创建指针变量-只是声明了变量类型。

目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

//获得函数指针的大小

unsignedpsize=sizeof(void(*)());

//为函数指针声明类型定义

typedefvoid(*pfv)();

pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。

使用这个类型定义名可以隐藏复杂的函数指针语法。

指针变量应该有一个变量名:

void(*p)();//p是指向某函数的指针

p是指向某函数的指针,该函数无输入参数,返回值的类型为void。

左边圆括弧里星号后的就是指针变量名。

有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。

例如:

voidfunc()

{

/*dosomething*/

}

p=func;

p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。

传递回调函数的地址给调用者

现在可以将p传递给另一个函数(调用者)-caller(),它将调用p指向的函数,而此函数名是未知的:

voidcaller(void(*ptr)())

{

ptr();/*调用ptr指向的函数*/

}

voidfunc();

intmain()

{

p=func;

caller(p);/*传递函数地址到调用者*/

}

如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。

赋值可以发生在运行时,这样使你能实现动态绑定。

调用规范

到目前为止,我们只讨论了函数指针及回调而没有去注意ANSIC/C++的编译器规范。

许多编译器有几种调用规范。

如在VisualC++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。

C++Builder也支持_fastcall调用规范。

调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。

例如:

//被调用函数是以int为参数,以int为返回值

__stdcallintcallee(int);

//调用函数以函数指针为参数

voidcaller(__cdeclint(*ptr)(int));

//在p中企图存储被调用函数地址的非法操作

__cdeclint(*p)(int)=callee;//出错

指针p和callee()的类型不兼容,因为它们有不同的调用规范。

因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列。

函数指针和回调函数

2007-05-2408:

22

你不会每天都使用函数指针,但是,它们确有用武之地,两个最常见的用途是把函数指针作为参数传递给另一个函数以及用于转换表(jumptable)。

      【警告】简单声明一个函数指针并不意味着它马上就可以使用。

和其它指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。

下面的代码段说明了一种初始化函数指针的方法。

       int   f(int);

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

       第2个声明创建了函数指针pf,并把它初始化为指向函数f。

函数指针的初始化也可以通过一条赋值语句来完成。

在函数指针的初始化之前具有f的原型是很重要的,否则编译器就无法检查f的类型是否与pf所指向的类型一致。

       初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。

&操作符只是显式地说明了编译器隐式执行的任务。

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

       int   ans;

    

       ans=f(25);

       ans=(*pf)(25);

       ans=pf(25);

       第1条语句简单地使用名字调用函数f,但它的执行过程可能和你想象的不太一样。

函数名f首先被转换为一个函数指针,该指针指定函数在内存中的位置。

然后,函数调用操作符调用该函数,执行开始于这个地址的代码。

       第2条语句对pf执行间接访问操作,它把函数指针转换为一个函数名。

这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。

不过,这条语句的效果和第1条是完全一样的。

       第3条语句和前两条的效果是一样的。

间接访问并非必需,因为编译器需要的是一个函数指针。

       

(一)回调函数

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

它的参数是一个指向链表第1个节点的指针以及那个需要查找的值。

       Node*

       search_list(Node   *node,int   const   value)

       {

           while(node!

=NULL){

               if(node->value==value)

                   break;

               node=node->link;

           }

           returnnode;

       }

       这个函数看上去相当简单,但它只适用于值为整数的链表。

如果你需要在一个字符串链表中查找,你不得不另外编写一个函数。

这个函数和上面那个函数的绝大部分代码相同,只是第2个参数的类型以及节点值的比较方法不同。

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

我们必须对函数的两个方面进行修改,使它与类型无关。

       首先,我们必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较。

这个目标听上去好像不可能,如果你编写语句用于比较整型值,它怎么还可能用于其它类型如字符串的比较呢?

解决方案就是使用函数指针。

调用者编写一个比较函数,用于比较两个值,然后把一个指向此函数的指针作为参数传递给查找函数。

而后查找函数来执行比较。

使用这种方法,任何类型的值都可以进行比较。

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

比较函数有一个void   *形参,用于接收这个参数。

然后指向这个值的指针便传递给比较函数。

(这个修改使字符串和数组对象也可以被使用。

字符串和数组无法作为参数传递给函数,但指向它们的指针却可以。

       使用这种技巧的函数被称为回调函数(callback   function),因为用户把一个函数指针作为参数传递其它函数,后者将”回调“用户的函数。

任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。

      

       【提示】

       在使用比较函数的指针之前,它们必须被强制转换为正确的类型。

因为强制类型转换能够躲开一般的类型检查,所以你在使用时必须格外小心,确保函数参数类型是正确的。

       在这个例子里,回调函数比较两个值。

查找函数向比较函数传递两个指向需要进行比较的值的指针,并检查比较函数的返回值。

例如:

零表示相等的值,现在查找函数就与类型无关,因为它本身并不执行实际的比较。

确实,调用者必须编写必需的比较函数,但这样做是很容易的,因为调用者知道链表中所包含的值的类型。

如果使用几个分别包含不同类型值的链表,为每种类型编写一个比较函数就允许单个查找函数作用于所有类型的链表。

       程序段01是类型无关的查找函数的一种实现方法。

注意函数的第3个参数是一个函数指针。

这个参数用一个完整的原型进行声明。

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

如果node被声明为const,函数将不得不返回一个const结果,这将限制调用程序,它便无法修改查找函数所找到的节点。

       /*

       **程序01——类型无关的链表查找函数

       **在一个单链表中查找一个指定值的函数。

它的参数是一个指向链表第1个节点的指针、一个指向我们需要   查找的值的指针和一个函数指针。

       **它所指向的函数用于比较存储于链表中的类型的值。

       */

       #include   

       #include   "node.h"

      

       Node*

       search_list(Node*node,   void   const   *value,   int   (*compare)(void   const   *,voidconst*))

       {

           while(node!

=NULL){

               if(compare(&node->value,value)==0)

                   break;

           node=node->link;

           }

           returnnode;

       }

       指向值参数的指针和&node->value被传递给比较函数。

后者是我们当前所检查的节点值。

      

       在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并把指向该函数的指针和指向需要查找的值的指针传递给查找函数下面是一个比较函数,它用于在一个整数链表中进行查找。

       int

       compare_ints(voidconst*a,voidconst*b)

       {

           if(*(int*)a==*(int*)b)

               return0;

           else

               return1;

       }

       这个函数像下面这样使用:

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

       注意强制类型转换:

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

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

       #include   

       ...

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

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

void*。

       

(二)转移表

       转换表最好用个例子来解释。

下面的代码段取自一个程序,它用于实现一个袖珍式计算器。

程序的其他部分已经读入两个数(op1和op2)和一个操作数(oper)。

下面的代码对操作符进行测试,然后决定调用哪个函数。

       switch(oper){

       caseADD:

               result=add(op1,op2);

               break;

       caseSUB:

               result=sub(op1,op2);

               break;

       caseMUL:

               result=mul(op1,op2);

               break;

       caseDIV:

               result=div(op1,op2);

               break;

        

         ......

       对于一个新奇的具有上百个操作符的计算器,这条switch语句将非常长。

       为什么要调用函数来执行这些操作呢?

把具体操作和选择操作的代码分开是一种良好的设计方法,更为复杂的操作将肯定以独立的函数来实现,因为它们的长度可能很长。

但即使是简单的操作也可能具有副作用,例如保存一个常量值用于以后的操作。

       为了使用switch语句,表示操作符的代码必须是整数。

如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。

转换表就是一个函数指针数组。

       创建一个转换表需要两个步骤。

首先,声明并初始化一个函数指针数组。

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

       doubleadd(double,double);

       doublesub(double,double);

       doublemul(double,double);

       doublediv(double,double);

       ......

       double(*oper_func[])(double,double)={

           add,sub,mul,div,...

       };

       初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。

这个例子假定ADD是0,SUB是1,MUL是2,依次类推。

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

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

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

回调函数这个东西使用得比较多,其实所谓的回调函数就是函数指针,但在面向对象编程中,往往我们会使用他们达到很巧的目的,比如说类的封装中;或者用得更多的是实现动态绑定;呵呵这不是C++中传说的多态吗,

   先来个简单的介绍函数指针:

 Copycode

#include

typedefint(*callback)(int);

usingnamespacestd;

classX

{

protected:

intxx;

public:

X()

{

xx=11;

//hh=22;

}

staticintget_hh(inta)

{

cout<<\"函数get_hh\"<

returna;

}

};

classY

{

private:

intxx;

 X  X_class;

 callbacklp;

public:

Y()

{

xx=7;

}

inthehe(callbacklp)

{

lp(xx);

returnxx;

}

voidxixi()

{

lp=&X:

:

get_hh;

}

};

intf_xx(inta)

{

cout<<\"这是全局函数f_xx:

\"<

returna;

}

intmain(intargc,char*argv[],charenv[])

{

intxx;

callbacklp;

xx=111;

cout<<\"呵呵!

\"<

cout<<\"第一步:

函数指针\"<

lp=&X:

:

get_hh;

cout<<\"这就是函数指针直接调类中的静态成员函数get_xx:

\"<

cout<

lp=&f_xx;

cout<<\"这就是函数指针直接全局函数f_xx:

\"<

cout<<\"第二步:

跨类回调\"<

YY_class;

lp=&X:

:

get_hh;

cout<<\"这就是在Y类中调用了X类中的方法\"<

cout<

cin>>xx;

return0;

}

   主要需要注意的是在使用函数指针的时候,如果指向的函数是在某个类中,则该函数必须是静态成员函数

   还有函数指针的申明格式,怪怪的哈,呵呵用多了就不觉得了,接下来我们用它来干点面向对象因该干的事情:

 Copycode

include

usingnamespacestd;

typedefint(*callback)(int);

classX

{

public:

intX_test(callbacklp,inta)

{

a--;

cout<<\"执行回调函数的之前,传入参数变成:

\"<

cout<

a=lp(a);

cout<<\"执行回调函数的之后,传入参数变成:

\"<

cout<

returna;

}

};

classY

{

protected:

intxx;

private:

X  X_class;

 callbacklp;

public:

Y(inta)

{

xx=a;

}

staticinthehe(inta)

{

for(inti=1;i<=10;i++)

{

 a++;

}

returna;

}

voidxixi()

{

Xc_x;

lp=&hehe;

xx=c_x.X_test(lp,xx);

cout<<\"最终结果传到Y类中的xx变成了:

\"<

cout<

}

};

intmain(intargc,char*argv[],charenv[])[Page]

{

intxx;

Y*y_c=newY(100);

y_c->xixi();

return0;

}

   这样在X类中使用了Y类中的方法,现在我们的X类只是做加法,而减法放在了Y类中。

当然这个只是为了便于理解才简化的,只是一个类比,比如一个类实现数据处理而另外一个实现视图处理。

是不是

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 高中教育

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1