01、文章目录
02、前言
C++相对其他面向对象语言来说,之所以灵活、高效。很大程度的占比在于其多态技术和模板技术。C++虚函数表是支撑C++多态的重要技术,它是C++动态绑定技术的核心。
本文章将着重图解虚函数表相关知识,在阅读本文前,博主已经默认你已经学习了C++多态的语法,并且通过代码实践过。
03、多态及内存分布
多态的概念,这里想必不用多说,调用相同的接口(方法)有不同的表现。
假设有一个基类ClassA,一个继承了该基类的派生类ClassB,并且基类中有虚函数,派生类实现了基类的虚函数。
我们在代码中运用多态这个特性时,通常以两种方式
多态的两种代码表示:
- classA* a = new classB();
- classB b; classA *a = &b;
以上两种方式都是用基类指针去指向一个派生类实例,区别在于第1个用了new关键字而分配在堆上,第2个分配在栈上,看看下面的内存图:
多态的条件:
- 继承
- 子类重写父类的虚函数
- 父类指针指向子类对象或者父类对象引用子类对象。
请看上图,不同两种方式起手仅仅影响了派生类对象实例存在的位置。
以左图为例,ClassA *a是一个栈上的指针。
该指针指向一个在堆上实例化的子类对象。基类如果存在虚函数,那么在子类对象中,除了成员函数与成员变量外,编译器会自动生成一个指向该类的虚函数表(这里是类ClassB)的指针,叫作虚函数表指针。通过虚函数表指针,父类指针即可调用该虚函数表中所有的虚函数。
04、类的虚函数表与类实例的虚函数指针
首先,不考虑继承的情况,如果一个类中有虚函数,那么该类就有一个虚函数表
这个虚函数表是属于类的,所有该类的实例化对象中都会有一个虚函数表指针去指向该类的虚函数表。
从第一部分的图中我们也能看到,一个类的实例要么在堆上,要么在栈上。也就是说一个类可以有很多很多个实例。但是!一个类只能有一个虚函数表。在编译时,一个类的虚函数表就确定了,这也是为什么它放在了只读数据段中。
我们参考下图理解一下上面这段话的意思:
类ClassA的虚函数表一开始就有了,里面排列的虚函数指针01…等等就是后面实例化的类,得到的虚函数指针,均指向虚函数表。
05、多态及多重继承Code 实现
#include <iostream>
#include <string>
using namespace std;
//涉及到多态、继承,只要基类又虚函数(virtual)派生类都要实现它
class CainA
{
public:
CainA(){cout << " CainA is Constructor" << endl;}
virtual ~CainA() {cout << " CainA is Destructor" << endl;}
public:
void test1() { cout << "CainA is test1" << endl;}
void test2() { cout << "CainA is test2" << endl;}
virtual void test3() { cout << "CainA is test3" << endl;}
virtual void test4() { cout << "CainA is test4" << endl;}
private:
string usernameA;
};
class CainB : public CainA //B继承A
{
public:
CainB() {cout << " CainB is Constructor" << endl;}
virtual ~CainB() {cout << " CainB is Destructor" << endl;}
public:
void serach() { cout << "CainA is serach" << endl;}
virtual void test3() { cout << "CainB is test3" << endl;}
virtual void test4() { cout << "CainB is test4" << endl;}
private:
string usernameB;
};
class CainC : public CainB //C继承B
{
public:
CainC(){cout << " CainC is Constructor" << endl;}
virtual ~CainC() {cout << " CainC is Destructor" << endl;}
public:
void demo1() { cout << "CainC is demo1" << endl;}
virtual void test3() { cout << "CainC is test3" << endl;}
virtual void test4() { cout << "CainC is test4" << endl;}
private:
string usernameC;
};
//main函数写多态的实现,写一种,另一种自己类似
int main()
{
//大家观察test3()函数打印的结果就知道有没有实现多态了
CainA* A = new CainB();
A->test3();
A->test4();
CainB* B = new CainC();
B->test3();
B->search();
CainC* C = new CainC();
C->test3();
C->demo1();
system("pause");
return 0;
}
在第二部分中,我们讨论了在没有继承的情况下,虚函数表的逻辑结构。
那么在有继承情况下,只要基类有虚函数,子类不论实现或没实现,都有虚函数表。
- CainA是基类,普通函数有test1,test2,虚函数有test3,test4.
- CainB继承CainA时,普通函数有serach,虚函数有test3,test4
- CainC继承CainB时,普通函数有demo1,虚函数有test3,test4
基类的虚函数表和子类的虚函数表不是同一个表。下图是基类实例与多态情形下,数据逻辑结构。注意,虚函数表是在编译时确定的,属于类而不属于某个具体的实例。虚函数在代码段,仅有一份。
CainB继承与CainA,其虚函数表是在ClassA虚函数表的基础上有所改动的,变化的仅仅是在子类中重写的虚函数。如果子类没有重写任何父类虚函数,那么子类的虚函数表和父类的虚函数表在内容上是一致的。(请忽略图片的美丑)
下面还是以这个例子为例、
class ClassA
{
public:
ClassA() { cout << "ClassA::ClassA()" << endl; }
virtual ~ClassA() { cout << "ClassA::~ClassA()" << endl; }
void func1() { cout << "ClassA::func1()" << endl; }
void func2() { cout << "ClassA::func2()" << endl; }
virtual void vfunc1() { cout << "ClassA::vfunc1()" << endl; }
virtual void vfunc2() { cout << "ClassA::vfunc2()" << endl; }
private:
int aData;
};
class ClassB : public ClassA
{
public:
ClassB() { cout << "ClassB::ClassB()" << endl; }
virtual ~ClassB() { cout << "ClassB::~ClassB()" << endl; }
void func1() { cout << "ClassB::func1()" << endl; }
virtual void vfunc1() { cout << "ClassB::vfunc1()" << endl; }
private:
int bData;
};
class ClassC : public ClassB
{
public:
ClassC() { cout << "ClassC::ClassC()" << endl; }
virtual ~ClassC() { cout << "ClassC::~ClassC()" << endl; }
void func2() { cout << "ClassC::func2()" << endl; }
virtual void vfunc2() { cout << "ClassC::vfunc2()" << endl; }
private:
int cData;
};
ClassA *a = new ClassB();
a->func1(); // "ClassA::func1()" 隐藏了ClassB的func1()
a->func2(); // "ClassA::func2()"
a->vfunc1(); // "ClassB::vfunc1()" 重写了ClassA的vfunc1()
a->vfunc2(); // "ClassA::vfunc2()"
只有在调用虚函数时,才是调用的子类的,普通函数还是父类。这个结果已经说明了C++的隐藏、重写(覆盖)特性。
同理,也就不难推导出ClassC的逻辑结构图了
类的继承情况是: ClassC继承ClassB,ClassB继承ClassA
这是一个多次单继承的情况。(多重继承)
ClassA* a = new ClassC;
a->func1(); // "ClassA::func1()" 隐藏ClassB::func1()
a->func2(); // "ClassA::func2()" 隐藏ClassC::func2()
a->vfunc1(); // "ClassB::vfunc1()" ClassB把ClassA::vfunc1()覆盖了
a->vfunc2(); // "ClassC::vfunc2()" ClassC把ClassA::vfunc2()覆盖了
ClassB* b = new ClassC;
b->func1(); // "ClassB::func1()" 有权限操作时,子类优先
b->func2(); // "ClassA::func2()" 隐藏ClassC::func2()
b->vfunc1(); // "ClassB::vfunc1()" ClassB把ClassA::vfunc1()覆盖了
b->vfunc2(); // "ClassC::vfunc2()" ClassC把ClassA::vfunc2()覆盖了
06、多继承下的虚函数表(同时继承多个基类)
多继承是指一个类同时继承了多个基类,假设这些基类都有虚函数,也就是说每个基类都有虚函数表,那么该子类的逻辑结果和虚函数表是什么样子呢?
一般我们在开发时,能不用多继承就不用,因为,会把程序结构搞得非常复杂,所以,建议能用单个继承就单个继承。
class ClassA1
{
public:
ClassA1() { cout << "ClassA1::ClassA1()" << endl; }
virtual ~ClassA1() { cout << "ClassA1::~ClassA1()" << endl; }
void func1() { cout << "ClassA1::func1()" << endl; }
virtual void vfunc1() { cout << "ClassA1::vfunc1()" << endl; }
virtual void vfunc2() { cout << "ClassA1::vfunc2()" << endl; }
private:
int a1Data;
};
class ClassA2
{
public:
ClassA2() { cout << "ClassA2::ClassA2()" << endl; }
virtual ~ClassA2() { cout << "ClassA2::~ClassA2()" << endl; }
void func1() { cout << "ClassA2::func1()" << endl; }
virtual void vfunc1() { cout << "ClassA2::vfunc1()" << endl; }
virtual void vfunc2() { cout << "ClassA2::vfunc2()" << endl; }
virtual void vfunc4() { cout << "ClassA2::vfunc4()" << endl; }
private:
int a2Data;
};
class ClassC : public ClassA1, public ClassA2
{
public:
ClassC() { cout << "ClassC::ClassC()" << endl; }
virtual ~ClassC() { cout << "ClassC::~ClassC()" << endl; }
void func1() { cout << "ClassC::func1()" << endl; }
virtual void vfunc1() { cout << "ClassC::vfunc1()" << endl; }
virtual void vfunc2() { cout << "ClassC::vfunc2()" << endl; }
virtual void vfunc3() { cout << "ClassC::vfunc3()" << endl; }
};
ClassA1是第一个基类,拥有普通函数func1(),虚函数vfunc1() vfunc2()。
ClassA2是第二个基类,拥有普通函数func1(),虚函数vfunc1() vfunc2(),vfunc4()。
ClassC依次继承ClassA1、ClassA2。普通函数func1(),虚函数vfunc1() vfunc2() vfunc3()。
在多继承情况下,有多少个基类就有多少个虚函数表指针,前提是基类要有虚函数才算上这个基类。
如图,虚函数表指针01指向的虚函数表是以ClassA1的虚函数表为基础的,子类的ClassC::vfunc1(),和vfunc2()的函数指针覆盖了虚函数表01中的虚函数指针01的位置、02位置。当子类有多出来的虚函数时,添加在第一个虚函数表中。
当有多个虚函数表时,虚函数表的结果是0代表没有下一个虚函数表。" * "号位置在不同操作系统中实现不同,代表有下一个虚函数表。
注意:
1.子类虚函数会覆盖每一个父类的每一个同名虚函数。
2.父类中没有的虚函数而子类有,填入第一个虚函数表中,且用父类指针是不能调用。
3.父类中有的虚函数而子类没有,则不覆盖。仅子类和该父类指针能调用。
最后给出代码和结果:
ClassA1 *a1 = new ClassC;
a1->func1(); // "ClassA1::func1()" 隐藏子类同名函数
a1->vfunc1(); // "ClassC::vfunc1()" 覆盖父类ClassA1虚函数
a1->vfunc2(); // "ClassC::vfunc2()" 覆盖父类ClassA1虚函数
没有a1->vfunc3(),父类没有这个虚函数
ClassA2 *a2 = new ClassC;
a2->func1(); // "ClassA2::func1()" 隐藏子类同名函数
a2->vfunc1(); // "ClassC::vfunc1()" 覆盖父类ClassA2虚函数
a2->vfunc2(); // "ClassC::vfunc2()" 覆盖父类ClassA2虚函数
a2->vfunc4(); // "ClassA2::vfunc4()" 未被子类重写的父类虚函数
ClassC *c = new ClassC;
c->func1(); // "ClassC::func1()"
c->vfunc1(); // "ClassC::vfunc1()"
c->vfunc2(); // "ClassC::vfunc2()"
c->vfunc3(); // "ClassC::vfunc3()"
c->vfunc4(); // "ClassA2::func4()"
07、总结
这篇文章,是看了别人的文章总结的,图文想必不难看出关系,由于不能作图,没办法后面还是用的另外一位博主的图和例子,该电脑是公司电脑被加密了,有问题可以讨论,我会给予解答的。
版权声明:转载请注明出处,并附带上诉博文地址,谢谢!