在性能不是很好、资源不是很多的中使用C语言面向对象编程就显得尤为重要

不想错过我的推送,记得到右上角-查看公众号-设置为星星,送你一颗小星星

不知道有多少人了解了语言的发展史。早期C语言的语法功能其实比较简单。随着应用需求和场景的变化,C语言的语法功能也在不断的升级和变化。

虽然我们的教科书有这样一个结论:C语言是面向过程的语言,C++是面向对象的编程语言,但是面向对象的概念已经处于C语言阶段,并且已经应用​​到很多地方,例如某些操作系统。内核、通信协议等

面向对象编程,也称为OOP(Object Oriented Programming)不是一种特定的语言或工具,它只是一种设计方法和设计思想。它表现出的三个最基本的特性是封装、继承和多态。

1、为什么要用C来实现OOP

在阅读正文之前,一定有读者会问这样一个问题:我们有C++面向对象语言,为什么要在C语言中实现面向对象?

C语言,一种非面向对象的语言,也可以用面向对象的思想来编写程序。用面向对象的C++语言更容易实现面向对象的编程,但C语言的效率是其他面向对象编程语言无法比拟的。

当然,用C语言实现面向对象开发是比较难懂的,这也是为什么大部分学过C语言的人都看不懂Linux内核源码的原因。

所以,这个问题其实很容易理解,只要有一定C语言编程经验的读者应该都能明白:与面向过程的C语言和面向对象的C++语言相比,代码运行效率和代码量有很大不同。在性能较差、资源不多的MCU中使用C语言面向对象编程尤为重要。

2、条件

要使用C语言实现面向对象,首先需要具备一些基础知识。例如:(C语言)结构、函数、指针、函数指针等,(C++)基类、派生、多态、继承等。

首先,不仅要了解这些基础知识,还要有一定的编程经验,因为上面说的“面向对象是一种设计方法和设计思想”,如果仅仅停留在字面上的理解,是没有这样的设计理念肯定没有。

因此,不建议初学者使用C语言实现面向对象,尤其是在实际项目中。建议在使用前先练习好基本功。

使用C语言实现面向对象的方法有很多,下面将介绍最基本的封装、继承和多态。

3、包

封装就是将数据和函数封装到一个类中。事实上,大多数 C 语言程序员都接触过它们。

C标准库中的fopen()、fclose()、fread()、fwrite()等函数的操作对象是FILE。数据内容为FILE如果没有覆盖虚函数,数据读写操作为fread()、fwrite(),fopen()类比构造函数,fclose()是析构函数。

这似乎很容易理解,所以让我们实现基本的封装特性。

这是Shape类的声明,非常简单易懂。通常,声明将放在头文件“Shape.h”中。让我们看一下与Shape类相关的定义,当然是在“Shape.c”中。

再看main.c

编译后看执行结果:

整个例子很简单,也很容易理解。以后写代码的时候,要多考虑标准库的文件IO操作,有意识地培养面向对象编程的思维。

4、继承

继承就是在已有类的基础上定义一个新的类,这样有助于代码的复用,更好的组织代码。在C语言中,实现单继承非常简单,只要将基类放在被继承类的第一个数据成员的位置即可。

比如我们现在要创建一个Rectangle类,我们只需要继承Shape类已有的属性和操作,然后在Rectangle中添加与Shape不同的属性和操作即可。

以下是Rectangle的声明和定义:

我们来看看Rectangle的继承关系和内存布局:

由于这种内存布局,您可以安全地将指向 Rectangle 对象的指针传递给期望指向 Shape 对象的指针的函数,即参数为“Shape *”的函数,您可以传递“Rectangle”*”,而且很安全,这样子类的所有属性和方法都可以被继承类继承!

输出结果:

5、多态性

C++ 语言通过使用虚函数来实现多态性。在C语言中,也可以实现多态。

现在,我们要添加另一个圆,为了扩展 Shape 中的功能,我们要添加 area() 和 draw() 函数。但是Shape相当于一个抽象类,我不知道怎么计算它自己的面积,更不知道怎么画自己。此外,矩形和圆形的面积计算方式与几何图像不同。

让我们重新声明 Shape 类:

看添加虚函数后的类图:

5.1 虚拟表和虚拟指针

虚表是指向该类所有虚函数的函数指针的集合。

虚拟指针是指向虚拟表的指针。这个虚拟指针必须存在于每个对象实例中,并且将被所有子类继承。

这些在“深入了解 C++ 对象模型”的第一章中进行了介绍。

5.2 在构造函数中设置 vptr

在每个对象实例中如果没有覆盖虚函数,必须初始化 vptr 以指向其 vtbl。初始化的最佳位置是在类构造函数中。实际上,在构造函数中,C++ 编译器隐式创建了一个已初始化的 vptr。在 C 语言中,我们必须显式地初始化 vptr。

下面展示了如何在 Shape 的构造函数中初始化这个 vptr。

5.3 继承 vtbl 并重载 vptr

如前所述,基类包含vptr,子类会自动继承。但是,vptr 需要由子类的 vtable 重新分配。而且,这也必须发生在子类的构造函数中。下面是 Rectangle 的构造函数。

5.4 虚函数调用

有了前面虚拟表(Virtual Tables)和虚拟指针(Virtual Pointers)的基本实现,就可以用下面的代码实现虚拟调用(后期绑定)了。

这个函数可以放在一个.c文件中,但是会带来每次虚调用都有额外的调用开销的缺点。为了避免这个缺点,如果编译器支持内联函数(C99)。我们可以将定义放在头文件中,如下所示:

如果是旧的编译器(C89),我们可以用宏函数来做,像这样:

看一下示例中的调用机制:

5.5 main.c

输出结果:

6、总结

同样,面向对象编程是一种方法,不限于一种编程语言。用C语言实现封装和单继承,理解和实现起来比较简单,但是多态会稍微复杂一些。如果您打算广泛使用多态,建议切换到 C++ 语言。毕竟,这层复杂性是被这种语言封装的。现在,您只需要简单地使用它。但这并不意味着C语言不能实现多态的特性。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论