ImageVerifierCode 换一换
格式:DOCX , 页数:10 ,大小:21.34KB ,
资源ID:4646139      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/4646139.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(回调函数.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

回调函数.docx

1、回调函数c|c+声明函数指针 ;回调函数;调用规范 ;函数指针实现回调2009-03-31 22:35调函数程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。声明函数指针回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:void f();/ 函数原型上面的语句声明了一个函数,

2、没有输入参数并返回void。那么函数指针的声明方法如下:void (*) ();让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:/ 获得函数指针的大小 unsigned psize = sizeof (void (*) ();/ 为函数指针声明类型定义 typedef void (*pfv) ();pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个

3、类型定义名可以隐藏复杂的函数指针语法。指针变量应该有一个变量名:void (*p) (); /p是指向某函数的指针p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:void func() /* do something */ p = func;p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。传递回调函数的地址给调用者现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:void caller(void(*ptr)(

4、) ptr(); /* 调用ptr指向的函数 */ void func(); int main() p = func; caller(p); /* 传递函数地址到调用者 */ 如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。调用规范到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C+的编译器规范。许多编译器有几种调用规范。如在Visual C+中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C+ Builder也支持_fastcall调用规范。调用规

5、范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:/ 被调用函数是以int为参数,以int为返回值 _stdcall int callee(int);/ 调用函数以函数指针为参数 void caller( _cdecl int(*ptr)(int);/ 在p中企图存储被调用函数地址的非法操作 _cdecl int(*p)(int) = callee; / 出错指针p和callee()的类型不兼容,因为它们有不

6、同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列。函数指针和回调函数2007-05-24 08:22你不会每天都使用函数指针,但是,它们确有用武之地,两个最常见的用途是把函数指针作为参数传递给另一个函数以及用于转换表(jump table)。 【警告】简单声明一个函数指针并不意味着它马上就可以使用。和其它指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。下面的代码段说明了一种初始化函数指针的方法。 int f(int); int (*pf)(int)&f; 第 2 个声明创建了函数指针 pf ,并把它初始化为指向函数 f 。函数指针的初始化也可

7、以通过一条赋值语句来完成。 在函数指针的初始化之前具有 f 的原型是很重要的,否则编译器就无法检查 f 的类型是否与 pf 所指向的类型一致。 初始化表达式中的 & 操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。 & 操作符只是显式地说明了编译器隐式执行的任务。 在函数指针被声明并且初始化之后,我们就可以使用三种方式调用函数: int ans; ansf(25); ans(*pf)(25); anspf(25); 第 1 条语句简单地使用名字调用函数 f ,但它的执行过程可能和你想象的不太一样。 函数名 f 首先被转换为一个函数指针,该指针指定函数在内存中的位置。然后, 函

8、数调用操作符调用该函数,执行开始于这个地址的代码。 第 2 条语句对 pf 执行间接访问操作,它把函数指针转换为一个函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第1条是完全一样的。 第 3 条语句和前两条的效果是一样的。间接访问并非必需,因为编译器需要的是一个函数指针。 (一)回调函数 这里有一个简单的函数,它用于在单链表中查找一个值。它的参数是一个指向链表第 1 个节点的指针以及那个需要查找的值。 Node * search_list(Node *node, int const value) while(node!=NULL)

9、if( node-value = value ) break; node = node-link; return node; 这个函数看上去相当简单,但它只适用于值为整数的链表。如果你需要在一个字符串链表中查找,你不得不另外编写一个函数。这个函数和上面那个函数的绝大部分代码相同,只是第 2 个参数的类型以及节点值的比较方法不同。 一种更为通用的方法是使查找函数与类型无关,这样它就能用于任何类型的值的链表。我们必须对函数的两个方面进行修改,使它与类型无关。 首先,我们必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较。这个目标听上去好像不可能,如果你编写语句用于比较整型值,它怎么还可能

10、用于其它类型如字符串的比较呢? 解决方案就是使用函数指针。调用者编写一个比较函数,用于比较两个值,然后把一个指向此函数的指针作为参数传递给查找函数。而后查找函数来执行比较。使用这种方法,任何类型的值都可以进行比较。 我们必须修改的第 2 个方面是向比较函数传递一个指向值的指针而不是值本身。比较函数有一个 void * 形参,用于接收这个参数。然后指向这个值的指针便传递给比较函数。(这个修改使字符串和数组对象也可以被使用。字符串和数组无法作为参数传递给函数,但指向它们的指针却可以。) 使用这种技巧的函数被称为回调函数(callback function),因为用户把一个函数指针作为参数传递其它函

11、数,后者将”回调“用户的函数。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。 【提示】 在使用比较函数的指针之前,它们必须被强制转换为正确的类型。因为强制类型转换能够躲开一般的类型检查,所以你在使用时必须格外小心,确保函数参数类型是正确的。 在这个例子里,回调函数比较两个值。查找函数向比较函数传递两个指向需要进行比较的值的指针,并检查比较函数的返回值。例如:零表示相等的值,现在查找函数就与类型无关,因为它本身并不执行实际的比较。确实,调用者必须编写必需的比较函数,但这样做是很容易的,因为调用者知道链表中所包含的值的类

12、型。如果使用几个分别包含不同类型值的链表,为每种类型编写一个比较函数就允许单个查找函数作用于所有类型的链表。 程序段01 是类型无关的查找函数的一种实现方法。 注意函数的第 3 个参数是一个函数指针。这个参数用一个完整的原型进行声明。同时注意虽然函数绝不会修改参数 node 所指向的任何节点,但 node 并未被声明为 const 。如果 node 被声明为 const,函数将不得不返回一个const结果,这将限制调用程序,它便无法修改查找函数所找到的节点。 /* *程序 01 类型无关的链表查找函数 *在一个单链表中查找一个指定值的函数。它的参数是一个指向链表第 1 个节点的指针、一个指向我

13、们需要 查找的值的指针和一个函数指针。 *它所指向的函数用于比较存储于链表中的类型的值。 */ #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-value 被传递给比较函数。后者是我们当前所检查

14、的节点值。 在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并把指向该函数的指针和指向需要查找的值的指针传递给查找函数下面是一个比较函数,它用于在一个整数链表中进行查找。 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 ); 注意强制类型转换:比较函数的参数必须声明为 voi

15、d * 以匹配查找函数的原型,然后它们再强制转换为 int * 类型,用于比较整型值。 如果你希望在一个字符串链表中进行查找,下面的代码可以完成这项任务: #include . desired_node = search_list( root, desired_value, strcmp); 碰巧,库函数 strcmp 所执行的比较和我们需要的完全一样,不过有些编译器会发出警告信息,因为它的参数被声明为 char * 而不是void *。 (二)转移表 转换表最好用个例子来解释。下面的代码段取自一个程序,它用于实现一个袖珍式计算器。程序的其他部分已经读入两个数(op1和op2)和一个操作数(o

16、per)。下面的代码对操作符进行测试,然后决定调用哪个函数。 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语句将非常长。 为什么要调用函数来执行这些操作呢? 把具体操作和选择操作的代码分开是一种良好的设计方法,更为复杂

17、的操作将肯定以独立的函数来实现,因为它们的长度可能很长。但即使是简单的操作也可能具有副作用,例如保存一个常量值用于以后的操作。 为了使用 switch 语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。 创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。 double add (double,double); double sub (double,double); double mul (double,double); double div

18、 (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从数组中选择正确的函数指针,而函数调用操作符执行这个函数。回调函数这个东西使用得比较多,其实所谓的回调函数就是函数指针,但在面向对象编程中,往往我们会使用他

19、们达到很巧的目的,比如说类的封装中;或者用得更多的是实现动态绑定;呵呵这不是C+中传说的多态吗, 先来个简单的介绍函数指针: Copy code#includetypedef int(*callback)(int);using namespace std;class Xprotected: int xx;public:X()xx=11;/ hh=22;static int get_hh(int a)cout函数get_hhendl;return a;class Yprivate: int xx; X X_class; callback lp;public:Y()xx=7;int hehe(ca

20、llback lp)lp(xx);return xx;void xixi()lp=&X:get_hh;int f_xx(int a)cout这是全局函数f_xx:aendl;return a;int main(int argc,char *argv,char env)int xx;callback lp;xx=111;cout呵呵!endl;cout第一步:函数指针endl-endl;lp=&X:get_hh;cout这就是函数指针直接调类中的静态成员函数get_xx:endl;coutlp(xx)endl;lp=&f_xx;cout这就是函数指针直接全局函数f_xx:lp(xx)endl;c

21、out第二步:跨类回调endl-endl;Y Y_class;lp=&X:get_hh;cout这就是在Y类中调用了X类中的方法endl;coutY_class.hehe(lp)xx;return 0; 主要需要注意的是在使用函数指针的时候,如果指向的函数是在某个类中,则该函数必须是静态成员函数 还有函数指针的申明格式,怪怪的哈,呵呵用多了就不觉得了 ,接下来我们用它来干点面向对象因该干的事情: Copy codeincludeusing namespace std;typedef int(*callback)(int);class Xpublic:int X_test(callback lp

22、,int a)a-;cout执行回调函数的之前,传入参数变成:endl;coutaendl;a=lp(a);cout执行回调函数的之后,传入参数变成:endl;coutaendl;return a;class Yprotected: int xx;private : X X_class; callback lp;public:Y(int a)xx=a;static int hehe(int a)for(int i=1;i=10;i+) a+;return a;void xixi()X c_x;lp=&hehe;xx=c_x.X_test(lp,xx);cout最终结果传到Y类中的xx变成了:endl;coutxxxixi();return 0; 这样在X类中使用了Y类中的方法,现在我们的X类只是做加法,而减法放在了Y类中。当然这个只是为了便于理解才简化的,只是一个类比,比如一个类实现数据处理而另外一个实现视图处理。是不是

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

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