【C++进阶学习】C++中的多态
零、前言
一、多态的概念和定义
- 概念:
- 示例:买票
- 定义:
-
多态构成条件:
-
示图:

二、虚函数
1、概念和定义
- 虚函数语法:
- 示例:
class Person {
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
- 虚函数重写:
- 示例:
class Person {
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
- 结果:

2、虚函数重写的特例
- 协变
- 示例:
class A {};
class B : public A {};
class Person {
public:
virtual A* f()
{
cout << "virtual A* f()" << endl;
return new A;
}
};
class Student : public Person {
public:
virtual B* f()
{
cout << "virtual B* f()" << endl;
return new B;
}
};
void Func(Person& p)
{
p.f();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
- 结果:

- 派生类虚函数不加virtual
- 原因:
- 示例:
class Person {
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student : public Person {
public:
//派生类虚函数不加virtual也构成虚函数
void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
- 结果:

- 注意:
- 析构函数的重写
- 示例:
class Person {
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person {
public:
virtual ~Student()
{
cout << "~Student()" << endl;
}
};
//只有派生类Student的析构函数重写了Person的析构函数
//当delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数
int main()
{
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
return 0;
}
- 结果:

3、C++11 override 和 final
- 引入:
- final
- 示例:
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "Benz-舒适" << endl; }
};
- 结果:

- override
- 示例:
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
- 结果:

4、重载/重写/重定义对比
- 对比示图:

三、抽象类
- 概念:
- 示例:
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
class BMW :public Car
{
public:
virtual void Drive()
{
cout << "BMW-操控" << endl;
}
};
void Test()
{
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
}
- 结果:

- 接口继承和实现继承:
- 注意:
四、多态的原理
1、虚函数表
- 例题:
// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
- 结果:

- 解释:
- 注意:
- 示例:
// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Func3
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
- 示图:

- 说明:
- 派生类的虚表生成总结:
- 注意:
2、多态的原理
- 示例:
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
- 示图:

- 说明:
- 示图:

- 汇编:
// 以下汇编代码中跟你这个问题不相关的都被去掉了
void Func(Person* p)
{
...
p->BuyTicket();
// p中存的是mike对象的指针,将p移动到eax中
001940DE mov eax,dword ptr [p]
// [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx
001940E1 mov edx,dword ptr [eax]
// [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax
00B823EE mov eax,dword ptr [edx]
// call eax中存虚函数的指针。这里可以看出满足多态的调用,不是在编译时确定的,是运行起来以后到对象的中取找的
001940EA call eax
001940EC cmp esi,esp
}
int main()
{
...
// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,所以这里是普通函数的调用转换成地址时,是在编译时已经从符号表确认了函数的地址,直接call 地址
mike.BuyTicket();
00195182 lea ecx,[mike]
00195185 call Person::BuyTicket (01914F6h)
...
}
3、动态绑定与静态绑定
- 概念:
4、多继承虚函数表
- 示例:
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
typedef void(*VFPTR) ();//这种函数指针的强转是一种特例
void PrintVTable(VFPTR vTable[])
{
cout << " 虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Derive d;
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
//取对象地址,(虚函数表指针一般存在对象前四个字节)要取前四个字节地址则用int*进行强转(只能指针类型强转才能改变指针的步长)
//再进行解引用拿到虚函数表指针,然而这样子无法进行传参(类型不匹配),要强转为虚函数指针数组类型
PrintVTable(vTableb1);
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));//先转为char*步长便于越过Base1类去拿到Base2的地址
PrintVTable(vTableb2);
return 0;
}
- 示图:

- 说明:
- 为什么对于两个虚函数func1的覆盖地址会不一样?
- 对于菱形继承/菱形虚拟继承:
五、继承和多态常见的面试问题
- 什么是多态?
- 什么是重载、重写(覆盖)、重定义(隐藏)?
- 多态的实现原理?
- inline函数可以是虚函数吗?
- 静态成员可以是虚函数吗?
- 构造函数可以是虚函数吗?
- 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
- 对象访问普通函数快还是虚函数更快?
- 虚函数表是在什么阶段生成的,存在哪的?
- C++菱形继承的问题?虚继承的原理?
- 什么是抽象类?抽象类的作用?