上一节讨论了C语言中的指针可以看作是一种普通的数据类型,这么一来,函数指针数组就不难理解了,无非就是存放函数指针元素的数组而已。
函数指针结构体
稍微思考一下,应该能够想到C语言中的普通数据类型不仅可以用于定义数组,还可以用来定义结构体,例如:
struct cn{
char c;
int i;
double d;
};
那么可以看作“普通数据类型”的函数指针也可以定义结构体吗?自然是可以的,请看下面的C语言代码示例:
struct cfun{
void (*vfun)();
int (*ifun)(int a);
float (*ffun)(float x);
};
显然,使用“函数指针类型”定义结构体和使用普通数据类型没什么两样。
C语言的“对象”
事实上,定义好函数指针结构体之后,用起来也和普通数据类型定义的结构体一样:
struct cfun s;
// init s
s.vfun();
s.ifun(a);
struct cfun *ps = &s;
ps->ffun(x);
从上面这几行C语言代码可以看出,使用函数指针结构体调用函数,看起来很像其他高级语言中的“面向对象”风格。的确如此,C语言中的结构体和指针语法,允许C语言程序员写出“面向对象”风格的代码。
很多程序员觉得C语言没有对象语法,认为C语言只能按照面向过程风格的代码开发程序。其实,“面向对象”更多时候是一种编程思想,而不仅限于一种编程语言的语法。
不过,因为C语言没有原生的对象语法,在使用函数指针结构体之前,必须对结构体初始化,否则最终C语言程序就会有崩溃的风险。因为和其他变量一样,没有初始化的函数指针变量的指向是不确定的,没有初始化过的函数指针本质上是一种“野指针”,操作野指针的危害相信读者已经了解。
初始化C语言的“对象”
使用函数指针定义的结构体成员本身不具备功能,它只是一个指针,只有将其指向某个具体函数,它才会具备实际功能。下面是一个例子:
void myprint()
{
printf("hello embedTime\n");
}
int add_inum(int a)
{
return a+2;
}
float add_fnum(float x)
{
return x+2.0;
}
void init_cfun(struct cfun *s)
{
s->vfun = myprint;
s->ifun = add_inum;
s->ffun = add_fnum;
}
struct cfun s;
init_cfun(&s);
// s now is available
s->vfun(); // hello embedTime
请看上述C语言代码,init_cfun() 函数将接收到的函数指针结构体初始化,即将该结构体的各个成员指向事先实现的函数。只有在 init_cfun(&s); 之后,结构体 s 才可用。
当然,我们也可以基于C语言结构体的赋值语法,实现一套静态初始化的代码,请看:
static struct cfun CFUN_INIT = {myprint, add_inum, add_fnum};
之后再定义 cfun 结构体时,直接将 CFUN_INIT 赋值给该结构体,就可以实现初始化了,请看相关C语言代码:
struct cfun s = CFUN_INIT;
// s now is available
s->vfun(); // hello embedTime
很多C语言程序员在处理结构体赋值时,常使用 memcpy() 拷贝内存,其实对于相同的结构体,直接赋值也是一样的。
C语言“对象”的意义
基于C语言指针和结构体语法实现的“面向对象”风格代码,有利于程序员管理。在实际C语言项目开发中,某个模块的功能常常无法只依靠一个函数实现,若干个函数实现一个模块的功能并不少见。但是这些函数零散的分布在整个C语言项目代码中,管理起来有时的确比较头疼。
读完本节,相信读者应该发现,结合C语言指针和结构体语法,能够方便的将一个模块的所有函数接口封装成一个“对象”,一个对象管理一个模块,整个代码就显得比较简洁易懂了。事实上,在 Linux 内核代码中,基于C语言指针和结构体语法实现的“面向对象”风格代码相当常见、
另外值得说明的是,C语言的“对象”也便于函数命名。现在想象一下,加入有若干个模块都需提供读写的功能,那在实现C语言代码时,可能在项目中,会出现下面这种命名:
void moduleA_write(void *data)
{
...
}
void moduleB_write(void *data)
{
...
}
void moduleC_write(void *data)
{
...
}
写在C语言代码中,会重复写很多次“module”。如果使用“对象”封装,方法名的差异完全可以使用“对象名”区分,例如:
ma.write();
mb.write();
显然这种写法简洁许多。
小结
本节主要讨论了C语言中的指针与结构体语法的结合使用,可以看出,基于此C语言甚至可以模拟其他高级编程语言中的“面向对象”风格。文章最后尝试讨论了“面向对象”这种编程思想在C语言项目开发中的便利性,当然了,这里讨论的只是很小的一个点,读者在之后的实际项目开发中,一定能够发现更多特性的。
终于找到这一个版本了