函数指针--Nginx和Redis中两种回调函数写法
2013-08-11 16:41
190 查看
1.Nginx和Redis中两种回调函数写法
函数名就是一个指针,如同于数组a[],a和&a其实都是一样的。当调用一个函数时,我们都是直接用函数名调用,或者说通过指针调用。
风格1--tpyedef自定义函数指针类型
风格2--typedef自定义函数类型
《C语言程序设计:现代方法:第2版》
2.函数指针定义
函数指针及应用
我们先来看一下以下 的声明:
int f(int);
int (*pf)(int)=&f;//&操作符可选;因为函数名被使用时总是由编译器把它转换为函数指针;
或者
pf=f;
int ans;
ans=f(25);
ans=(*pf)(25);
ans=pf(25);//间接访问操作并非必需,因为编译器需要的是一个函数指针;
使用举例:
3.[b]函数指针应用一--回调函数[/b]
这里有一个简单的函数,它用于在一个单链表中查找一个值,它的参数是一个指向链表第一个节点的指针以及那个需要查找的值.
这个函数看上去相当简单,但它只适用于值为整数的链表,如果你需要在一个字符串链表中查找,你不得不另外编写一个函数,这个函数和上面那个函数的绝大部分代码相同,只是第二个参数的类型以及节点值的比较方法不同.
一种更为通用的方法是查找函数与类型无关,这样它就能用于任何类型的值的链表,我们必须对函数的两个方面进行修改,使它与类型无关.首先我们必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较.这个目标听上去好象不可能,如果你编写语句用于比较整型值,它怎么还可能用于其他类型如字符串的比较呢?解决方案就是使用函数指针,调用者编写一个函数,用于比较两个值,然后把一个指向这个函数的指针作为参数传递给查找函数.然后查找函数调用这个函数来执行值的比较,使用这种方法,任何类型的值都可以进行比较.我们必须修改的第二个方面是向函数传递一个指向值的指针而不是本身.函数由一个void
*形参,用于接收这个参数,然后指向这个值的指针便传递给比较函数,这个修改使字符串和数组对象也可以被使用,字符串和数组无法作为参数传递给函数,但指向它们的指针可以.
使用这种技巧的函数叫"回调函数"(callback function);因为用户把一个函数指针作为参数传递给其他函数,后者将"回调"用户的函数.任何时候,如果你所编写的
函数必须能够在不同的时刻执行不同类型的工作或执行只能由函数调用者定义的工作,你都可以使用这个技巧.许多窗口系统使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定用户程序中的某个特定函数.我们无法在这个上下文环境中为回调函数编写一个准确的原型,因为我们并不知道进行比较的值的类型.事实上,我们需要查找函数能作用于任何类型的值,解决这个难题的方法是把参数类型声明为"void *",表示"一个指向未知类型的指针".
同时注意虽然函数不会修改参数node所指向的任何节点,但node并未声明为const。如果node被声明为const,函数不得不返回一个const结果,这将限制调用程序,它便无法修改查找函数所找到的节点。
在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并把指向该函数的指针和指向需要查找的值的指针传递给查找函数。例如,下面是一个比较函数,它用于在一个整数链表中进行查找。
这个函数将像下面这样使用:
desired_node=search_list(root,&desired_value,compare_ints);
4.函数指针应用二--转移表(jump table)
转移表最好用个例子来解释。下面的代码段取自一个程序,它用于实现一个袖珍式计算器。程序的其他部分已经读入两个数(op1和op2)和一个操作符(oper)。下面的代码对操作符进行测试,最后决定调用哪个函数。
对于一个新奇的具有上百个操作符的计算器,这条switch语句将会非常之长。
为什么要调用函数来执行这些操作呢?把具体操作和选择操作的代码分开是一种良好的设计方案。更为复杂的操作将肯定以独立的函数来实现,因为它们的长度可能很长。但即使是简单的操作也可能具有副作用,例如保存一个常量值用于以后的操作。
为了使用switch语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针
数组。
创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一
需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。
double add(double,double);
double sub(double,double);
double mul(double,double);
double div(double,double);
……
double (*oper_func[])(double,double)={
add,sub,mul,div,
……};
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子假定ADD是0,SUB是1,MUL是2,接下去以此类推。
第二个步骤是用下面这条语句替换前面整条switch语句!
result=oper_func[oper](op1,op2);
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。
5.复杂的函数指针拆解
void (*signal (int signo,void (*func) (int) )) (int)
这一大堆看起来很难,其实仔细分析下不算很难搞。
首先要明白一件事:这里都是从最基本的语法展开的。
那么这里最基本的语法就是函数的声明:返回值 函数名(参数)。
先将这一大堆给看成void (f)(int)也就是将*signal (int signo,void (*func) (int) )看成f,那么相对而言就比较好理解了。
那么signal就是函数名,而他的返回值为指向函数f的指针即(指向一个返回值为void参数为int的函数的指针)。
然后signal的参数为signo和一个返回值为void参数为int的函数指针。
然后那个f的返回值为void 参数为int这就不必多说了。
好了,关于void (*signal (int signo,void (*func) (int) )) (int)这个字面上的意思大抵就是这样。
其实是unix信号通信机制里面的。具体如下:
void (*signal(int signo, void (*func)(int)))(int);
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
从这里结合上面的分析我们可以得出singal这个函数的返回值类型为void(*)(int)。
同时
#define SIG_ERR (void (*)(int))-1
#define SIG_DFL (void (*)(int))0
#define SIG_IGN (void (*)(int))1
这里以(void(*)())1表示将1强制性转换为返回值为void参数为int的函数指针。
不知道干嘛要这样,后来看到一个demo后明白了。
if (signal(SIGINT, sig_int) == SIG_ERR)这里用来捕捉各个信号。
由于signal的返回值类型为void(*)(int)所以为了区别各个信号所以只能将这些东西定义为这么复杂的声明了。
关于typedef参考:深度分析typedef
#include <stdio.h> //仿Nginx风格 //结构外声明函数指针类型 typedef void (*ngx_connection_handler_pt)(int c); //仿redis风格 typedef void redisCommandProc(int c); typedef struct { int a; //结构内定义函数指针变量pshow void (*pshow)(int); //结构内定义函数指针变量 ngx_connection_handler_pt handler; redisCommandProc *proc; }TMP; void func(TMP *tmp) { tmp->handler(tmp->a); tmp->proc(tmp->a); if(tmp->a >10)//如果a>10,则执行回调函数。 { tmp->pshow(tmp->a); } } void show(int a) { printf("a的值是%d\n",a); } void main() { TMP test; test.a = 11; test.pshow = show; test.handler = show; test.proc = show; func(&test); }
函数名就是一个指针,如同于数组a[],a和&a其实都是一样的。当调用一个函数时,我们都是直接用函数名调用,或者说通过指针调用。
风格1--tpyedef自定义函数指针类型
#include <stdio.h> typedef int (*fp_t)(char c); int f0(char c) { printf("f0, c = %c\n", c); return 0; } int f1(char c) { printf("f1, c = %c\n", c); return 1; } int main(){ int ret; fp_t fp;//fp是一个指向一个函数类型(返回的是int,参数是char)的函数指针 fp = f0; ret = fp('a');通过函数指针调用函数 fp = f1; ret = fp('x'); return 0; }
风格2--typedef自定义函数类型
#include <stdio.h> typedef int f_t(char c); int f0(char c) { printf("f0, c = %c\n", c); return 0; } int f1(char c) { printf("f1, c = %c\n", c); return 1; } int main() { int ret; f_t *fp;//f_t是函数类型,所以fp是指向此函数类型的指针 fp = f0; ret = fp('a'); fp = f1; ret = fp('x');//函数指针调用此函数 return 0; }
《C语言程序设计:现代方法:第2版》
2.函数指针定义
函数指针及应用
我们先来看一下以下 的声明:
int f(int);
int (*pf)(int)=&f;//&操作符可选;因为函数名被使用时总是由编译器把它转换为函数指针;
或者
pf=f;
int ans;
ans=f(25);
ans=(*pf)(25);
ans=pf(25);//间接访问操作并非必需,因为编译器需要的是一个函数指针;
使用举例:
#include <stdio.h> int getA(int a) { return a+1; } int getB(int b) { return b*2; } int search(void const * a,void const * b,int(*compare)(void const *,void const *)) { return compare(a,b); } int copmare_int(void const *a,void const *b) { if(*(int *)a==*(int *)b) { return 0; } else{ return 1; } } int (*pf)(int k); int main(void) { int f=0; printf( "please input>>>\n"); scanf("%d",&f); if(f==1) { pf=getA; } else{ pf=getB; } int ff=pf(f); printf( "ff =%d\n", ff); int f1=0; int f2=0; int x1=2; int x2=2; f1=search(&x1,&x2,copmare_int); printf( "f1 =%d\n", f1); }
3.[b]函数指针应用一--回调函数[/b]
这里有一个简单的函数,它用于在一个单链表中查找一个值,它的参数是一个指向链表第一个节点的指针以及那个需要查找的值.
Node* search_list(Node* node,int const value) { while(node!=NULL) { if(node->value==value) break; node=node->link; } return node; }
这个函数看上去相当简单,但它只适用于值为整数的链表,如果你需要在一个字符串链表中查找,你不得不另外编写一个函数,这个函数和上面那个函数的绝大部分代码相同,只是第二个参数的类型以及节点值的比较方法不同.
一种更为通用的方法是查找函数与类型无关,这样它就能用于任何类型的值的链表,我们必须对函数的两个方面进行修改,使它与类型无关.首先我们必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较.这个目标听上去好象不可能,如果你编写语句用于比较整型值,它怎么还可能用于其他类型如字符串的比较呢?解决方案就是使用函数指针,调用者编写一个函数,用于比较两个值,然后把一个指向这个函数的指针作为参数传递给查找函数.然后查找函数调用这个函数来执行值的比较,使用这种方法,任何类型的值都可以进行比较.我们必须修改的第二个方面是向函数传递一个指向值的指针而不是本身.函数由一个void
*形参,用于接收这个参数,然后指向这个值的指针便传递给比较函数,这个修改使字符串和数组对象也可以被使用,字符串和数组无法作为参数传递给函数,但指向它们的指针可以.
使用这种技巧的函数叫"回调函数"(callback function);因为用户把一个函数指针作为参数传递给其他函数,后者将"回调"用户的函数.任何时候,如果你所编写的
函数必须能够在不同的时刻执行不同类型的工作或执行只能由函数调用者定义的工作,你都可以使用这个技巧.许多窗口系统使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定用户程序中的某个特定函数.我们无法在这个上下文环境中为回调函数编写一个准确的原型,因为我们并不知道进行比较的值的类型.事实上,我们需要查找函数能作用于任何类型的值,解决这个难题的方法是把参数类型声明为"void *",表示"一个指向未知类型的指针".
/* **在一个单链表中查找一个指定值的函数,它的参数是一个指向链表第一个节点 **的指针,一个指向我们需要查找的值的指针和一个函数指针,它所指向的函数 **用于比较存储于此链表中的类型的值. */ #include<stdio.h> #include "node.h" Node* search_list(Node *node,void const *value, int(*compare)(void const*,void const*)) //函数声明; { while (node!=NULL) { if(compare(&node->value,value)==0) break; node=node->link; } return node; }
同时注意虽然函数不会修改参数node所指向的任何节点,但node并未声明为const。如果node被声明为const,函数不得不返回一个const结果,这将限制调用程序,它便无法修改查找函数所找到的节点。
在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并把指向该函数的指针和指向需要查找的值的指针传递给查找函数。例如,下面是一个比较函数,它用于在一个整数链表中进行查找。
int compare_ints(void const* a,void const* b) { if(*(int*)a==*(int*)b) return 0; else return 1; }
这个函数将像下面这样使用:
desired_node=search_list(root,&desired_value,compare_ints);
4.函数指针应用二--转移表(jump table)
转移表最好用个例子来解释。下面的代码段取自一个程序,它用于实现一个袖珍式计算器。程序的其他部分已经读入两个数(op1和op2)和一个操作符(oper)。下面的代码对操作符进行测试,最后决定调用哪个函数。
switch(oper) { case ADD: result=add(op1,op2);break; case SUB: result=sub(op1,op2);break; case MUL: result=mul(op1,op2);break; case DIV: result=div(op1,op2);break; }
对于一个新奇的具有上百个操作符的计算器,这条switch语句将会非常之长。
为什么要调用函数来执行这些操作呢?把具体操作和选择操作的代码分开是一种良好的设计方案。更为复杂的操作将肯定以独立的函数来实现,因为它们的长度可能很长。但即使是简单的操作也可能具有副作用,例如保存一个常量值用于以后的操作。
为了使用switch语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针
数组。
创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一
需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。
double add(double,double);
double sub(double,double);
double mul(double,double);
double div(double,double);
……
double (*oper_func[])(double,double)={
add,sub,mul,div,
……};
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子假定ADD是0,SUB是1,MUL是2,接下去以此类推。
第二个步骤是用下面这条语句替换前面整条switch语句!
result=oper_func[oper](op1,op2);
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。
5.复杂的函数指针拆解
void (*signal (int signo,void (*func) (int) )) (int)
这一大堆看起来很难,其实仔细分析下不算很难搞。
首先要明白一件事:这里都是从最基本的语法展开的。
那么这里最基本的语法就是函数的声明:返回值 函数名(参数)。
先将这一大堆给看成void (f)(int)也就是将*signal (int signo,void (*func) (int) )看成f,那么相对而言就比较好理解了。
那么signal就是函数名,而他的返回值为指向函数f的指针即(指向一个返回值为void参数为int的函数的指针)。
然后signal的参数为signo和一个返回值为void参数为int的函数指针。
然后那个f的返回值为void 参数为int这就不必多说了。
好了,关于void (*signal (int signo,void (*func) (int) )) (int)这个字面上的意思大抵就是这样。
其实是unix信号通信机制里面的。具体如下:
void (*signal(int signo, void (*func)(int)))(int);
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
从这里结合上面的分析我们可以得出singal这个函数的返回值类型为void(*)(int)。
同时
#define SIG_ERR (void (*)(int))-1
#define SIG_DFL (void (*)(int))0
#define SIG_IGN (void (*)(int))1
这里以(void(*)())1表示将1强制性转换为返回值为void参数为int的函数指针。
不知道干嘛要这样,后来看到一个demo后明白了。
if (signal(SIGINT, sig_int) == SIG_ERR)这里用来捕捉各个信号。
由于signal的返回值类型为void(*)(int)所以为了区别各个信号所以只能将这些东西定义为这么复杂的声明了。
关于typedef参考:深度分析typedef
相关文章推荐
- C语言里函数指针的两种常见用途(回调函数与转移表)
- 被调函数中给指针数组赋值的两种写法
- C++中回调函数及函数指针的实例详解
- 函数调用参数改变的两种方法——指针与引用
- 成员函数指针与回调函数
- 将字符串中各单词首字母转化成大写,使用回调函数实现[函数指针]
- C语言中的回调函数(函数指针)
- 指向函数的指针(pointers to functions)和回调函数
- 函数指针和回调函数
- 指针函数 、函数指针 、 回调函数
- 【C++基础之八】函数指针和回调函数
- 新手,对函数,函数指针,回调函数, 函数指针作为函数的返回值和block的一些见解
- 回调函数,函数指针
- 10.6 分别用字符数组和字符指针作函数参数两种方法编程实现在字符串每个字符间插入一个空格的功能
- 重学C++Primer笔记9---回调函数与函数指针的应用
- 回调函数与函数指针
- 学习回调函数同时引申出用函数参数返回一些值或指针
- 以指针和引用两种参数实现删除单链表L中所有值为X的结点的函数
- 回调函数,函数指针与函数对象
- 数组的函数传递的两种方式(数组名和指针)