研究这部分内容的动机主要还是编写 STM32 的 C 代码的时候, 期望有一个固有的类能够管理其方法, 这样能够比较清晰的管理这些同属于一类的代码结构。 其实这里就涉及到了 面向对象 的编程思想, 将数据抽象为 类 和 方法, 类能表征数据的属性, 而方法则是数据属性及状态改变的一种行为。
之前在接触 Linux 驱动的时候其实就浅浅地接触到了 C 面向对象的思想, 例如 MTD 层抽象接口提供给 Block, 我们直接从顶层通过代码索引的方式是没办法找到具体的 Block 调用方法的。 我们只能看到 MTD 抽象出来的接口, 这一定程度上提供了数据的私有化的能力。
网上的相关文章众说纷纭, 但目前看来就是 C 现代编程 这本书讲的最清晰最明白, LW_OOPC 则是 高焕堂 以及其团队 MISOO 设计的方便 C 面向对象的宏定义模块, 能方便相关的开发。 其实这些文章读过来, 主要还是需要开发者用好 struct
, Macro
, pointer
这三类资源。 struct
可谓是重中之重, 可以看到在 C++ 中的 struct 和 class 仅在数据接口的私有性上存在差异, 而在 Rust 中则直接删除了 class 的定义, 转向了 struct + trait 的编程模式。
这篇文章不定期更新 OOP C 的一些想法, 其实读 C 现代编程 还是有很多疑惑的, 但是没用到就没太深刻的印象, 有问题再好好研究记录!
0. 资料汇总
- C 现代编程 - 百度网盘
这本书最关键, 下面的感觉写的都不如这本书详细清晰。
- UML+OOPC嵌入式C语言开发精讲 - 百度网盘
- lw_oopc - Github
- Object Oriented C - ooc
- 真的可以,用C语言实现面向对象编程OOP - 李肖遥 WeChat
- C语言使用结构体面向对象编程举例讲解 - 极客子羽 博客园
1. 使用 struct 模块化编程
主要想法还是利用 struct 管理方法函数, 利用 static
对 .c
文件中的函数进行私有化处理防止命名冲突。
另外, struct 也能起到虚函数的功能, 如下定义了一个 page_t
的结构体, 这个结构体规定了其内实现的方法函数的接口。
typedef struct {
void (*painter)(void*);
void (*handler)(void*);
} page_t;
2. 跨文件的全局变量
另外在整个工程中会需要跨文件使用相关的变量, 例如我在 encoder.h
定义了一个 struct Encoder
作为编码器类, 而我希望通过一个 g_encoder
变量作为跨文件的全局变量被不同文件使用。 例如在 encoder.c
中初始化方法函数, 而在其他文件中调用这些方法, 此时通过一个全局的变量就非常方便了。 这里需要区分 声明 和 定义 这两个概念。
- 声明
- 向编译器说明一个变量或函数的信息,包括:名字、类型、初始值等,即声明变量、函数的属性细节, 包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。
- 定义
- 指明变量、函数存储在哪里,当定义发生时,系统为变量或函数分配内存单元。
我在这里是这样构想的, 对于特定功能的类, 例如 Encoder
编码器类, 则直接在 encoder.c
中定义一个这个类型的变量 g_encoder
, 在 encoder.h
中则通过 extern
声明 g_encoder
。 这样, 任何包含 encoder.h
的文件都能直接使用这个变量。 而一些影响设备属性的 primitive 类型的变量则通过 config.h
以及 config.c
进行声明和定义。 这样能够分离文件的职能, 不会导致 config.h
这个管理全局变量的文件不断 include 各种子设备而导致文件管理过于繁杂和臃肿。 但我需要在 config.h
用注释说明这些全局变量的位置以及功能, 方便后续进行查阅检索。
// encoder.c
Encoder g_encoder;
// encoder.c
typedef struct Encoder {
...
} Encoder;
extern Encoder g_encoder;