C语言陷阱与技巧27节,“函数指针结构体”为C语言找了个“对象”

上一节讨论了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语言项目开发中的便利性,当然了,这里讨论的只是很小的一个点,读者在之后的实际项目开发中,一定能够发现更多特性的。

阅读更多:   C语言
已有 2 条评论
  1. Hi Hi

    终于找到这一个版本了

添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif