回调函数Word格式.docx
- 文档编号:17581050
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:10
- 大小:21.34KB
回调函数Word格式.docx
《回调函数Word格式.docx》由会员分享,可在线阅读,更多相关《回调函数Word格式.docx(10页珍藏版)》请在冰豆网上搜索。
void(*p)();
//p是指向某函数的指针
p是指向某函数的指针,该函数无输入参数,返回值的类型为void。
左边圆括弧里星号后的就是指针变量名。
有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。
例如:
voidfunc()
{
/*dosomething*/
}
p=func;
p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。
传递回调函数的地址给调用者
现在可以将p传递给另一个函数(调用者)-caller(),它将调用p指向的函数,而此函数名是未知的:
voidcaller(void(*ptr)())
ptr();
/*调用ptr指向的函数*/
voidfunc();
intmain()
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);
(*pf)(int)=&
f;
第2个声明创建了函数指针pf,并把它初始化为指向函数f。
函数指针的初始化也可以通过一条赋值语句来完成。
在函数指针的初始化之前具有f的原型是很重要的,否则编译器就无法检查f的类型是否与pf所指向的类型一致。
初始化表达式中的&
操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。
&
操作符只是显式地说明了编译器隐式执行的任务。
在函数指针被声明并且初始化之后,我们就可以使用三种方式调用函数:
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
<
stdio.h>
"
node.h"
search_list(Node*node,
void
*value,
(*compare)(void
*,voidconst*))
while(node!
if(compare(&
node->
value,value)==0)
node=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*类型,用于比较整型值。
如果你希望在一个字符串链表中进行查找,下面的代码可以完成这项任务:
string.h>
...
desired_node=search_list(root,"
desired_value"
strcmp);
碰巧,库函数strcmp所执行的比较和我们需要的完全一样,不过有些编译器会发出警告信息,因为它的参数被声明为char*而不是
void*。
(二)转移表
转换表最好用个例子来解释。
下面的代码段取自一个程序,它用于实现一个袖珍式计算器。
程序的其他部分已经读入两个数(op1和op2)和一个操作数(oper)。
下面的代码对操作符进行测试,然后决定调用哪个函数。
switch(oper){
caseADD:
result=add(op1,op2);
caseSUB:
result=sub(op1,op2);
caseMUL:
result=mul(op1,op2);
caseDIV:
result=div(op1,op2);
......
对于一个新奇的具有上百个操作符的计算器,这条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<
iostream>
typedefint(*callback)(int);
usingnamespacestd;
classX
{
protected:
intxx;
public:
X()
xx=11;
//hh=22;
staticintget_hh(inta)
cout<
<
\"
函数get_hh\"
endl;
returna;
};
classY
private:
X
X_class;
callbacklp;
Y()
xx=7;
inthehe(callbacklp)
lp(xx);
returnxx;
voidxixi()
lp=&
X:
:
get_hh;
intf_xx(inta)
这是全局函数f_xx:
a<
intmain(intargc,char*argv[],charenv[])
intxx;
callbacklp;
xx=111;
呵呵!
!
第一步:
函数指针\"
endl<
---------------------------------------------------------\"
这就是函数指针直接调类中的静态成员函数get_xx:
lp(xx)<
f_xx;
这就是函数指针直接全局函数f_xx:
第二步:
跨类回调\"
YY_class;
这就是在Y类中调用了X类中的方法\"
Y_class.hehe(lp)<
cin>
>
xx;
return0;
主要需要注意的是在使用函数指针的时候,如果指向的函数是在某个类中,则该函数必须是静态成员函数
还有函数指针的申明格式,怪怪的哈,呵呵用多了就不觉得了,接下来我们用它来干点面向对象因该干的事情:
include<
intX_test(callbacklp,inta)
a--;
执行回调函数的之前,传入参数变成:
a=lp(a);
执行回调函数的之后,传入参数变成:
private:
Y(inta)
xx=a;
staticinthehe(inta)
for(inti=1;
i<
=10;
i++)
a++;
Xc_x;
hehe;
xx=c_x.X_test(lp,xx);
最终结果传到Y类中的xx变成了:
xx<
intmain(intargc,char*argv[],charenv[])[Page]
Y*y_c=newY(100);
y_c->
xixi();
这样在X类中使用了Y类中的方法,现在我们的X类只是做加法,而减法放在了Y类中。
当然这个只是为了便于理解才简化的,只是一个类比,比如一个类实现数据处理而另外一个实现视图处理。
是不是
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 调函