前言:为什么要在C中面向对象

虽然c++由c语言发展而来,且其中(大部分)兼容了c的语法。它解决了c中开发繁杂(没有对象、抽象方法),不好阅读(为了实现多态,指针、强制类型转换满天飞),不易维护等痛点。

但不得不说,现在的c++更像一锅大杂烩,学习难度指数级上升。而c与其相比就变得很容易入门了(基础总共就那么点)。而且,很多的嵌入式项目并没有提供c++的工具链,导致仍然不得不使用c编程 (或者折腾一周环境之后,回到用c编程)

当然……以上其实并没有完全说明为什么要在c中面向对象。毕竟要实现的话,又繁琐又不方便……

但是它很有挑战不是喵!而且还可以学到一些新的技巧。

什么是面向对象编程

对于一个程序,可以分为两部分:

  1. 数据(你希望处理的东西)
  2. 算法(如何处理这些数据)

面向过程编程

C就是一个典型的面向过程的语言。它的数据和算法是分开的,你通过将数据一个一个放到函数里进行处理,获得想要的结果。

面向对象编程

面向对象就是将数据和算法包在一起,做成一个东西。接下来无论你想要做什么(无论是改变参数还是产生行为),你都直接告诉这个东西就好

想要让一个语言能够面向对象,一般需要实现三个特征:

1.封装

封装,也就是如何将数据和方法打包在一起,成为一个对象

2.继承

继承,也就是如何让一个对象在另一个对象的基础上进行扩展

3.多态

多态,也就是如何让一个对象不论其中的数据是何种类型,都能够用一种固定的方法来处理

开始面向对象吧!

封装的原理

对于封装来说,可以直接用C中的结构体。而方法的封装也可以直接用函数指针来实现。这是好的。

当然这里会有一个小小的问题:如何实现可见性修饰(也就是private,public,protected)呢?

遗憾的是,c中只能模拟实现出private和public(悲)。对于public方法,我们可以直接封装在结构体中允许读取和修改;对于private,可以只在.c的源代码中声明且加入static修饰,以免暴露或产生冲突。

继承与多态的原理

对于继承,这里就需要对C中结构体的内存排列有一定的的了解了。

C中结构体的对象在内存中的排列方式是与你定义该结构体时相同的(这里我们不妨不管内存对齐的事,你会发现并没有影响)

以下面一个结构体为例:

1
2
3
4
5
struct eg{
int a_int;
struct struct_eg a_struct;
double a_double;
};

它在内存中会如下排列:

那就非常方便了:我们只需要将要继承的结构体放在新结构体的最前面,那就可以直接通过强制类型转换而调用它的父类。而关于实现父类中的方法,则可以将父类中的函数指针重新赋值即可。(当然,这里可以像c++中实现一个虚表,但那样就有点复杂了w)

下面是一个示例:

首先我们有一个基类Animal,其中有一个方法叫bark:

1
2
3
4
typedef struct _Animal {
...
void (*bark)(void* _this);
} Animal;

接下来我们定义一个子类Cat,并实现bark方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _Cat {
Animal inhert;
...
} Cat;

void Cat_bark(void *_this);

void Cat_constructor(void *_this)
{
Cat* this=new(Cat); //如何实现这样的new呢?让我们以后再说

this->inhert.bark=Cat_bark;
}

于是,在内存中,就会变成这样:

而要是我们将其直接转换为Animal类型时,则会只访问到Animal inhert的内存空间,也就是它的父类。而调用父类的bark方法时,由于其指向了Cat_bark函数,我们相当于使用了子类的方法。

当然,这样的方法有一个小小的缺点:只支持单继承(趴

在尾声之后,我会给出一个完整的示例

尾声

至此,我们已经论证了,在C中实现面向对象是可能的。

但是,这里还有一些重大的问题:如果只是如此简单的实现的话,我们只能说是 使用了面向对象的思想 ,却不能说 很好的实现了面向对象 。毕竟,你现在无法简单的new、delete一个对象,你甚至需要每次传入它的this指针,而这使得如cat->bark(_this)与直接调用Cat_bark(_this)毫无区别(悲)。目前也没有实现模板多态性(上面的多态只是继承多态性)

但是,会好起来的~w

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

//<class animal>
typedef struct {
int age;

void (*print_name)(void *_this);
} Animal;

void Animal_constructor(void *_this,int age)
{
Animal* this=_this;

this->age=age;
}

//</class animal>

typedef struct {
Animal inhert;

void (*nyan)(void *_this); //新增的属性
} Cat;

void Cat_print_name(void *_this)
{
printf("this is a cat!\n");
}

void Cat_nyan(void *_this)
{
printf("nyan~\n");
}

void Cat_constructor(void *_this,int age)
{
Cat* this=_this;

Animal_constructor(this,age);
this->inhert.print_name=Cat_print_name;
this->nyan=Cat_nyan;
}

//</Cat>

int main()
{
printf("beg\n");

Cat* cat_test=malloc(sizeof(Cat));
Cat_constructor(cat_test,16);

cat_test->nyan(cat_test);

Animal* animal_test=(Animal*)cat_test;
animal_test->print_name(animal_test);

return 0;
}