typedef int (*PF) (const char *, const char *); 这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:
PF Register(PF pf); Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:
int (*Register (int (*pf)(const char *, const char *)))
(const char *, const char *); 很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,而是一种必需。持怀疑态度的人可能会问:"OK,有人还会写这样的代码吗?",快速浏览一下揭示 signal()函数的头文件 <csinal>,一个有同样接口的函数。
不用typedef声明的:
void (*signal (int signo, void (*func)(int)))(int);
typedef void (*_sighandler_t)(int);
_sighandler_t signal(int signo, _sighandler_t func);
2.声明函数指针并回调
程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。
声明函数指针
回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:
void f();// 函数原型
上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:
void (*) ();
让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:
// 获得函数指针的大小
unsigned psize = sizeof (void (*) ());
// 为函数指针声明类型定义
typedef void (*pfv) ();
pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。
指针变量应该有一个变量名:
void (*p) (); //p是指向某函数的指针
p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:
void func()
{
/* do something */
}
p = func;
p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。
传递回调函数的地址给调用者
现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:
void caller(void(*ptr)())
{
ptr(); /* 调用ptr指向的函数 */
}
void func();
int main()
{
p = func;
caller(p); /* 传递函数地址到调用者 */
}
如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。
调用规范
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:
// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int);
// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int));
// 在p中企图存储被调用函数地址的非法操作
__cdecl int(*p)(int) = callee; // 出错
指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列
4。函数指针和回调函数
函数指针和回调函数
你不会每天都使用函数指针,但是,它们确有用武之地,两个最常见的用途是把函数指针作为参数传递给另一个函数以及用于转换表(jump table)。
【警告】简单声明一个函数指针并不意味着它马上就可以使用。和其它指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。下面的代码段说明了一种初始化函数指针的方法。
int f(int);
int (*pf)(int)=&f;
第 2 个声明创建了函数指针 pf ,并把它初始化为指向函数 f 。函数指针的初始化也可以通过一条赋值语句来完成。 在函数指针的初始化之前具有 f 的原型是很重要的,否则编译器就无法检查 f 的类型是否与 pf 所指向的类型一致。
初始化表达式中的 & 操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。 & 操作符只是显式地说明了编译器隐式执行的任务。
在函数指针被声明并且初始化之后,我们就可以使用三种方式调用函数:
int ans;
ans=f(25);
ans=(*pf)(25);
ans=pf(25);
第 1 条语句简单地使用名字调用函数 f ,但它的执行过程可能和你想象的不太一样。 函数名 f 首先被转换为一个函数指针,该指针指定函数在内存中的位置。然后, 函数调用操作符调用该函数,执行开始于这个地址的代码。
第 2 条语句对 pf 执行间接访问操作,它把函数指针转换为一个函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第1条是完全一样的。
第 3 条语句和前两条的效果是一样的。间接访问并非必需,因为编译器需要的是一个函数指针。
3.
以前我们讨论过的,把它贴过来:
回调函数有点像你随身带的BP机:告诉别人号码,在它有事情时Call你
callback 函数是先注册,然后在需要的时候使用。
1、与回调函数同类的函数(返回值、参数均同)的类型定义。定义一指向该类函数的指针;
typedef int (*PFunc)(int a);
2、回调函数A的定义及实现;
int A(int a){...};
3、回调函数A把其地址当作参数传给另一个函数B;
void B(PFunc A)
{...}
4、而在函数B内部除了执行一些别的语句外,还包括了对A的执行,由此来实现对函数A的调用。
void B(PFunc A)
{
PFunc pf;
pf=A;
.........
.........
(*pf)(5); //执行
.........
}
4.
举个常见的例子(不好意思,借用上面的一段):
1、与回调函数同类的函数(返回值、参数均同)的类型定义。定义一指向该类函数的指针;
typedef int (*PFunc)(int a);
2.然后你就可以拿这个PFunc作为一种类型来声明变量,这些变量都是返回值和参数的类型都相同的函数。
PFunc aStateProc, bStateProc, cStateProc等等。
3、然后分别实现;
int aStateProc(int a){...};
int bStateProc(int b){...};
int cStateProc(int c){...};
4. 定义一个数组PFunc paStateProc[N]={aStateProc, bStateProc, cStateProc...}
5.这样,当你收到一条消息,触发一个事件,则可以调用当前状态下收到事件的处理函数来处理,即paStateProc[CurrrentState](x)。
5.
本文首先复习一下基于 C 语言函数指针和回调函数的概念,进而学习 GObject 闭包的用法。这些知识都与面向对象程序设计基本上没有什么关系。
函数指针
所谓函数指针,就是可以指向函数的指针,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
int
foo ( void )
{
return 1 ;
}
int
main ( void )
{
int (*func) ( void );
func = foo;
func ();
return 0 ;
}
|
代码中的 func 即为一个函数指针,它可以指向一个无参数且返回值为整型数的函数,还可以调用它。
只要不会将:
1
|
int (*func) ( void );
|
与
1
|
int *func ( void );
|
弄混(后者是返回值类型为整型数指针的函数),那么对于函数指针的理解就没什么大问题了。
由于 "int (*func) (void)" 这种声明函数指针的形式看上去有点怪异,所以很多人喜欢用 typedef 将函数值指针定义成类型以便使用,例如:
1
2
3
|
typedef int (*Func) ( void );
Func func = foo;
|
如果对于上述内容理解起来并不费劲,那么下面就可以谈谈回调函数了。
回调函数
在编写一些程序库的时候,设计者有时会认为有些功能,不应该由他自作主张,而应当由使用者来决定。这方面,比较有名的例子是 C 标准库中的 qsort 函数:
1
2
3
4
|
void qsort ( void *base,
size_t nmemb,
size_t size,
int (*compar) ( const void *, const void *));
|
它的第 4 个参数即为函数指针,它可以指向形式为:
1
|
int foo ( const void *param1, const void *param2);
|
的所有函数。
Q:这个函数指针的作用是什么?
A:它是用来指向用户提供的回调函数的。
Q:用户提供的回调函数是什么意思?
A:因为 qsort 的设计目标是对任意类型的同类数据集合进行快速排序,比如一组整型数的排序,或者一组字符串的排序。既然是排序,那么 qsort 必须知道如何判定具体类型的两个数据的大小,而 qsort 的设计者认为这一功能应当交给用户来实现,因为他并不清楚用户究竟要使用 qsort 对哪种类型的数据集合进行排序,只有用户自己清楚。
Q:用户提供的回调函数究竟是什么意思?
A:就是对于用户所要排序的数据集合而言,用户提供一个可以比较该数据集合中任意两个元素大小的函数,这个函数便是 qsort 的回调函数。
用户、被调用的函数 qsort 以及回调函数,它们之间的关系如下图所示:
下面是使用 qsort 函数进行字符串数组递增排序的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int
str_compare ( const void *s1, const void *s2)
{
char *str1 = *( char **)s1;
char *str2 = *( char **)s2;
size_t l1 = strlen (str1);
size_t l2 = strlen (str2);
if (l1 > l2)
return 1;
else if (l1 == l2)
return 0;
else
return -1;
}
int
main ( void )
{
char *str_array[5] = { "a" , "abcd" , "abc" , "ab" , "abcde" };
qsort (&str_array, 5, sizeof ( char *), str_compare);
for ( int i = 0; i< 5; i++)
printf ( "%s " , str_array[i]);
printf ( "\n" );
return 0;
}
|
闭包(Closure)的概念
从上一节,我们通过函数指针向 qsort 函数传入了一个函数 str_compare,这个函数被称为回调函数,但是它还有一个比较深奥的名字——“闭包”。
所谓闭包,简而言之,就是一个函数加上它所访问的所有非局部变量,而所谓“非局部变量”,表示这些变量对于那个函数而言既非局部变量,也非全局变量。
我们向 qsort 传入函数 str_compare,它所接受排序数据集合中的 2 个元素,而且 2 个元素对于 str_compare 而言,既非是全局变量,也非其局部变量,因此 str_compare 与这 2 个参数形成了一个闭包。
在许多动态语言中,闭包通常也被昵称为“函数是第一类对象”,即函数与那些语言中基本类型具有相同的权利,例如函数可以存储在变量中,可以作为实参传递给其他函数,还可以作为其他函数的返回值。
恶魔来临
在 C 语言中,利用函数指针并配合参数的复制与传递,可模拟闭包这种结构,但是在可读性上没有那些内建支持闭包的语言优雅。
GObject 提供了 GClosure 对象与方法,实现了功能比较全面的 C 闭包模拟,我们可以在程序中直接使用它。下面,通过一个很小的示例,演示 GClosure 的使用。
先来看一个非 GClosure 的 C 闭包示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
#include <stdio.h>
#include <math.h>
#include <string.h>
typedef int (*Func) ( void *, void *);
static void
compare ( void *a, void *b, Func callback)
{
int r = callback (a, b);
if (r == -1)
printf ( "a < b\n" );
else if (r == 0)
printf ( "a = b\n" );
else
printf ( "a > b\n" );
}
static int
float_compare ( void *a, void *b)
{
float *f1 = ( float *)a;
float *f2 = ( float *)b;
if (*f1 > *f2)
return 1;
else if ( fabs (*f1 - *f2) <= 10E-6)
return 0;
else
return -1;
}
static int
str_compare ( void *a, void *b)
{
size_t len1 = strlen (( char *)a);
size_t len2 = strlen (( char *)b);
if (len1 > len2)
return 1;
else if (len1 == len2)
return 0;
else
return -1;
}
int
main ( void )
{
float a = 123.567;
float b = 222.222;
Func func = float_compare;
compare (&a, &b, func);
char *s1 = "hello world!" ;
char *s2 = "hello!" ;
func = str_compare;
compare (s1, s2, func);
return 0;
}
|
上述代码主要实现了一个 compare 函数,它可以比较两个任意类型数据的大小,前提是你要向它提供特定的回调函数(闭包),例如代码中的 float_compare 与 str_compare 函数,它们分别实现了浮点数比较与字符串比较。
6.
基本概念:如果参数是一个函数指针,调用者可以传递一个函数的地址给实现者,即调用者提供一个函数但自己不去调用它,而是让实现者去调用它,这称为回调函数(Callback
以上的概念叙述很难明白,回调函数到底是怎么一回事儿,下面将通过一个实例描述:
此示例由三个文件组成:para_callback.h、para_callback.c、callback.c,三者的代码如下:
#ifndef
#def
/*声明一个函数指针类型callback_t,用callback_t声明的变量指向这样的函数:无返回值,有一个参数,参数类型为空指针类型*/
typedef
void
#endif
#include
void
{
}
#include
#include
void
{
}
void
{
}
int
{
speak(speakfloat,
return
}
在本例中回调函数的参数按什么类型解释由调用者规定,对于实现者来说就是一个void*指针,实现者只负责将这个指针转交给回调函数,而不关心它到底指向什么数据类型。调用者知道自己传的参数是int*和float*类型的,那么在自己的回调函数中就应该知道参数要转换成int*型和float*型来解释。
7.
#include <stdio.h>
//几个用于测试的函数
int
max(
int
a,
int
b)
{
return
a>b?a:b;
}
int
min(
int
a,
int
b)
{
return
a<b?a:b;
}
//结构体
struct
func
{
int (*max)( int , int ); //函数指针 //注册回调函数
int (*min)( int , int );//注册回调函数
};
typedef
struct
func func;
//添加别名
void init(func *data)
{
data->max = max; //初始化函数指针
data->min = min;
}
int
main()
{
int
a, b;
func test;
init(&test); //初始化,你可以说它是构造函数
a = test.max(100, 215);
b = test.min(64, 42);
printf
(
"result:\nmax: %d\nmin: %d\n"
, a, b);
return
0;
}
|
http://blog.chinaunix.net/uid-1827018-id-3243753.html
http://www.doc88.com/p-993319419615.html