IOT-OS之RT-Thread(三)--- C语言对象化与内核对象管理

一、C语言对象化模型

RT-Thread的内核对象模型是一种非常有趣的面向对象实现方式。由于C语言更为面向系统底层,操作系统核心通常都是采用C语言和汇编语言混合编写而成。C语言作为一门高级计算机编程语言,一般被认为是一种面向过程的编程语言:程序员按照特定的方式把要处理事物的过程一级级分解成一个个子过程。面向对象源于人类对世界的认知多偏向于类别模式,根据世界中不同物品的特性分门别类的组织在一起抽象并归纳,形成各个类别的自有属性。

在计算机领域一般采用一门新的,具备面向对象特征的编程语言实现面向对象的设计,例如常见的编程语言C++,Java,Python等。那么RT-Thread既然有意引入对象系统,为什么不直接采用C++来实现? 这个需要从C++的实现说起,用过C++的开发人员都知道,C++的对象系统中会引入很多未知的东西,例如虚拟重载表、命名粉碎、模板展开等。对于一个需要精确控制的系统,这不是一个很好的方式,假于他人之手不如握入己手。面向对象有它非常优越的地方,取其精华(即面向对象思想,面向对象设计),也就是RT-Thread内核对象模型的来源。RT-Thread实时操作系统中包含一个小型的,非常紧凑的对象系统,这个对象系统完全采用C语言实现。

采用C语言实现对象化模型的关键是如何运用C语言本身的特性来实现面向对象的特征。

1.1 封装—隐藏内部实现

封装是一种信息隐蔽技术,它体现于类的说明,是对象的重要特性。封装使数据和加工该数据的方法(函数)封装为一个整体,以实现独立性很强的模块,使得用户只能见到对象的外特性(对象能接受哪些消息,具有那些处理能力),而对象的内特性(保存内部状态的私有数据和实现加工能力的算法)对用户是隐蔽的。封装的目的在于把对象的设计者和对象者的使用分开,使用者不必知晓行为实现的细节,只须用设计者提供的消息来访问该对象。

在C语言中,大多数函数的命名方式是动词+名词的形式,例如要获取一个semaphore,会命名成take_semaphore,重点在take这个动作上。在RT-Thread系统的面向对象编程中刚好相反,命名为rt_sem_take,即名词+动词的形式,重点在名词上,体现了一个对象的方法。另外对于某些方法,仅局限在对象内部使用,它们将采用static修辞把作用范围局限在一个文件的内部。通过这样的方式,把一些不想让用户知道的信息屏蔽在封装里,用户只看到了外层的接口,从而形成了面向对象中的最基本的对象封装实现。

下面给出一段示例代码,展示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 这样的关键字,数据结构和相关操作是分开写的,看起来不太完美, 但确实是实现了封装。
Shape示例对象结构

1.2 继承—复用现有代码

继承性是子类自动共享父类之间数据和方法的机制。它由类的派生功能体现。一个类直接继承其它类的全部描述,同时可修改和扩充。继承具有传递性。继承分为单继承(一个子类只有一父类)和多重继承(一个类有多个父类,当前RT-Thread的对象系统不能支持)。类的对象是各自封闭的,如果没继承性机制,则类对象中数据、方法就会出现大量重复。继承不仅支持系统的可重用性,而且还促进系统的可扩充性。

RT-Thread通过结构体的层层相套和包含,对象简单的继存关系就体现出来了:父对象放于数据块的最前方,代码中可以通过强制类型转换获得父对象指针。

继续上面的例子,再定义一个Rectangle结构体,里面嵌套Shape结构体,示例代码如下:

// rectangle.h
#include "shape.h"

typedef struct{
   
	Shape base;
	int width;
	int height;
}Rectangle;

Rectangle * Rectangle_create(int x, int y, int width, int height);

// rectangle.c
Rectangle * Rectangle_create(int x, int y, int width, int height)
{
   
	Rectangle * r = malloc(sizeof(Rectangle));
	Shape_init((Shape *) r, x, y);
	r->width = width;
	r->height = height;
	return r;
}

// main.c
include "rectangle.h"

int main(int argc, char *argv[])
{
   
	Rectangle * r = Rectangle_create(5, 5, 20, 10);
	Shape_move((Shape *) r, 30, 40);
	return 0;
}

结构体Rectangle和Shape在内存中的分布关系如下:
结构体Rectangle与Shape的内存分布关系
通过这种组合方式,也算是实现了继承,继承关系如下图所示:
结构体Rectangle与Shape的继承关系

1.3 多态—改写对象行为

对象根据所接收的消息而做出动作。同一消息为不同的对象接受时可产生完全不同的行动,这种现象称为多态性。利用多态性用户可发送一个通用的信息,而将所有的实现细节都留给接受消息的对象自行决定,如是,同一消息即可调用不同的方法。例如:RT-Thread系统中的设备:抽象设备具备接口统一的读写接口。串口是设备的一种,也应支持设备的读写。但串口的读写操作是串口所特有的,不应和其他设备操作完全相同,例如操作串口的操作不应应用于SD卡设备中。

C语言的多态性用到了函数指针,而且在结构体中封装了函数指针,封装函数指针的结构体可以成为虚函数表,比如我们仍然基于上面的例子,在结构体中封装两个函数指针(计算图像面积area、画出图形draw)构成虚函数表ShapeVtbl,然后在结构体Shape中包含该虚函数表,示例代码如下:

// shape.h

typedef struct tag_ShapeVtbl ShapeVtbl;
typedef struct tag_Shape Shape;

struct tag_ShapeVtbl{
   
	float (* area)(Shape * self);
	void (* draw)(Shape * self);
};

struct tag_Shape{
   
	ShapeVtbl * vptr;
	int x;
	int y;
};

float Shape_area(Shape * self);

// shape.c

float Shape_area(Shape * self)
{
   
	return (* self->vptr->area)(self);
}

每当调用area方法时,实际上是去虚函数表ShapeVtbl中找对应的方法,只要子类比如Rectangle能把vptr指向不同的函数表比如RectangleTbl,调用Shape_area方法实际上执行的是子类实现的函数比如Rectangle_area,多态就实现了。下面给出Rectangle与Shape对象的多态实现过程示意图:
Rectangle与Shape对象的多态实现示意图
无论是 Rectangle 对象,还是 Square 对象,在调用 Shape_area 方法的时候, 都需要通过 vptr 这个指针找到虚函数表中的 area 方法,对于 Rectangle,找到的是 Rectangel_area 方法,对于 Square,找到的是 Square_area 方法。

在Linux和RT-Thread中对文件和设备的管理就使用了这种面向对象的方法,下面给出RT-Thread中设备操作结构体(虚函数表)代码示例:

// rt-thread-4.0.1\include\rtdef.h
/**
 * operations set for device object
 */
struct rt_device_ops
{
   
    /* common device interface */
    rt_err_t  (*init)   (rt_device_t dev);
    rt_err_t  (*open)   
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值