通过前面两节的讨论,相信读者已经发现C语言中函数指针的灵活与强大了。毫不夸张的说,C语言的指针语法,有时甚至让C语言看起来像具备了“新特性”似的。
将指针当作一种普通数据类型
不过C语言指针的灵活与强大,也导致很多初学者认为指针是一个很难的概念,因此在遇到指针时,常常会觉得“紧张”。例如下面这个例子:
int fun(int a)
{
a = 3;
}
int val = 1;
fun(val);
printf("val = %d\n", val);
即使是初学者,只要了解了函数形参和实参的关系,也知道上面这段C语言代码编译后会输出 val = 1。但是如果将C语言代码做适当修改:
int fun(int *a)
{
a = (int*)3;
}
int *val = (int*)1;
fun(val);
printf("val = %p\n", val);
这段C语言代码编译后,会输出什么呢?可能一些初学者会认为输出 val=0x3,但是将这段代码实际编译运行,得到的输出却是:
# gcc t.c
# ./a.out
val = 0x1
怎么回事呢?原因当然是简单的,这个问题仍然可以用函数形参和实参的关系解释,很基础。但是有些初学者还会迷惑,“这可是指针!”其实,这就是将C语言中的指针“特殊化”了。
在分析指针问题时,一个小技巧就是将指针当作C语言中的“普通数据类型”,例如可以将 int* 变量看作是“int指针型”变量。现在改写上面的C语言代码:
typedef int* IPTR;
int fun(IPTR a)
{
a = (IPTR)3;
}
IPTR val = (IPTR)1;
fun(val);
printf("val = %p\n", val);
这里使用 IPTR 表示 int* 类型,一切看起来就很容易理解了。
“函数指针类型”
既然把C语言中的指针看作是一种数据类型,那么函数指针也就容易理解了,也不过是一种像 char 、int 一样的数据类型而已。char 、int 作为C语言中的基础数据类型,是允许用其定义数组的,那函数指针这种“数据类型”也可以定义数组吗?答案是肯定的,请看:
typedef int (*funs[8])(int *data);
上面这行C语言代码就定义了一个函数指针数组 funs,funs 可以管理 8 个函数指针。有时候为了便于理解,上述代码常常会拆解成下面两行:
typedef int (*FPTR)(int *data);
FPTR funs[8];
funs 的各个元素可以执行原型为 int f(int *p);
的函数,例如:
funs[0] = fun;
funs[3] = fun;
使用 funs 实现函数调用也很简单:
funs[0](val);
funs[3](val);
函数指针数组的意义
相信读者已经了解函数指针数组的定义和使用了,那它有什么意义呢?实际嵌入式C语言项目开发中常会遇到这样的需求:服务端采集到数据后,需要将其分别发送给模块1、模块2、...、模块 n,但是各个模块使用的协议不一致,因此这里假设每一个模块都定义了一个函数用于传输数据:
void m1Send(void *data)
{
// 模块1 发送函数
}
void m2Send(void *data)
{
// 模块2 发送函数
}
...
void mnSend(void *data)
{
// 模块n 发送函数
}
这些发送函数根据各自的协议自定义逻辑,但是接口却是一致的,如果服务端需要分发数据,则需要:
m1Send(data);
m2Send(data);
...
mnSend(data);
如果有若干组数据需要分发,那么上面这样的C语言代码需要重复写若干次,太繁琐了,而且重复的代码不利于后期维护。这种情况下,使用函数指针数组就非常方便了:
void (*mxSend[N])(void *data);
void init()
{
mxSend[0] = m1Send;
...
mxSend[N-1] = mnSend;
}
只需调用 init() 函数将各个模块的数据分发函数注册到 mxSend 数组,之后使用 mxSend 就很方便了,请看下面的C语言代码:
int i;
for(i=0; i<N; i++)
mxSend[i](data);
上面这几行代码将函数指针数组像普通数组一样使用,但是这种风格的C语言代码要比逐个调用各模块的数据分发函数方便多了。
小结
经过本节的讨论,相信读者能够发现C语言中的指针也不过如此,在实际使用中,只需将其当作普通的数据类型就可以了。这样一来,函数指针数组也就不难理解了,合理的使用它,可以写出更加紧凑的C语言代码。本节最后还举了一个数据分发的实例用于说明C语言函数指针数组的方便,当然了,函数指针的用途远不止于此。