在C中面向对象:#0x0 封装、继承与多态
前言:为什么要在C中面向对象
虽然c++由c语言发展而来,且其中(大部分)兼容了c的语法。它解决了c中开发繁杂(没有对象、抽象方法),不好阅读(为了实现多态,指针、强制类型转换满天飞),不易维护等痛点。
但不得不说,现在的c++更像一锅大杂烩,学习难度指数级上升。而c与其相比就变得很容易入门了(基础总共就那么点)。而且,很多的嵌入式项目并没有提供c++的工具链,导致仍然不得不使用c编程 (或者折腾一周环境之后,回到用c编程)
当然……以上其实并没有完全说明为什么要在c中面向对象。毕竟要实现的话,又繁琐又不方便……
但是它很有挑战不是喵!而且还可以学到一些新的技巧。
什么是面向对象编程
对于一个程序,可以分为两部分:
- 数据(你希望处理的东西)
- 算法(如何处理这些数据)
面向过程编程
C就是一个典型的面向过程的语言。它的数据和算法是分开的,你通过将数据一个一个放到函数里进行处理,获得想要的结果。
面向对象编程
面向对象就是将数据和算法包在一起,做成一个东西。接下来无论你想要做什么(无论是改变参数还是产生行为),你都直接告诉这个东西就好
想要让一个语言能够面向对象,一般需要实现三个特征:
1.封装
封装,也就是如何将数据和方法打包在一起,成为一个对象
2.继承
继承,也就是如何让一个对象在另一个对象的基础上进行扩展
3.多态
多态,也就是如何让一个对象不论其中的数据是何种类型,都能够用一种固定的方法来处理
开始面向对象吧!
封装的原理
对于封装来说,可以直接用C中的结构体。而方法的封装也可以直接用函数指针来实现。这是好的。
当然这里会有一个小小的问题:如何实现可见性修饰(也就是private,public,protected)呢?
遗憾的是,c中只能模拟实现出private和public(悲)。对于public方法,我们可以直接封装在结构体中允许读取和修改;对于private,可以只在.c的源代码中声明且加入static
修饰,以免暴露或产生冲突。
继承与多态的原理
对于继承,这里就需要对C中结构体的内存排列有一定的的了解了。
C中结构体的对象在内存中的排列方式是与你定义该结构体时相同的(这里我们不妨不管内存对齐的事,你会发现并没有影响)
以下面一个结构体为例:
1 | struct eg{ |
它在内存中会如下排列:
那就非常方便了:我们只需要将要继承的结构体放在新结构体的最前面,那就可以直接通过强制类型转换而调用它的父类。而关于实现父类中的方法,则可以将父类中的函数指针重新赋值即可。(当然,这里可以像c++中实现一个虚表,但那样就有点复杂了w)
下面是一个示例:
首先我们有一个基类Animal,其中有一个方法叫bark:
1 | typedef struct _Animal { |
接下来我们定义一个子类Cat,并实现bark方法:
1 | typedef struct _Cat { |
于是,在内存中,就会变成这样:
而要是我们将其直接转换为Animal类型时,则会只访问到Animal inhert的内存空间,也就是它的父类。而调用父类的bark方法时,由于其指向了Cat_bark函数,我们相当于使用了子类的方法。
当然,这样的方法有一个小小的缺点:只支持单继承(趴
在尾声之后,我会给出一个完整的示例
尾声
至此,我们已经论证了,在C中实现面向对象是可能的。
但是,这里还有一些重大的问题:如果只是如此简单的实现的话,我们只能说是 使用了面向对象的思想 ,却不能说 很好的实现了面向对象 。毕竟,你现在无法简单的new、delete一个对象,你甚至需要每次传入它的this指针,而这使得如cat->bark(_this)
与直接调用Cat_bark(_this)
毫无区别(悲)。目前也没有实现模板多态性(上面的多态只是继承多态性)
但是,会好起来的~w
示例
1 |
|