深入理解Callback函数
什么是回调函数
回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python、ECMAScript等更现代的编程语言中还可以使用仿函数或匿名函数。
回调函数的使用可以大大提升编程的效率,这使得它在现代编程中被非常多地使用。同时,有一些需求必须要使用回调函数来实现。
最著名的回调函数调用有C/C++标准库stdlib.h/cstdlib中的快速排序函数qsort和二分查找函数bsearch中都会要求的一个与strcmp类似的参数,用于设置数据的比较方法。
因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。简而言之,回调函数就是允许用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。
回调函数在实际中有许多作用。假设有这样一种情况:我们要编写一个库,它提供了某些排序算法的实现(如冒泡排序、快速排序、shell排序、shake排序等等),为了能让库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,能让库可用于多种数据类型(int、float、string),此时,可以使用函数指针,并进行回调。
回调可用于通知机制。例如,有时要在A程序中设置一个计时器,每到一定时间,A程序会得到相应的通知,但通知机制的实现者对A程序一无所知。那么,就需一个具有特定原型的函数指针进行回调,通知A程序事件已经发生。实际上,API使用一个回调函数SetTimer来通知A程序。如果没有提供回调函数,它还会把一个消息发往程序的消息队列。
另一个使用回调机制的API函数是EnumWindow,它枚举屏幕上所有的顶层窗口,每个窗口都可以通过它调用另一个程序提供的函数,并传递窗口的处理程序。例如:如果被调用者返回一个值,就继续进行迭代;否则,退出。EnumWindow并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。
C语言的回调函数只能通过函数指针实现,在C++中则可以使用匿名函数(lambda)或仿函数(functor)作为回调函数。
可以看到,给Linktable增加Callback方式的接口,需要两个函数接口,一个是call-in方式函数,如SearchLinkTableNode函数,其中有一个函数作为参数,这个作为参数的函数就是callback函数,即代码中Conditon函数。由于我们在工程化C编程中追求松散耦合,所以给call-in方式的函数接口SearchLinkTableNode增加了一个参数args,callback函数Conditon也增加了一个参数args。args用于传递用户输入的菜单命令(help、version和quit)。
实现代码
/*
* 在节点表中寻找一个节点
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Condition(tLinkTableNode *pNode, void *args), void * args){
if (pLinkTable == NULL || Condition == NULL) return NULL;
tLinkTableNode * pNode = pLinkTable->pHead;
while (pNode != NULL){
if (Condition(pNode, args) == SUCCESS) return pNode;
pNode = pNode->pNext;
}
return NULL;
}
事实上,该实现代码中没有实现Condition函数,而Condition是用户代码传进来的一个函数指针,只是我们在回调函数接口定义时规定了传进来的函数指针的规格。
全部用法如下:
typedef struct DataNode {
tLinkTableNode head;
char* cmd;
char* desc;
int (*handler)();
}tDataNode;
int SearchCondition(tLinkTableNode *pLinkTableNode, void *args) {
char * cmd = (char*) args;
tDataNode * pNode = (tDataNode *)pLinkTableNode;
if (strcmp(pNode->cmd, cmd) == 0) {
return SUCCESS;
}
return FAILURE;
}
/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd) {
return (tDataNode *)SearchLinkTableNode(head, SearchCondition, (void *)cmd);
}
附录代码
linktableInternal.h
#include <pthread.h>
/*
* LinkTable Node Type
*/
struct LinkTableNode
{
struct LinkTableNode * pNext;
};
/*
* LinkTable Type
*/
struct LinkTable
{
struct LinkTableNode *pHead;
struct LinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;
};
linktable.h
#ifndef _LINK_TABLE_H_
#define _LINK_TABLE_H_
#include "linktableInternal.h"
#define SUCCESS 0
#define FAILURE (-1)
/*
* LinkTable Node Type
*/
typedef struct LinkTableNode tLinkTableNode;
/*
* LinkTable Type
*/
typedef struct LinkTable tLinkTable;
/*
* Create a LinkTable
*/
tLinkTable * CreateLinkTable();
/*
* Delete a LinkTable
*/
int DeleteLinkTable(tLinkTable *pLinkTable);
/*
* Add a LinkTableNode to LinkTable
*/
int AddLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode *pNode);
/*
* Delete a LinkTableNode from LinkTable
*/
int DelLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode *pNode);
/*
* Serach a LinkTableNode from LinkTable
* int Condition(tLinkTableNode *pNode, void * args);
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Condition(tLinkTableNode *pNode, void *args), void * args);
/*
* get LinkTableHead
*/
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
/*
* get next LinkTableNode
*/
tLinkTableNode *GetNextLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode *pNode);
#endif /* _LINK_TABLE_H_ */
linktable.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "linktable.h"
/*
* Create a LinkTable
*/
tLinkTable * CreateLinkTable()
{
tLinkTable * pLinkTable = (tLinkTable *)malloc(sizeof(tLinkTable));
if (pLinkTable == NULL)
{
return NULL;
}
pLinkTable->pHead = NULL;
pLinkTable->pTail = NULL;
pLinkTable->SumOfNode = 0;
pthread_mutex_init(&(pLinkTable->mutex), NULL);
return pLinkTable;
}
/*
* Delete a LinkTable
*/
int DeleteLinkTable(tLinkTable *pLinkTable)
{
if (pLinkTable == NULL)
{
return FAILURE;
}
while (pLinkTable->pHead != NULL)
{
tLinkTableNode *p = pLinkTable->pHead;
pthread_mutex_lock(&(pLinkTable->mutex));
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1;
pthread_mutex_unlock(&(pLinkTable->mutex));
free(p);
}
pLinkTable->pHead = NULL;
pLinkTable->pTail = NULL;
pLinkTable->SumOfNode = 0;
pthread_mutex_destroy(&(pLinkTable->mutex));
free(pLinkTable);
return SUCCESS;
}
/*
* Add a LinkTableNode to LinkTable
*/
int AddLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode *pNode)
{
if (pLinkTable == NULL || pNode == NULL)
{
return FAILURE;
}
pNode->pNext = NULL;
pthread_mutex_lock(&(pLinkTable->mutex));
if (pLinkTable->pHead == NULL)
{
pLinkTable->pHead = pNode;
}
if (pLinkTable->pTail == NULL)
{
pLinkTable->pTail = pNode;
}
else
{
pLinkTable->pTail->pNext = pNode;
pLinkTable->pTail = pNode;
}
pLinkTable->SumOfNode += 1;
pthread_mutex_unlock(&(pLinkTable->mutex));
return SUCCESS;
}
/*
* Delete a LinkTableNode from LinkTable
*/
int DelLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode *pNode)
{
if (pLinkTable == NULL || pNode == NULL)
{
return FAILURE;
}
pthread_mutex_lock(&(pLinkTable->mutex));
if (pLinkTable->pHead == pNode)
{
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1;
if (pLinkTable->SumOfNode == 0)
{
pLinkTable->pTail = NULL;
}
pthread_mutex_unlock(&(pLinkTable->mutex));
return SUCCESS;
}
tLinkTableNode * pTempNode = pLinkTable->pHead;
while (pTempNode != NULL)
{
if (pTempNode->pNext == pNode)
{
pTempNode->pNext = pTempNode->pNext->pNext;
pLinkTable->SumOfNode -= 1;
if (pLinkTable->SumOfNode == 0)
{
pLinkTable->pTail = NULL;
}
pthread_mutex_unlock(&(pLinkTable->mutex));
return SUCCESS;
}
pTempNode = pTempNode->pNext;
}
pthread_mutex_unlock(&(pLinkTable->mutex));
return FAILURE;
}
/*
* Serach a LinkTableNode from LinkTable
* int Condition(tLinkTableNode *pNode, void * args);
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable,
int Condition(tLinkTableNode *pNode, void *args),
void * args)
{
if (pLinkTable == NULL || Condition == NULL)
{
return NULL;
}
tLinkTableNode * pNode = pLinkTable->pHead;
while (pNode != NULL)
{
if (Condition(pNode, args) == SUCCESS)
{
return pNode;
}
pNode = pNode->pNext;
}
return NULL;
}
/*
* get LinkTableHead
*/
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable)
{
if (pLinkTable == NULL)
{
return NULL;
}
return pLinkTable->pHead;
}
/*
* get next LinkTableNode
*/
tLinkTableNode *GetNextLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode *pNode)
{
if (pLinkTable == NULL || pNode == NULL)
{
return NULL;
}
tLinkTableNode * pTempNode = pLinkTable->pHead;
while (pTempNode != NULL)
{
if (pTempNode == pNode)
{
return pTempNode->pNext;
}
pTempNode = pTempNode->pNext;
}
return NULL;
}
menu.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "linktable.h"
int Help();
int Quit();
#define CMD_MAX_LEN 128
#define DESC_LEN 1024
#define CMD_NUM 10
typedef struct DataNode
{
tLinkTableNode head;
char* cmd;
char* desc;
int (*handler)();
}tDataNode;
int SearchCondition(tLinkTableNode *pLinkTableNode, void *args)
{
char * cmd = (char*) args;
tDataNode * pNode = (tDataNode *)pLinkTableNode;
if (strcmp(pNode->cmd, cmd) == 0)
{
return SUCCESS;
}
return FAILURE;
}
/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd)
{
return (tDataNode *)SearchLinkTableNode(head, SearchCondition, (void *)cmd);
}
/* show all cmd in listlist */
int ShowAllCmd(tLinkTable * head)
{
tDataNode * pNode = (tDataNode *)GetLinkTableHead(head);
while (pNode != NULL)
{
printf("%s - %s\n", pNode->cmd, pNode->desc);
pNode = (tDataNode *)GetNextLinkTableNode(head, (tLinkTableNode *)pNode);
}
return 0;
}
int InitMenuData(tLinkTable ** ppLinktable)
{
*ppLinktable = CreateLinkTable();
tDataNode* pNode = (tDataNode *)malloc(sizeof(tDataNode));
pNode->cmd = "help";
pNode->desc = "Menu List:";
pNode->handler = Help;
AddLinkTableNode(*ppLinktable, (tLinkTableNode *)pNode);
pNode = (tDataNode *)malloc(sizeof(tDataNode));
pNode->cmd = "version";
pNode->desc = "Menu Program V1.0";
pNode->handler = NULL;
AddLinkTableNode(*ppLinktable, (tLinkTableNode *)pNode);
pNode = (tDataNode *)malloc(sizeof(tDataNode));
pNode->cmd = "quit";
pNode->desc = "Quit from Menu Program V1.0";
pNode->handler = Quit;
AddLinkTableNode(*ppLinktable, (tLinkTableNode *)pNode);
return 0;
}
/* menu program */
tLinkTable * head = NULL;
int main()
{
InitMenuData(&head);
/* cmd line begins */
while (1)
{
char cmd[CMD_MAX_LEN];
printf("Input cmd > ");
scanf("%s", cmd);
tDataNode *p = FindCmd(head, cmd);
if (p == NULL)
{
printf("This is a wrong cmd!\n");
continue;
}
printf("%s - %s\n", p->cmd, p->desc);
if (p->handler != NULL)
{
p->handler();
}
}
return 0;
}
int Help()
{
printf("This is help can do for you:\n");
ShowAllCmd(head);
return 0;
}
int Quit()
{
printf("Bye Bye\n");
exit(0);
}