C 语言面向对象编程 - 封装
本帖最后由 广轻电气091 于 2019-11-29 17:01 编辑大部分使用 C 语言进行开发的工程师,在接触更高级的编程语言之前,都认为 C 语言是面向过程的。
确实,对于一些小规模的应用程序,C 语言一般都被用作面向过程编程。例如:单片机应用程序开发。
但是,如果是使用 C 语言开发一些规模较大的软件时,就必须用面向对象的思想去考虑和设计整个软件框架了。例如:嵌入式Linux操作系统。
嵌入式Linux操作系统虽然是使用 C 语言作为主要的编写语言,但里面的设计大部分都使用了面向对象的编程思想。
很多单片机工程师或者嵌入式Linux驱动初学者,觉得入门困难,很大一部分原因就是,他们还停留在单片机那种面向过程的思维模式上面。
编程语言只是一种工具,编程思想才是用好这个工具的关键。C 语言只是工具,而面向对象是一种编程思想,用来指导我们如何用好 C 语言。
接下来,我们将尝试使用 C 语言进行面向对象程序开发,务求使用 C 语言实现面向对象的一些基本特性。
首先,我们先来说说封装。
封装就是把一个抽象事物的属性和属性的操作函数打包在一起,外界的模块只能通过这个抽象事物对外提供的函数接口,对其属性进行访问。
在C++或其他高级语言中,封装通常被称作“类”。而 C 语言一般使用结构体对事物进行封装。
接下来,我们先看两段代码,这两段代码主要声明和定义了一个坐标类对象,以及其坐标属性,还提供坐标属性的操作函数。
头文件 coordinate.h
源文件 coordinate.c
代码比较简单,在头文件 coordinate.h里面,通过结构体封装了一个coordinate类,里面有两个坐标属性 x 和 y 。
coordinate_create 函数主要用于创建一个 P_COORDINATE_T 类型的对象,并为其分配内存空间,内存分配成功后,设置两个坐标属性的初始值,最后返回申请成功的对象指针。
coordinate_destroy 主要是释放对象之前申请的内存空间,然后把对象指针重置为NULL。
其他的操作函数,主要是对类对象的属性进行操作,比如获取 x 和 y 的属性值,重置坐标的属性值。
以下是测试函数,在主函数中调用,即可测试类coordinate对外提供的接口。
测试代码比较简单,主要是创建了两个 P_COORDINATE_T 类型的对象,然后打印其坐标初始值,再通过对外提供的函数修改其坐标值,然后再打印出来。
测试函数运行后,结果如下所示:
从上述代码可以看出,使用结构体可以很好地对数据进行封装,并且需要通过指定的操作函数对结构体内的数据进行访问。
每个操作函数的第一个参数是对象本身的指针,通过这个指针去访问具体对象里面的属性。
这是因为在 C 语言中不存在像 C++ 语言那样的 this 指针,所以我们只能显式地通过函数传参的方式,让函数内部可以访问对象实例的其他成员。
对于对象属性的各种操作函数,还可以使用函数指针的方式,放入结构体内进行封装。但为了便于理解,本文并没有采用这种方法。
源码下载地址:https://github.com/embediot/my_program_test
欢迎关注我的技术公众号
没有对象的人只能面壁编程了{:lol:} 留个记号 留个记号 想问一下 为什么结构体指针引用成员时,不用加(*),而一般的单个变量指针取值时要加(*)?
书上这么规定的吗? 楼主讲得真好,我就是总搞不清楚结构体 kevin_me 发表于 2019-11-29 09:32
想问一下 为什么结构体指针引用成员时,不用加(*),而一般的单个变量指针取值时要加(*)?
书上这么规 ...
很抱歉,我不是很能理解你的问题呢。 *是表示取值符,表示取某个地址存储的值。 chen849928055 发表于 2019-11-29 09:35
楼主讲得真好,我就是总搞不清楚结构体
结构体其实不难理解,画一段内存图就知道了 wyn20007 发表于 2019-11-29 09:17
留个记号
感谢关注! 广轻电气091 发表于 2019-11-29 09:49
很抱歉,我不是很能理解你的问题呢。 *是表示取值符,表示取某个地址存储的值。 ...
不好意思,问得太挫了。
p_coordinate->x取x的值,我有限的知识认为应该这么写: *(p_coordinate->x)
kevin_me 发表于 2019-11-29 09:54
不好意思,问得太挫了。
p_coordinate->x取x的值,我有限的知识认为应该这么写: *(p_coordinate->x ...
如果 x 是被定义为指针变量,那写成 *(p_coordinate->x) 是正确的。但代码里, x 是 short int 类型的变量,因此,直接引用就可以了 有机会也用这种方法试试,
试过某高人的类似高焕堂那种方法--把函数也写到结构体里面,感觉在构造环境时,相对楼主的方法,有点繁琐。楼主的方法几乎没有在C上强加多少绕弯的东西(比如,宏)。用C就是想要那种明明白白的感觉。 本帖最后由 chen849928055 于 2019-11-29 10:20 编辑
广轻电气091 发表于 2019-11-29 09:50
结构体其实不难理解,画一段内存图就知道了
p_coordiante->x += dx; 修改x坐标的属性,这里不是用修改的值加原来的值吗?
COORDINATE_T *p_coordiante = NULL;你这里是不是也可以这样直接定义一个结构体指针 标记一下,学习了 chen849928055 发表于 2019-11-29 10:14
p_coordiante->x += dx; 修改x坐标的属性,这里不是用修改的值加原来的值吗?
COORDINATE_T *p_coordian ...
是的,你的理解是正确的 kv2004 发表于 2019-11-29 10:10
有机会也用这种方法试试,
试过某高人的类似高焕堂那种方法--把函数也写到结构体里面,感觉在构造环境时 ...
下一篇文章,会把函数通过函数指针的方式,封装在结构体内,进一步模块化和提高封装性 ckhf 发表于 2019-11-29 10:17
标记一下,学习了
感谢关注! kevin_me 发表于 2019-11-29 09:54
不好意思,问得太挫了。
p_coordinate->x取x的值,我有限的知识认为应该这么写: *(p_coordinate->x ...
p_coordinate->x并不是指针,*(p_coordinate->x)这么用肯定不对,这里p_coordinate才是指针(也就是个地址),后面跟着连续的内存空间,存储着x,y两个值。
如果你一定要用*取值也是可以的,像下面这样就行了
X = *(short int *)pp_coordiante;
Y = *((short int *)pp_coordiante + 1); 感谢楼主分享,学习一下楼主的思想! kv2004 发表于 2019-11-29 10:10
有机会也用这种方法试试,
试过某高人的类似高焕堂那种方法--把函数也写到结构体里面,感觉在构造环境时 ...
结构体里包含函数指针,应该挺常见的吧 LZ的字体看着难受。 kevin_me 发表于 2019-11-29 09:54
不好意思,问得太挫了。
p_coordinate->x取x的值,我有限的知识认为应该这么写: *(p_coordinate->x ...
理解了结构体是连续的内存空间,会方便你更深刻的理解指针的含义。假如有一个结构体内部很多参数需要存储到flash,很多人会单独一个一个参数的存储,而用结构体指针可以直接引用指针,然后按照结构体的长度连续读取N个字节存到flash上可以了,读取一样的(反过来就行)。 本帖最后由 FireBrain 于 2019-11-29 10:54 编辑
从来就不推荐用c模拟面向对象的写法,代码可读性简直不忍直视。我没看过linux内核代码,即使它用到了某些OO思想,比如模拟实现了继承、多态等的特性,也绝不会是你的style来写的,那样还不如直接c++ hyghyg1234 发表于 2019-11-29 10:37
p_coordinate->x并不是指针,*(p_coordinate->x)这么用肯定不对,这里p_coordinate才是指针(也就是个地 ...
(short int *)这样是先把p_coordiante转换为short int型再对short intp_coordiante取值吗? chen849928055 发表于 2019-11-29 10:45
(short int *)这样是先把p_coordiante转换为short int型再对short intp_coordiante取值吗? ...
是的,指针实际上就是个地址,你认为它是什么就强制转化成什么就可以了,再利用结构体空间上连续的特性。有个经典的float转hex,也是利用的指针的特性,float是4个字节,强制转换成char或者int都可以。 hyghyg1234 发表于 2019-11-29 10:37
p_coordinate->x并不是指针,*(p_coordinate->x)这么用肯定不对,这里p_coordinate才是指针(也就是个地 ...
是的,使用指针,不妨记住这几点:1、指针的本质是变量。2、*表示取值符,取地址里面的值。3、&表示取址符,取某个变量的地址 surken 发表于 2019-11-29 10:37
感谢楼主分享,学习一下楼主的思想!
感谢关注 luobote55 发表于 2019-11-29 10:39
LZ的字体看着难受。
感谢建议,下次排版优化一下 FireBrain 发表于 2019-11-29 10:44
从来就不推荐用c模拟面向对象的写法,代码可读性简直不忍直视
所谓可读性差,除了代码不规范导致可读性差。还有一个原因是读者的自身的水平能力不足 p_coordinate是结构体指针
p_coordinate->x
等价于
(*p_coordinate).x
FireBrain 发表于 2019-11-29 10:44
从来就不推荐用c模拟面向对象的写法,代码可读性简直不忍直视。我没看过linux内核代码,即使它用到了某些OO ...
为什么 Linux kernel 不用 C++ 进行编写?本文所表达的意思是,C 语言使用面向对象的思想,可以让代码的模块化程度更高,耦合度更低,方便以后维护和迭代 广轻电气091 发表于 2019-11-29 10:54
所谓可读性差,除了代码不规范导致可读性差。还有一个原因是读者的自身的水平能力不足 ...
我只能说你的品味很独特 FireBrain 发表于 2019-11-29 10:57
我只能说你的品味很独特
感谢指导,我会继续努力 广轻电气091 发表于 2019-11-29 10:56
为什么 Linux kernel 不用 C++ 进行编写?本文所表达的意思是,C 语言使用面向对象的思想,可以让代码的 ...
不要曲解,我是说c语言OO代码的风格,linux内核不会有这么明显的创建对象,销毁对象的代码 广轻电气091 发表于 2019-11-29 10:54
所谓可读性差,除了代码不规范导致可读性差。还有一个原因是读者的自身的水平能力不足 ...
说的很对,很多时候感觉难懂是应该自身修为还不够,有机会多去玩玩java C#这种高级语言学习下他们的思维方式很重要。 感谢楼主分享,学习一下楼主的思想! 本帖最后由 WM_CH 于 2019-11-29 11:39 编辑
别人不调用你的接口函数,直接修改变量值也可以。应该加上访问控制把结构体内部的实现隐藏起来
结构体实现放在.c中,.h直接写结构体声明 WM_CH 发表于 2019-11-29 11:38
别人不调用你的接口函数,直接修改变量值也可以。应该加上访问控制把结构体内部的实现隐藏起来
结构体实现 ...
感谢指导。从语法层面来说,是可以无限制访问的,毕竟 C 结构体不是真的面向对象特性,没有private, public, protect 感谢楼上几位指导,懂了谢谢,还是水平太差。 把函数也封装进去 这个层面暴露接构体,用户直接在接口里面改变量.... 只能说是层面上的封装. ooc不怎么适合c.
虽然有一定的方面作一定层面细节上的屏蔽,
比如这样:
//! 消息邮箱结构体
typedef struct {
uint16_t dumy0;
uint16_t dumy1;
void *pdumy2;
} MsgBox_t;
内部真实是这样:
// 消息队列结构体
typedef struct {
uint16_t count;
uint16_t capacity;
MsgQ_t qHead;
} msgBoxInner_t;
但最终都改变不了暴露的指针对实体操作的问题, 你永远无法想像各类程序员的脑洞有多大, 什么操作都有
留个记号 slzm40 发表于 2019-11-29 11:59
这个层面暴露接构体,用户直接在接口里面改变量.... 只能说是层面上的封装. ooc不怎么适合c.
虽然有一定的 ...
是的,因为C语言是除了汇编之外最接近硬件底层的编程语言,理论上,C可以做任何事。这种做法,是思维上的面向对象,不是真正从语法层面上的面向对象 ztrx 发表于 2019-11-29 11:58
把函数也封装进去
是的,下一篇文章会把函数也进行封装 刚才到github你的主页下载了 smartlight_system-master 源码, 编译了一下竟然可以运行{:lol:}
编译运行如图
封装可以只暴露接口,不暴露数据。
比如头文件:
typedef struct interface1_struct {
void (*func1) (int a, int b);
bool (*func1) (int a);
void* priv;
} interface1_t;
static inline void interface1_func1(interface1_t *me, int a, int b) {
me->func1(a,b);
}
static inline bool interface1_func2(interface1_t *me, int a) {
return me->func2(a);
}
interface1_t* create_interface1_type1();
interface1_t* create_interface1_type2();
所有的具体实现,都可以在C文件里面实现。而且可以有若干种不同的实现。 sunliezhi 发表于 2019-11-29 14:47
编译运行如图
哈哈,是的,下载下来,编译就可以运行 mark一下 flamma 发表于 2019-11-29 14:57
封装可以只暴露接口,不暴露数据。
比如头文件:
typedef struct interface1_struct {
是的,这是一种封装程度更高的思路,感谢指导 广轻电气091 发表于 2019-11-29 15:00
哈哈,是的,下载下来,编译就可以运行
硬件方面的连接可以透露么? sunliezhi 发表于 2019-11-29 15:05
硬件方面的连接可以透露么?
这是一个纯应用软件,没有添加硬件进去呢 广轻电气091 发表于 2019-11-29 15:38
这是一个纯应用软件,没有添加硬件进去呢
噢, 好的,谢谢提供这么好的代码! sunliezhi 发表于 2019-11-29 16:18
噢, 好的,谢谢提供这么好的代码!
不客气,欢迎关注我的技术公众号
广轻电气091 发表于 2019-11-29 10:32
下一篇文章,会把函数通过函数指针的方式,封装在结构体内,进一步模块化和提高封装性 ...
封装进去以后,还得多一步----给它赋值,感觉麻烦。每个实例都要留一套相同的指向相同函数的指针,又浪费空间又浪费时间,浪费感情。 kv2004 发表于 2019-11-29 17:02
封装进去以后,还得多一步----给它赋值,感觉麻烦。每个实例都要留一套相同的指向相同函数的指针,又浪费 ...
感谢指导,对于模块化,降低耦合度,请问有何高见?愿闻其详{:handshake:} 广轻电气091 发表于 2019-11-29 17:04
感谢指导,对于模块化,降低耦合度,请问有何高见?愿闻其详
太抬举了,我是一直在在对象和“光棍”之间徘徊(不论编程上还是生活中,呵呵)。
有时觉得对象好,有时觉得没有对象更清楚。看环境了,在C环境下面,看楼主1楼的帖子,觉得---心中有对象.实际没对象---这种方式,也是挺好的。
就是对C的面向过程的一根筋编程方式有点意见:我觉得用“ptThread”的方式,变换一下就好了。
也期望楼主有更好的方法。 其实然而没有太大意义。
特别是嵌入式领域,c的优点,c++不可替代。同样,c++的优点也很明显。
但用c模拟c++是比较奇怪的存在,我觉得研究这个有点浪费时间。
广轻电气091 发表于 2019-11-29 10:03
如果 x 是被定义为指针变量,那写成 *(p_coordinate->x) 是正确的。但代码里, x 是 short int 类型的变 ...
这是用的什么编辑器,字体配色很好看。 相由心生 发表于 2019-11-29 17:39
其实然而没有太大意义。
特别是嵌入式领域,c的优点,c++不可替代。同样,c++的优点也很明显。
但用c模拟c+ ...
文中所表达的意思是,用面向对象的思想去使用 C 语言,可以降低代码的耦合度,让代码框架更模块化,便于后期的维护和迭代呢。
每种语言都有其优劣,本文不是片面地表达谁劣谁优,而是在我们开发的过程中,可以借鉴其他优秀的思想。
Linux内核和RTT等操作系统,都是 C 语言编写,但思想都是面向对象的。 kinsno 发表于 2019-11-29 17:41
这是用的什么编辑器,字体配色很好看。
vscode编辑器,不是编译器呢 赞一个 ,感觉现在写代码确实需要这样封装,条理清晰
要不然就是 全局变量很多,可读性不好, nds_shenzhen 发表于 2019-11-29 17:53
赞一个 ,感觉现在写代码确实需要这样封装,条理清晰
要不然就是 全局变量很多,可读性不好, ...
尽量少用或不用全局变量,特别是跨 .c 调用的全局变量 广轻电气091 发表于 2019-11-29 17:46
文中所表达的意思是,用面向对象的思想去使用 C 语言,可以降低代码的耦合度,让代码框架更模块化,便于 ...
大多嵌入式代码已经按功能分类了。最后调功能引出的接口中就可以了。
如果仿c++,再引入函数指针,那么这个类就必须全部参与编译,增加程序大小,对于嵌入式一般都是资源紧张的,不太适合。
对于比较高级的片子,esp32或linux,空间比较多,直接c++倒是更好的选择。
我在十年前曾经在几个项目中大量应用OOPC,封装的极其漂亮。实际在出货时还要人工根据有没有引用到这个函数去注释掉以减下代码量,感觉好麻烦。
后面就直接不再使用这种方法了。
由于时代情况,比如向主流看,向钱看等。所以我不是很建议楼主去做这方面的研究,反倒搞些little-shell, easyflash, cjson,littlefs倒是很实用。
不过现在嵌入式工程师整体思想不如互联网工程师。从这个角度来讲,强制通过形式的方式教育一下(比如10年前对OOPC对我的教育),这是很好的,这方面的意义很大。
相由心生 发表于 2019-11-29 19:37
大多嵌入式代码已经按功能分类了。最后调功能引出的接口中就可以了。
如果仿c++,再引入函数指针,那么这 ...
这位哥们戳到关键处了。。。你干的事我都干过,最后在单片机上为了减少编译代码量,而在封装中用大量宏。导致时间久翻出来用时会有点懵逼裁剪的事。 linux上我直接上golang或python,解决我在用c时的所有痛点。 在单片机上我现在只是相应的简单封装,够用就行。如果要真oo思想,python,c++一系列面向对象随便搞,linux不差一点空间。 相由心生 发表于 2019-11-29 19:37
大多嵌入式代码已经按功能分类了。最后调功能引出的接口中就可以了。
如果仿c++,再引入函数指针,那么这 ...
是的,感谢建议!你说得很有道理,OOPC并不是所有场合都适用。使用 OOPC,对于锻炼面向对象思维和学习 C 指针,有积极意义,所以才花了一两周的时间去仔细琢磨一下。 slzm40 发表于 2019-11-29 22:11
这位哥们戳到关键处了。。。你干的事我都干过,最后在单片机上为了减少编译代码量,而在封装中用大量宏。 ...
是的,你说的是应用层的高级语言编程,为了加快开发效率,确实是需要使用面向对象语言进行开发。但对于系统底层的结构框架,OOPC也占有一席之地 {:lol:}高手过招点到为止已彼此心领神会,看几位的讨论非常精彩。我也不建议单片机或小型嵌入式应用场合过度封装。一段代码如果有必要在多个项目应用,把接口适当封装写漂亮点无可厚非,但太多类似应用后反而觉得浪费时间写封装的时间也浪费接手开发人员的时间,同时对于硬件资源也会有所浪费。{:lol:} NanceMichael 发表于 2019-11-30 16:27
高手过招点到为止已彼此心领神会,看几位的讨论非常精彩。我也不建议单片机或小型嵌入式应用场合过 ...
又要封装得好,又要节约资源
只有上C++模板实现的编译时多态技术
比如st收购的TouchGFX就是一个例子
缺点就是会用的人太少 NanceMichael 发表于 2019-11-30 16:27
高手过招点到为止已彼此心领神会,看几位的讨论非常精彩。我也不建议单片机或小型嵌入式应用场合过 ...
是的,具体问题具体分析 canspider 发表于 2019-11-30 17:01
又要封装得好,又要节约资源
只有上C++模板实现的编译时多态技术
比如st收购的TouchGFX就是一个例子
高级语言貌似还有运行时多态{:lol:} 运行时多态配合虚函数才叫牛逼 广轻电气091 发表于 2019-11-29 10:32
下一篇文章,会把函数通过函数指针的方式,封装在结构体内,进一步模块化和提高封装性 ...
那就是类了吧 广轻电气091 发表于 2019-11-30 18:26
高级语言貌似还有运行时多态
多态就是 同一个函数名的函数 入口参数 不同 磊磊映画 发表于 2019-11-30 19:36
多态就是 同一个函数名的函数 入口参数 不同
你说的应该是重载吧 C语言的特点是小 精,干核心函数用的,有些技巧和方法自己学习,理解语言都没啥问题, 但是真不适合实际应用. 用C语言去实现面向对象的编程是一件吃力不讨好的事!但了解一下还是可以增强自己的编程能力的。 期待楼主的下一篇文章 c++的 “++” 部分确实抽象难以理解,最好用dos下的 turbo c++学习,写一些简单的例子程序,编译时选择输出汇编代码,看看存储结构和调用,这样的话我想应该更加一目了然。 rengo 发表于 2019-11-30 22:29
C语言的特点是小 精,干核心函数用的,有些技巧和方法自己学习,理解语言都没啥问题, 但是真不适合实际应用. ...
不是呢,Linux kernel里面全是复杂的面向对象思想和设计模式 cddx 发表于 2019-12-1 01:09
用C语言去实现面向对象的编程是一件吃力不讨好的事!但了解一下还是可以增强自己的编程能力的。 ...
用这种思想去编写程序,程序的模块化会更好,耦合度会更低 admvip 发表于 2019-12-1 13:48
期待楼主的下一篇文章
感谢关注! WalkingCat 发表于 2019-12-1 18:41
c++的 “++” 部分确实抽象难以理解,最好用dos下的 turbo c++学习,写一些简单的例子程序,编译时选择输出 ...
是的,多练多写 本文PDF下载: 相由心生 发表于 2019-11-29 19:37
大多嵌入式代码已经按功能分类了。最后调功能引出的接口中就可以了。
如果仿c++,再引入函数指针,那么这 ...
多谢大神回复。倒是解决了我近期的疑惑。之前的代码,大都是按照模块封装,觉得也还好。后面先“规范”团队代码。接触了很多进一步封装的内容。
但是看到诸如ZLG的Ametal的时候,一边羡慕封装的“完美”,一边感叹是否需要这样。一直在打转转~看来是时候做减法了。再次感谢!!! 标记一下 留个记号!!! 加油,为啥不直接上c++ lncwangfeilnc 发表于 2019-12-3 20:11
标记一下
感谢关注 psbhero 发表于 2019-12-3 21:49
留个记号!!!
感谢关注 star_tale 发表于 2019-12-3 22:02
加油,为啥不直接上c++
因为很多操作系统底层,都大量使用了 C 语言 + 面向对象思想 先留个记号,回头看看 学习了。 jaky80000 发表于 2019-12-10 21:52
先留个记号,回头看看
感谢关注! klesky 发表于 2019-12-11 09:35
学习了。
感谢关注! 写得太经典了,谢谢楼主的分享,已关注! c用了很多年了 实在不想改了要改也改行 呵呵
定楼主的分享 sinxcosytana 发表于 2019-12-25 08:41
写得太经典了,谢谢楼主的分享,已关注!
感谢关注! TonyCai 发表于 2019-12-25 08:48
c用了很多年了 实在不想改了要改也改行 呵呵
定楼主的分享
感谢支持! 其实不是,最简单的就是c++只是把c语言的数据跟方法放在一个叫class的东西去了,在c语言里分成2个部分:一个struct以及一系列围绕这个struct进行操作的函数。
页:
[1]
2