10

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

 3 years ago
source link: https://blog.popkx.com/c%E8%AF%AD%E8%A8%80%E9%99%B7%E9%98%B1%E4%B8%8E%E6%8A%80%E5%B7%A727%E8%8A%82-%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E7%BB%93%E6%9E%84%E4%BD%93%E4%B8%BAc%E8%AF%AD%E8%A8%80/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

发表于 2019-06-26 07:06:54   |   已被 访问: 681 次   |   分类于:   C语言   |   2 条评论

上一节讨论了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语言


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK