最近要开始准备实习了,所以需要对之前学过的知识做一些总结和回顾,最近发的博客可能更多的是对一些概念的理解。
通过C语言实现C++多态
一.C++多态的原理
在C++中,多态机制是通过虚表以及虚表指针来实现的。
我们都知道,当要实现多态功能的时候,都会通过virtual关键字来讲基类中的函数变为虚函数,然后通过子类继承后重写就能够达到多态的效果。但是实际上,在其中,编译器也为我们做了很多的工作。
1.当编译器发现基类中存在虚函数的时候,他会自动的生成一份虚表,在虚表中,存放的就是我们这些虚函数的入口地址。
2.当我们实例化一个带有虚函数对象的时候,编译器会给我们这个对象的前四个字节加一个虚表指针,指向我们上一步中生成的虚表,这也就是为什么当我们对一个存在虚函数的对象sizeof的时候,它的大小会比它的类成员变量总和大四个字节的原因。
3.当我们实例化一个派生类的时候,肯定是会先调用构造函数,这里还需要插入一些知识,我们都知道,当我们定义某个基类的时候,通常的做法是将这个基类的析构函数变为virtual的,也就是虚析构函数,因为在一般的情况下,我们会使用一个基类指针来接收子类对象,当我们释放这个基类指针的时候,就会自动调用我们子类的析构函数,但是假如不定义为虚析构函数的话,就可能导致只调用基类析构函数,从而导致子类析构不完全引发内存泄漏,原因之后会讲。现在回归正题,那我们一定会有疑惑为什么不将子类构造函数设置为虚函数呢?因为从我们上述的第二步我们可以看到,当我们实例化一个对象的时候之后,我们才会有一个虚表指针来指向我们生成的虚表,那么如果我们讲构造函数定义为虚函数的话,编译器要通过虚表指针来访问虚表,但是此时连虚表、虚表指针都没有,因此就会导致报错。现在回归正题,当我们实例化一个派生类的时候,首先会调用的就是基类的构造函数,当基类构造完全后,我们会有一个虚表以及一个基类的虚表指针,指向基类的虚表;进一步我们会调用子类的构造函数,构造完成之后就会有一个虚表指针来指向子类的虚表。
4.这样当我们使用基类指针来接收子类对象的时候,虚表指针就会指向子类的虚表,当我们调用对应的虚函数的时候,就会去虚表中查找从而调用,这样子就完成了多态。
具体多态就不演示了。
二.C语言实现多态
首先我们要明确,我们实现的多态是在运行时,通过父类指针来调用子类中的虚函数。因此首先我们先定义一个函数指针,来代表我们之前所描述的虚函数。
typedef void(*Fun) ();
在定义时,为了描述所谓的继承关系,我们先通过含有has-A的方式来使得C中的结构体能有一种逻辑上的继承关系,在如下所描述的代码中,我们的基类中含有一个函数指针,同样的我们的子类中含有一个父类对象,也就是说我们的子类中也有一个函数指针。
struct Father {
Fun fun;
};
struct Son {
struct Father f;
};
接下来我们来实现所谓的重写,也就是通过函数指针来定义不同的行为:
void funF() {
printf("this is father fun\n");
}
void funS() {
printf("this is son fun\n");
}
然后我们开始我们的常规操作,也就是说,通过父类指针来接收子类对象,当调用父类指针的虚函数的时候,也就是我们如上所描述的函数指针的时候,会产生多态的效果。
void Test1() {
struct Father f;
struct Son s;
//对应的重写
f.fun = funF;
s.f.fun = funS;
struct Father* p1 = &f;
//父类指针接收子类对象
struct Father* p2 = (struct Father*)&s;
p1->fun();
p2->fun();
}
最终结果如下所示: