俺理解的回调概念:
回调函数可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调运函数。简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。
牛人们的回调概念:
对指针的应用是C语言编程的精髓所在,而回调函数就是C语言里面对函数指针的高级应用。简而言之,回调函数是一个通过函数指针调用的函数。如果你把函数指针(函数的入口地址)传递给另一个函数,当这个函数指针被用来调用它所指向的函数时,我们就说这个函数是回调函数。
本人不太喜欢概念,喜欢从代码分析原理,但是理论还是很重要滴,所以别的不多说,先来一个简单代码示例:
Step 1示例:(注意:如果要想看懂回调,单单是看懂哦。你至少需要具备了解函数的指针变量及函数的指针变量做形参的概念,基础是很重要滴!)
int GetMaxValue(int iDataA, int iDataB)
{
return (iDataA > iDataB) ? iDataA : iDataB;
}
int GetFunc(int iDataA, int (*pFuncPoint)(int, int))
{
pFuncPoint(iDataA, 12);
}
int main (int argc, char **argv)
{
int iData = 24;
GetFunc(iData, GetMaxValue);
return 0;
}
Step2示例:(注意:看懂这个示例前提是完全理解上个示例哦!)
/*************************************************************
有一个快速排序算法,实现了快排算法的逻辑,但是快排算法中必须
涉及数据大小的比较,为提高程序的通用性,掉用者提供一个比较函
数,这样排序函数借此调用调用者的函数来比较。
C语言的标准库函数中很多地方就采用了回调函数来让用户定制处理过
程。如常用的快速排序函数、二分搜索函数等。
**************************************************************/
#include <stdio.h>
#include <stdlib.h>
int TheSortFunction(const void *pvDataA, const void *pvDataB);
int main(int argc, char *argv[])
{
int iIndex = 0;
int aiList [10] = {1, 0, 2, 4, 3, 5, 6, 9, 8, 7};
SortFunc((void*)aiList, 10, sizeof(int), TheSortFunction);
for (iIndex = 0; iIndex < 10; iIndex++)
{
printf("%i ",aiList[iIndex]);
}
printf("\n");
return 0;
}
/********************************************************************
用户在调用SortFunc函数时,需要自己实现一个int类型的回调函数,
然后,就可以直接把TheSortFunction当作一个变量传递给SortFunc。
*********************************************************************/
int TheSortFunction(const void *pvDataA, const void *pvDataB)
{
return *(int *)pvDataA - *(int *)pvDataB;
}
/*********************************************************************
Suppose this function in the C-Lib(qsort).Not in this main.c file.
快速排序函数原型。也可以有二分等原型,此处没列举。
其中fcmp就是一个回调函数的变量。
如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生
在运行时,这样使你能实现动态绑定。
**********************************************************************/
void SortFunc(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
Step3示例:(这个例子来源于网络搜索的,不是鄙人的,但是代码等是鄙人写的,非常到位,感谢原作者)
首先有这么一个功能函数,想必大家都懂得,这个函数作用是在一个单向链表中查找一个指定的值,返回保存这个值的节点。
/**************************************************** 查找一个指定的值返回节点地址。 ****************************************************/ Node * SearchList(Node * pstNode, const int iValue) { while (NULL != pstNode) { if (pstNode->iValue == iValue) { break; } pstNode = pstNode->pNext; } return pstNode; }
Question:这个函数看上去很easy,但是它只能适用于值为整数的链表,如果查找一个字符串链表等其他类型呢?
我们初学者通常做法:不得不再写一个函数,其实大部分代码和现在这个函数相同,只是第二个参数的类型和比较的方法不同。
砖家大牛经验(偷学的):大牛他们更希望令查找函数与类型无关,这样它就能用于查找存放任何类型值的链表了,因此必须改变比较的方式,而借助回调函数就可以达到这个目的。所以编写一个函数(回调函数),用于比较两个同类型的值,然后把一个指向这个函数的指针作为参数传递给查找函数,查找函数调用这个比较函数来执行比较,采用这个方法,任何类型的值得都可以进行比较。此时不得不感慨C语言博大进深啊,大牛们更牛逼啊!
/**************************************************** 大牛改版的查找一个指定的值返回节点地址。 ****************************************************/ Node * SearchList(Node * pstNode, int (*Compare)(void const *, void const *), void const *DesireValue) { while (NULL != pstNode) { if (0 == Compare((pstNode->ValueAddress), DesireValue)) { break; } pstNode = pstNode->pNext; } return pstNode; }
可以看到,用户将一个函数指针传递给查找函数,后者将回调这个函数。
注意这里我们的链表节点是这样定义的:
typedef struct list { void * ValueAddress; struct list *pNext; }Node;
这样定义可以让NODE *类型的指针指向存储任何类型数据的链表节点。而ValueAddress就是指向具体数据的指针,我们把它定义为void *,表示一个指向未知类型的指针,这样链表就可以存储任何类型的数据了,而我们传递给查找函数SearchList的第一个参数就可以统一表示为:NODE *,否则,还是要分别写查找函数以适应存储不同数据类型的链表。
现在,查找函数与类型无关,因为它不进行实际的比较,因此,我们必须编写针对不同类型的比较函数,这是很容易实现的,因为调用者知道链表中所包含的值的类型,如果创建几个分别包含不同类型值的链表,为每种类型编写一个比较函数就允许单个查找函数作用于所有类型的链表。
下面是一个比较函数,用于在一个整型链表中查找:
int int_compare(void const *a, void const *b) { if (*(int *)a == *(int *)b) { return 0; } else { return -1; } }
注意强制类型转换,比较函数的参数必须被声明为void *以匹配查找函数的原型,然后强制转换为(int *)类型用于比较整型。这个函数可以这样被使用:
DesireNode = SearchList(root, int_compare, &DesireIntValue);
如果你希望在一个字符串链表中进行查找,下面的代码就可以完成任务:
DesireNode = SearchList(root, strcmp, “abcdefg”);
正好库函数strcmp所执行的比较和我们需要的一样,不过gcc会发出警告信息:因为strcmp的参数被声明为const char *而不是void const *。
Step4示例:
上面的例子展示了回调函数的基本原理和用法,回调函数的应用是非常广泛的。通常,当我们想通过一个统一接口实现不同内容的时候,用回调函数来实现就非常合适。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同的类型的工作或者执行只能由函数调用者定义的工作,你都可以用回调函数来实现。许多窗口系统就是使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定调用用户程序中的某个特定函数。
回调函数博大精深,运用宽广,对于俺这种菜鸟还需要继续学习啊!Fighting,coding。