什么是回调?
简而言的回调就是个通过指针如果你把指针(地址)作为参数传递给另个当这个指针被用为它所指向时我们就说这是回调
为什么要使用回调?
可以把者和被者分开者不关心谁是被者所有它需知道只是存在个具有某种特定原型、某些限制条件(如返回值为)被
如果想知道回调在实际中有什么作用先假设有这样种情况我们要编写个库它提供了某些排序算法实现如冒泡排序、快速排序、shell排序、shake排序等等但为使库更加通用不想在中嵌入排序逻辑而让使用者来实现相应逻辑;或者想让库可用于多种数据类型(、float、)此时该如何办呢?可以使用指针并进行回调
回调可用于通知机制例如有时要在中设置个计时器每到定时间会得到相应通知但通知机制实现者对我们无所知而此时就需有个特定原型指针用这个指针来进行回调来通知我们事件已经发生实际上SetTimer API使用了个回调来通知计时器而且万没有提供回调它还会把个消息发往消息队列
另个使用回调机制API是EnumWindow它枚举屏幕上所有顶层窗口为每个窗口个提供并传递窗口处理如果被者返回个值就继续进行迭代否则退出EnumWindow并不关心被者在何处也不关心被者用它传递处理做了什么它只关心返回值基于返回值它将继续执行或退出
不管如何说回调是继续自C语言因而在C中应只在和C代码建立接口或和已有回调接口打交道时才使用回调除了上述情况在C中应使用虚拟思路方法或符(functor)而不是回调
个简单回调实现
下面创建了个sort.dll动态链接库它导出了个名为CompareFunction类型--typedef (__stdcall *CompareFunction)(const *, const *)它就是回调类型另外它也导出了两个思路方法:Bubblesort和Quicksort这两个思路方法原型相同但实现了区别排序算法
void DLLDIR __stdcall Bubblesort(* .gif' />, size, elem_size,CompareFunction cmpFunc);
void DLLDIR __stdcall Quicksort(* .gif' />, size, elem_size,CompareFunction cmpFunc);
这两个接受以下参数:
· * .gif' />:指向元素指针(任意类型)
· size:中元素个数
· elem_size:中个元素大小以字节为单位
·CompareFunction cmpFunc:带有上述原型指向回调指针
这两个会对进行某种排序但每次都需决定两个元素哪个排在前面而中有个回调其地址是作为个参数传递进来对编写者来说不必介意在何处实现或它怎样被实现所需在意只是两个用于比较元素地址并返回以下某个值(库编写者和使用者都必须遵守这个约定):[Page]
·-1:如果第个元素较小那它在已排序好中应该排在第 2个元素前面
·0:如果两个元素相等那么它们相对位置并不重要在已排序好中谁在前面都无所谓
·1:如果第个元素较大那在已排序好中它应该排第 2个元素后面
基于以上约定Bubblesort实现如下Quicksort就稍微复杂点:
void DLLDIR __stdcall Bubblesort(* .gif' />, size, elem_size,CompareFunction cmpFunc)
{
for( i=0; i < size; i)
{
for( j=0; j < size-1; j)
{
//回调比较
(1 (*cmpFunc)(.gif' />+j*elem_size,.gif' />+(j+1)*elem_size))
{
//两个相比较元素相交换
* temp = [elem_size];
memcpy(temp, .gif' />+j*elem_size, elem_size);
memcpy(.gif' />+j*elem_size,.gif' />+(j+1)*elem_size,elem_size);
memcpy(.gif' />+(j+1)*elem_size, temp, elem_size);
delete temp;
}
}
}
}
注意:实现中使用了memcpy所以在使用数据类型方面会有所局限
对使用者来说必须有个回调其地址要传递给Bubblesort下面有 2个简单举例个比较两个整数而另个比较两个串:
__stdcall CompareInts(const * velem1, const * velem2)
{
elem1 = *(*)velem1;
elem2 = *(*)velem2;
(elem1 < elem2)
-1;
(elem1 > elem2)
1;
0;
}
__stdcall CompareStrings(const * velem1, const * velem2)
{
const char* elem1 = (char*)velem1;
const char* elem2 = (char*)velem2;
strcmp(elem1, elem2);
}
下面另有个用于测试以上所有代码它传递了个有5个元素给Bubblesort和Quicksort同时还传递了个指向回调指针
( argc, char* argv)
{
i;
.gif' /> = {5432, 4321, 3210, 2109, 1098};
cout << \"Before sorting s with Bubblesort\\n\";
for(i=0; i < 5; i)
cout << .gif' />[i] << \'\\n\';
Bubblesort((*).gif' />, 5, (.gif' />[0]), &CompareInts);
cout << \"After the sorting\\n\";
for(i=0; i < 5; i)
cout << .gif' />[i] << \'\\n\';
const char str[5][10] = {\"estella\",\"danielle\",\"crissy\",\"bo\",\"angie\"};
cout << \"Before sorting s with Quicksort\\n\";
for(i=0; i < 5; i)
cout << str[i] << \'\\n\';
Quicksort((*)str, 5, 10, &CompareStrings);
cout << \"After the sorting\\n\";
for(i=0; i < 5; i)
cout << str[i] << \'\\n\';
0;
}
如果想进行降序排序(大元素在先)就只需修改回调代码或使用另个回调这样编程起来灵活性就比较大了
约定
上面代码中可在原型中找到__stdcall它以双下划线打头所以它是个特定于编译器扩展说到底也就是微软实现任何支持开发基于Win32都必须支持这个扩展或其等价物以__stdcall标识使用了标准约定为什么叫标准约定呢所有Win32 API(除了个别接受可变参数除外)都使用它标准约定在它们返回到者的前都会从堆栈中移除掉参数这也是Pascal标准约定但在C/C中约定是者负责清理堆栈而不是被;为强制使用C/C约定可使用__cdecl另外可变参数也使用C/C约定[Page]
Windows操作系统采用了标准约定(Pascal约定)其可减小代码体积这点对早期Windows来说非常重要那时它运行在只有640KB内存电脑上
如果你不喜欢__stdcall还可以使用CALLBACK宏它定义在windef.h中:
# CALLBACK __stdcallor
# CALLBACK PASCAL //而PASCAL在此被#d成__stdcall
作为回调C思路方法
平时很可能会使用到C编写代码也许会想到把回调写成类中个思路方法但先来看看以下代码:
CCallbackTester
{
public:
CALLBACK CompareInts(const * velem1, const * velem2);
};
Bubblesort((*).gif' />, 5, (.gif' />[0]),
&CCallbackTester::CompareInts);
如果使用微软编译器将会得到下面这个编译:
error C2664: \'Bubblesort\' : cannot convert parameter 4 from \' (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)\' to \' (__stdcall *)(const unsigned char *,const unsigned char *)\' There is no context in which this conversion is possible
这是非静态成员有个额外参数:this指针这将迫使你在成员前面加上当然还有几种思路方法可以解决这个问题但限于篇幅就不再论述了
最新评论