Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
面向对象
多态
内核中的封装继承与多态
发布于 2021-03-26 21:38:14 浏览:1112
订阅该版
[tocm] # 内核中的封装继承与多态 RT-Thread 虽然是使用面向过程的 C 语言来编写,但是处处都体现了面向对象的编程思想,先前对其感悟不够深,随着编写的程序越来愈多,对其理解也逐步加深。 ## 封装 封装是一种信息隐蔽技术,它体现于类的说明,是对象的重要特性。封装使数据和加工该数据的方法(函数)封装为一个整体,以实现独立性很强的模块,使得用户只能见到对象的外特性(对象能接受哪些消息,具有那些处理能力),而对象的内特性(保存内部状态的私有数据和实现加工能力的算法)对用户是隐蔽的。封装的目的在于把对象的设计者和对象者的使用分开,使用者不必知晓行为实现的细节,只须用设计者提供的消息来访问该对象。 在C语言中,大多数函数的命名方式是动词+名词的形式,例如要获取一个 semaphore,会命名成`take_semaphore`,重点在take这个动作上。在RT-Thread系统的面向对象编程中刚好相反,命名为 `rt_sem_take`,即名词+动词的形式,重点在名词上,体现了一个对象的方法。另外对于某些方法,仅局限在对象内部使用,它们将采用static修辞把作用范围局限在一个文件的内部。通过这样的方式,把一些不想让用户知道的信息屏蔽在封装里,用户只看到了外层的接口,从而形成了面向对象中的最基本的对象封装实现。 下面给出一段示例代码,展示C语言如何实现封装: ```c // shape.h typedef struct{ int x; int y; }Shape; Shape * Shape_create(int x, int y); void Shape_init(Shape * self, int x, int y); void Shape_move(Shape * self, int dx, int dy); // shape.c Shape * Shape_create(int x, int y) { Shape * s = malloc(sizeof(Shape)); s->x = x; s->y = y; return s; } void Shape_init(Shape * self, int x, int y) { self->x = x; self->y = y; } void Shape_move(Shape * self, int dx, int dy) { self->x += dx; self->y += dy; } // main.c #include "shape.h" int main(int argc, char *argv[]) { Shape * s = Shape_create(0, 0); Shape_move(s, 10, 10); return 0; } ``` 这里定义了一个叫做 Shape 的结构体,外界只能通过相关的函数来对这个 Shape 进行操作,例如创建(Shape_create), 初始化(`Shape_init`), 移动(Shape_move)等,不能直接访问 Shape 的内部数据结构。 如果想隐藏某个方法,即变成私有方法private,只需要在`shape.c`源文件中相应的方法前加上static限制该函数的作用范围为本文件内就可以了,既然隐藏了该方法也就不必在`shape.h`中声明该函数了。 虽然这里没有 class 这样的关键字,数据结构和相关操作是分开写的,看起来不太完美, 但确实是实现了封装。 ![20190923195151524.png](https://oss-club.rt-thread.org/uploads/20210326/e805c8ad39579583e61744a502b2c7e3.png) ## 继承 所有的内核对象都继承自更基础的对象,一直到最基础的对象 `rt_object`,下面从最上层的对象触发,看看这个继承过程是怎样的,以我最近编写的 uart16550 驱动为例。 - `struct zynq_uart16550` ```c struct zynq_uart16550 { struct rt_serial_device serial; // 继承自 rt_serial_device XUartNs550 UartNs550Instance; struct rt_semaphore rx_sem; struct zynq_uart16550_config *config; }; ``` - `struct rt_serial_device` ```c struct rt_serial_device { struct rt_device parent; // 继承自 rt_device const struct rt_uart_ops *ops; struct serial_configure config; void *serial_rx; void *serial_tx; }; ``` - `struct rt_device` ```c struct rt_device { struct rt_object parent; // 继承自 rt_object enum rt_device_class_type type; /**< device type */ rt_uint16_t flag; /**< device flag */ rt_uint16_t open_flag; /**< device open flag */ rt_uint8_t ref_count; /**< reference count */ rt_uint8_t device_id; /**< 0 - 255 */ /* device call back */ rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); #ifdef RT_USING_DEVICE_OPS const struct rt_device_ops *ops; #else /* common device interface */ rt_err_t (*init) (rt_device_t dev); rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); rt_err_t (*close) (rt_device_t dev); rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); rt_err_t (*control)(rt_device_t dev, int cmd, void *args); #endif #if defined(RT_USING_POSIX) const struct dfs_file_ops *fops; struct rt_wqueue wait_queue; #endif void *user_data; /**< device private data */ }; ``` - `struct rt_object` ```c struct rt_object // 作为基础内核对象 { char name[RT_NAME_MAX]; /**< name of kernel object */ rt_uint8_t type; /**< type of kernel object */ rt_uint8_t flag; /**< flag of kernel object */ #ifdef RT_USING_MODULE void *module_id; /**< id of application module */ #endif rt_list_t list; /**< list node of kernel object */ }; ``` 整个继承过程从后到前流程如下: 1. `struct zynq_uart16550` 2. `struct rt_serial_device` 3. `struct rt_device` 4. `struct rt_object` 可以看出,每一次继承,更基础的内核类都作为更高级的内核类的第一个成员出现,这就像是每一次继承发生时,都为新产生的子类添加了更多功能,同时也继承了父类的属性。 ## 多态—改写对象行为 在 RT-Thread 中如何体现了多态呢,这一点有时候会带给我困惑,但是如果仔细阅读平时编写的驱动程序的话,只要使用了 RT-Thread 的驱动框架,就会发现多态的实现几乎无处不在。 多态表示不同的对象可以执行相同的动作,但是要通过他们自己实现的代码来执行。什么意思呢,就是以设备举例,所有的 rt_device 都支持打开关闭,读写配置等操作,但是对不同类型设备这些操作的细节是不同的,例如串口设备的读写操作和块设备的读写操作不一样,但归根结底都是读写操作,在这一点上是没有区别的。 再举一个例子,同样是串口设备,这些串口设备都支持读写操作,但是读写 stm32 串口与操作 zynq 串口的细节就是不一样的,但归根结底,都是读写操作。也就是说,在这种情况下,有了父类,也就是串口设备,就知道说要将来可以进行读写操作,但是对于不同的子类来说,这些读写操作的具体细节要由子类来提供,将来真正要读写的时候,就调用子类提供的代码来实际操作。 下面以实际代码来说明多态在 RT-Thread 中的具体实现,首先观察如下代码: - `struct rt_device` ```c struct rt_device { struct rt_object parent; // 继承自 rt_object enum rt_device_class_type type; /**< device type */ rt_uint16_t flag; /**< device flag */ rt_uint16_t open_flag; /**< device open flag */ rt_uint8_t ref_count; /**< reference count */ rt_uint8_t device_id; /**< 0 - 255 */ /* device call back */ rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); const struct rt_device_ops *ops; // 注意,这里向子类提供了 rt_device 类的操作表,子类就可以进行方法重写 // 使用这种方式实现了多态,使得继承自设备父类的子类可以用于操作不同类型的设备 // 省略多余代码... void *user_data; /**< device private data */ }; ``` - `struct rt_serial_device` ```c struct rt_serial_device { struct rt_device parent; // serial device class 继承了父类 rt_device,也就拥有了父类的 rt_device_ops 函数表 const struct rt_uart_ops *ops; // 注意这里,这里向子类提供了 rt_uart_ops 类的操作表,子类就可以进行方法重写 struct serial_configure config; // 使用这种方式实现了多态,使得继承自串口父类的子类可以用于操作不同类型的串口设备 void *serial_rx; void *serial_tx; }; ``` - `struct zynq_uart16550` ```c struct zynq_uart16550 { struct rt_serial_device serial; // zynq_uart1655 设备类继承了父类 rt_serial_device,也就拥有了父类的 rt_uart_ops 函数表 XUartNs550 UartNs550Instance; struct rt_semaphore rx_sem; struct zynq_uart16550_config *config; }; ``` - `struct rt_uart_ops` ```c struct rt_uart_ops // 支持某一特定类型串口设备所需要重写的方法 { rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg); rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg); int (*putc)(struct rt_serial_device *serial, char c); int (*getc)(struct rt_serial_device *serial); rt_size_t (*dma_transmit)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction); }; ``` 具体实现实现添加操作函数时,实现如下: ```c static const struct rt_uart_ops zynq_uart16550_ops = // 为 ops 函数表设置特定串口设备的操作函数 { .configure = zynq_uart16550_configure, .control = zynq_uart16550_control, .putc = zynq_uart16550_putc, .getc = zynq_uart16550_getc, }; ``` 接下来使用驱动的初始化代码进行方法重写: ``` c uart16550_obj.serial.ops = &zynq_uart16550_ops; // 挂接 rt_serial_device 类中的 rt_uart_ops 函数表,相当于重写了函数表中的方法 ``` ## 参考 - [《C语言对象化与内核对象管理》](https://blog.csdn.net/m0_37621078/article/details/100788959) - [《Polymorphism in C》](https://chris-wood.github.io/2016/02/12/Polymorphism-in-C.html)
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
我夏了夏天
Life isn't about finding yourself, life is about creating yourself.
文章
24
回答
1319
被采纳
19
关注TA
发私信
相关文章
1
rt thread入门到内核学习记录
2
rt_thread 结构定义问题?
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
SFUD
msh
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1443
个答案
289
次被采纳
张世争
805
个答案
174
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
4
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
1
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部