📄 帮你理解回调函数_大肚水杯.htm
字号:
href="http://hi.baidu.com/kaixinlaohe/blog">博客</A><A
href="http://hi.baidu.com/kaixinlaohe/album">相册</A><SPAN>|</SPAN><A
href="http://hi.baidu.com/kaixinlaohe/profile">个人档案</A> <SPAN>|</SPAN><A
href="http://hi.baidu.com/kaixinlaohe/friends">好友</A> </DIV></DIV>
<DIV class=stage>
<DIV class=stagepad>
<DIV style="WIDTH: 100%">
<TABLE class=modth cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=modtl width=7> </TD>
<TD class=modtc noWrap>
<DIV class=modhead><SPAN class=modtit>查看文章</SPAN></DIV></TD>
<TD class=modtc noWrap align=right></TD>
<TD class=modtr width=7> </TD></TR></TBODY></TABLE>
<DIV class=modbox id=m_blog>
<DIV class=tit>帮你理解回调函数</DIV>
<DIV class=date>2007年05月06日 下午 10:06</DIV>
<TABLE style="TABLE-LAYOUT: fixed">
<TBODY>
<TR>
<TD>
<DIV
class=cnt><STRONG>什么是回调函数?<BR><BR></STRONG> 简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。<BR><BR> <STRONG>为什么要使用回调函数?<BR><BR></STRONG> 因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。<BR><BR> 如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。<BR><BR> 回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer()
API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。<BR><BR> 另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。<BR><BR> 不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。<BR><BR> <STRONG>一个简单的回调函数实现<BR><BR></STRONG> 下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef
int (__stdcall *CompareFunction)(const byte*, const
byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。<BR><BR>
<P>
<TABLE borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9
border=1>
<TBODY>
<TR>
<TD><STRONG>void DLLDIR __stdcall Bubblesort(byte* array,int
size,int elem_size,CompareFunction cmpFunc);<BR><BR>void DLLDIR
__stdcall Quicksort(byte* array,int size,int
elem_size,CompareFunction cmpFunc);</STRONG></TD></TR></TBODY></TABLE></P>
<P><BR><STRONG> 这两个函数接受以下参数:<BR><BR> ·byte *
array:指向元素数组的指针(任意类型)。<BR><BR> ·int size:数组中元素的个数。<BR><BR> ·int
elem_size:数组中一个元素的大小,以字节为单位。<BR><BR> ·CompareFunction
cmpFunc:带有上述原型的指向回调函数的指针。<BR><BR> 这两个函数的会对数组进行某种排序,但每次都需决定两个元素哪个排在前面,而函数中有一个回调函数,其地址是作为一个参数传递进来的。对编写者来说,不必介意函数在何处实现,或它怎样被实现的,所需在意的只是两个用于比较的元素的地址,并返回以下的某个值(库的编写者和使用者都必须遵守这个约定):<BR><BR> ·-1:如果第一个元素较小,那它在已排序好的数组中,应该排在第二个元素前面。<BR><BR> ·0:如果两个元素相等,那么它们的相对位置并不重要,在已排序好的数组中,谁在前面都无所谓。
<BR><BR> ·1:如果第一个元素较大,那在已排序好的数组中,它应该排第二个元素后面。<BR><BR> 基于以上约定,函数Bubblesort()的实现如下,Quicksort()就稍微复杂一点:<BR><BR></STRONG></P>
<P>
<TABLE borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9
border=1>
<TBODY>
<TR>
<TD><STRONG>void DLLDIR __stdcall Bubblesort(byte* array,int
size,int elem_size,CompareFunction cmpFunc)<BR>{<BR> for(int i=0; i
< size; i++)<BR> {<BR> for(int j=0; j < size-1;
j++)<BR> {<BR> //回调比较函数<BR> if(1 ==
(*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size))<BR> {<BR> //两个相比较的元素相交换<BR> byte*
temp = new byte[elem_size];<BR> memcpy(temp, array+j*elem_size,
elem_size);<BR> memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size);<BR> memcpy(array+(j+1)*elem_size,
temp, elem_size);<BR> delete []
temp;<BR> }<BR> }<BR> }<BR>}</STRONG></TD></TR></TBODY></TABLE></P>
<P><BR><STRONG> 注意:因为实现中使用了memcpy(),所以函数在使用的数据类型方面,会有所局限。<BR><BR> 对使用者来说,必须有一个回调函数,其地址要传递给Bubblesort()函数。下面有二个简单的示例,一个比较两个整数,而另一个比较两个字符串:<BR><BR></STRONG></P>
<P>
<TABLE borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9
border=1>
<TBODY>
<TR>
<TD><STRONG>int __stdcall CompareInts(const byte* velem1, const
byte* velem2)<BR>{<BR> int elem1 = *(int*)velem1;<BR> int elem2 =
*(int*)velem2;<BR><BR> if(elem1 < elem2)<BR> return
-1;<BR> if(elem1 > elem2)<BR> return 1;<BR><BR> return
0;<BR>}<BR><BR>int __stdcall CompareStrings(const byte* velem1,
const byte* velem2)<BR>{<BR> const char* elem1 =
(char*)velem1;<BR> const char* elem2 = (char*)velem2;<BR> return
strcmp(elem1, elem2);<BR>}</STRONG></TD></TR></TBODY></TABLE></P>
<P><BR><STRONG> 下面另有一个程序,用于测试以上所有的代码,它传递了一个有5个元素的数组给Bubblesort()和Quicksort(),同时还传递了一个指向回调函数的指针。<BR><BR></STRONG></P>
<P>
<TABLE borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9
border=1>
<TBODY>
<TR>
<TD><STRONG>int main(int argc, char* argv[])<BR>{<BR> int i;<BR> int
array[] = {5432, 4321, 3210, 2109, 1098};<BR><BR> cout <<
"Before sorting ints with Bubblesort\n";<BR> for(i=0; i < 5;
i++)<BR> cout << array[i] <<
'\n';<BR><BR> Bubblesort((byte*)array, 5, sizeof(array[0]),
&CompareInts);<BR><BR> cout << "After the
sorting\n";<BR> for(i=0; i < 5; i++)<BR> cout << array[i]
<< '\n';<BR><BR> const char str[5][10] =
{"estella","danielle","crissy","bo","angie"};<BR><BR> cout <<
"Before sorting strings with Quicksort\n";<BR> for(i=0; i < 5;
i++)<BR> cout << str[i] <<
'\n';<BR><BR> Quicksort((byte*)str, 5, 10,
&CompareStrings);<BR><BR> cout << "After the
sorting\n";<BR> for(i=0; i < 5; i++)<BR> cout << str[i]
<< '\n';<BR><BR> return
0;<BR>}</STRONG></TD></TR></TBODY></TABLE></P>
<P><BR><STRONG> 如果想进行降序排序(大元素在先),就只需修改回调函数的代码,或使用另一个回调函数,这样编程起来灵活性就比较大了。<BR></STRONG></P>
<P><STRONG>调用约定<BR><BR></STRONG> 上面的代码中,可在函数原型中找到__stdcall,因为它以双下划线打头,所以它是一个特定于编译器的扩展,说到底也就是微软的实现。任何支持开发基于Win32的程序都必须支持这个扩展或其等价物。以__stdcall标识的函数使用了标准调用约定,为什么叫标准约定呢,因为所有的Win32
API(除了个别接受可变参数的除外)都使用它。标准调用约定的函数在它们返回到调用者之前,都会从堆栈中移除掉参数,这也是Pascal的标准约定。但在C/C++中,调用约定是调用者负责清理堆栈,而不是被调用函数;为强制函数使用C/C++调用约定,可使用__cdecl。另外,可变参数函数也使用C/C++调用约定。<BR><BR> Windows操作系统采用了标准调用约定(Pascal约定),因为其可减小代码的体积。这点对早期的Windows来说非常重要,因为那时它运行在只有640KB内存的电脑上。<BR><BR> 如果你不喜欢__stdcall,还可以使用CALLBACK宏,它定义在windef.h中:<BR><BR></P>
<TABLE borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9
border=1>
<TBODY>
<TR>
<TD>#define CALLBACK __stdcallor<BR><BR>#define CALLBACK PASCAL
//而PASCAL在此被#defined成__stdcall</TD></TR></TBODY></TABLE><BR> <STRONG>作为回调函数的C++方法</STRONG><BR><BR> 因为平时很可能会使用到C++编写代码,也许会想到把回调函数写成类中的一个方法,但先来看看以下的代码:<BR><BR>
<TABLE borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9
border=1>
<TBODY>
<TR>
<TD>class CCallbackTester<BR>{<BR> public:<BR> int CALLBACK
CompareInts(const byte* velem1, const byte*
velem2);<BR>};<BR><BR>Bubblesort((byte*)array, 5,
sizeof(array[0]),<BR>&CCallbackTester::CompareInts);</TD></TR></TBODY></TABLE><BR> 如果使用微软的编译器,将会得到下面这个编译错误:<BR><BR>
<TABLE borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9
border=1>
<TBODY>
<TR>
<TD>error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int
(__stdcall CCallbackTester::*)(const unsigned char *,const unsigned
char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned
char *)' There is no context in which this conversion is
possible</TD></TR></TBODY></TABLE><BR> 这是因为非静态成员函数有一个额外的参数:this指针,这将迫使你在成员函数前面加上static。当然,还有几种方法可以解决这个问题,但限于篇幅,就不再论述了
.</DIV></TD></TR></TBODY></TABLE><BR>
<DIV class=opt><A title=查看该分类中所有文章
href="http://hi.baidu.com/kaixinlaohe/blog/category/vc++">类别:vc++</A> | <A
title=将此文章添加到百度搜藏
onclick="window.open('http://cang.baidu.com/do/add?it='+encodeURIComponent('帮你理解回调函数'+'_百度空间')+'&iu='+encodeURIComponent(location.href)+'&fr=sp#nw=1','_s','scrollbars=no,width=600,height=450,right=75,top=20,status=no,resizable=yes'); return false;"
href="http://cang.baidu.com/do/add" target=_blank>添加到搜藏</A> | 浏览(<SPAN
id=result></SPAN>) </DIV>
<DIV class=line></DIV>
<DIV id=in_reader>
<DIV class=tit>最近读者:</DIV>
<TABLE width="100%">
<TBODY>
<TR>
<TD align=middle width="10%"><IMG height=55
src="帮你理解回调函数_大肚水杯.files/portraitn.jpg" width=55 border=0><BR> </TD>
<TD align=left width="12%">
<SCRIPT>document.write("<a href='http://passport.baidu.com/?login&tpl=sp&tpl_reg=sp&u="+myref+"' target='_self'>登录</a>后,您就出现在这里。");</SCRIPT>
</TD>
<TD class=user vAlign=bottom align=middle width="10%"><A
href="http://hi.baidu.com/sunyubo458" target=_blank><IMG
src="帮你理解回调函数_大肚水杯.files/1e1473756e7975626f343538f002.jpg"
border=0><BR>sunyubo458 </A></TD>
<TD class=user vAlign=bottom align=middle width="10%"><A
href="http://hi.baidu.com/hu_amao" target=_blank><IMG
src="帮你理解回调函数_大肚水杯.files/b89268755f616d616f7802.jpg" border=0><BR>hu_amao
</A></TD>
<TD class=user vAlign=bottom align=middle width="10%"><A
href="http://hi.baidu.com/xiaoh274" target=_blank><IMG
src="帮你理解回调函数_大肚水杯.files/b81e7869616f68323734bd00.jpg"
border=0><BR>xiaoh274 </A></TD>
<TD class=user vAlign=bottom align=middle width="10%"><A
href="http://hi.baidu.com/jiangguiqing" target=_blank><IMG
src="帮你理解回调函数_大肚水杯.files/d4716a69616e675f6775695f71696e67f102.jpg"
border=0><BR>jiang_gui_qing </A></TD>
<TD class=user vAlign=bottom align=middle width="10%"><A
href="http://hi.baidu.com/yanzimywife" target=_blank><IMG
src="帮你理解回调函数_大肚水杯.files/da6279616e7a696d79776966658900.jpg"
border=0><BR>yanzimywife </A></TD>
<TD width="100%"></TD></TR></TBODY></TABLE></DIV>
<DIV class=line></DIV>
<SCRIPT language=JavaScript>
allkey=allkey+"fe010724b315d133c9955901_388f78cf7dcc143cf8dc61db_";
</SCRIPT>
<DIV id=in_comment><A name=comment></A>
<DIV class=tit>网友评论:</DIV>
<SCRIPT>
function writecmt(type,id,cmtname,cmturl,cmttime){
var html1="";
if(type==1){
if(cmturl==""){
html1="<a name='"+id+"'>"+cmtname+"</a> - <span class='date'>"+cmttime+"</span> ";
}else{
html1="<a name='"+id+"' href='"+cmturl+"' target='_blank' title='"+cmturl+"'>"+cmtname+"</a> - <span class='date'>"+cmttime+"</span> ";
}
}else{
if(cmtname=="匿名网友"){
if(cmturl==""){
html1="<a name='"+id+"'>"+cmtname+"</a> - <span class='date'>"+cmttime+"</span> ";
}else{
html1="<a name='"+id+"' href='"+cmturl+"' target='_blank' title='"+cmturl+"'>"+cmtname+"</a> - <span class='date'>"+cmttime+"</span>";
}
}else{
if(cmturl==""){
html1="<div class='f14' style='display:inline'>网友:<a name='"+id+"'>"+cmtname+"</a> - <span class=\"date\">"+cmttime+"</span></div>";
}else{
html1="<div class='f14' style='display:inline'>网友:<a name='"+id+"' href='"+cmturl+"' target='_blank' title='"+cmturl+"'>"+cmtname+"</a> - <span class=\"date\">"+cmttime+"</span></div>";
}
}
}
document.write(html1);
}
</SCRIPT>
<DIV id=page></DIV></DIV>
<DIV id=in_send>
<FORM id=popFormSubmit name=form1 onsubmit="return checkcmtform()"
action=/kaixinlaohe/commit method=post><INPUT type=hidden value=8 name=ct>
<INPUT type=hidden value=1 name=cm> <INPUT type=hidden
value=388f78cf7dcc143cf8dc61db name=spBlogID>
<SCRIPT language=JavaScript>
document.write("<input type='hidden' name='spRefURL' value='"+window.location.href+"'>");
</SCRIPT>
<DIV class=tit>发表评论:</DIV>
<TABLE cellSpacing=5 cellPadding=0 width=620 border=0>
<TBODY>
<TR>
<TD class=f14>姓 名:</TD>
<TD><INPUT id=spBlogCmtor style="WIDTH: 220px" onfocus=hidErr(1);
tabIndex=1 maxLength=49 onchange="checkname('spBlogCmtor')"
name=spBlogCmtor>
<SCRIPT>
document.write(" <a href='http://passport.baidu.com/?reg&tpl=sp&return_method=get&skip_ok=1&u=http://hi.baidu.com/sys/reg/' target='_blank'>注册</a>");
document.write(" | <a href='http://passport.baidu.com/?login&tpl=sp&tpl_reg=sp&u="+myref+"'>登录</a>");
</SCRIPT>
<DIV id=nmerror style="DISPLAY: none">*姓名最长为50字节</DIV></TD></TR>
<TR id=1_err style="DISPLAY: none">
<TD> </TD>
<TD>
<DIV class=error id=1_err_con></DIV></TD></TR>
<TR>
<TD class=f14>网址或邮箱:</TD>
<TD><INPUT id=spBlogCmtURL style="WIDTH: 360px" onfocus=hidErr(2);
tabIndex=2 maxLength=128 onchange="checkeandu('spBlogCmtURL')"
name=spBlogCmtURL> (选填)</TD>
<SCRIPT>
G("spBlogCmtor").value="";
G("spBlogCmtURL").value="";
</SCRIPT>
</TR>
<TR id=2_err style="DISPLAY: none">
<TD> </TD>
<TD>
<DIV class=error id=2_err_con></DIV></TD></TR>
<TR>
<TD class=f14 vAlign=top>内 容:</TD>
<TD><TEXTAREA id=spBlogCmtText style="WIDTH: 520px; HEIGHT: 155px" onfocus=hidErr(3); tabIndex=3 name=spBlogCmtText></TEXTAREA>
<SCRIPT>
G("spBlogCmtor").value=G("spBlogCmtor").defaultValue;
G("spBlogCmtText").value="";
</SCRIPT>
</TD></TR>
<TR id=3_err style="DISPLAY: none">
<TD> </TD>
<TD>
<DIV class=error id=3_err_con></DIV></TD></TR>
<TR id=vercode>
<TD class=f14 vAlign=top>验证码:</TD>
<TD vAlign=top><INPUT type=hidden
value=21720140DC3B8B76FDFC4A6F66CE10491A456EF5C9096D6B1C6953B63FB6FCFA15506AF54E8B32C1E465FC696DAE038494F9B5CC22030779D2B3A883E2C6513B
name=spVcode> <INPUT id=spVerifyKey tabIndex=4 maxLength=4 size=6
name=spVerifyKey autocomplete="off">请输入下图中的四位验证码,字母不区分大小写。<BR>
<SCRIPT language=JavaScript>
var imgsrc="http://post.baidu.com/cgi-bin/genimg?21720140DC3B8B76FDFC4A6F66CE10491A456EF5C9096D6B1C6953B63FB6FCFA15506AF54E8B32C1E465FC696DAE038494F9B5CC22030779D2B3A883E2C6513B";
document.write("<img id='verifypic' src='"+imgsrc+"' width='120' height='40'>");
function newverifypic(){
document.getElementById("verifypic").src = imgsrc +"&t="+ Math.random();
}
</SCRIPT>
<A title=看不清左边的字符 href="javascript:newverifypic();">看不清?</A> </TD></TR>
<TR>
<TD class=f14 vAlign=top> </TD>
<TD class=f14 vAlign=top><INPUT id=btn_ok tabIndex=5 type=submit value=发表评论 name=btn_ok></TD></TR></TBODY></TABLE></FORM></DIV><BR></DIV>
<TABLE height=8 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=modbl width=7> </TD>
<TD class=modbc> </TD>
<TD class=modbr width=7> </TD></TR></TBODY></TABLE></DIV></DIV></DIV></DIV>
<SCRIPT language=javascript>
<!--
var hstr="/kaixinlaohe/brwstat?key1=1";
document.write("<script src='"+hstr+"&key2="+allkey+"'><\/script>");
//-->
</SCRIPT>
<BR>
<CENTER>
<DIV id=ft>©2007 Baidu</DIV></CENTER>
<SCRIPT>
if(document.getElementById("m_blog"))
{
var imgarray = document.getElementById("m_blog").getElementsByTagName('img');
var imgw = document.getElementById("m_blog").offsetWidth;
imgw =imgw-40;
for(var i=0; i<imgarray.length; i++){
if(imgarray[i].className=="blogimg" && imgarray[i].width>=imgw) imgarray[i].width=imgw;
}
}
</SCRIPT>
</CENTER><IMG style="DISPLAY: none" src=""> </BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -