多态的介绍
多态的定义
多态是指不同对象调用同一函数时,表现出不同的行为。在继承体系中,构成多态要满足两个关键条件:
1必须通过基类的指针或引用调用虚函数
只有通过基类的指针或引用,才能在运行时根据对象的实际类型来决定调用哪个版本的虚函数(这一过程称为动态绑定)
2被调用的函数必须是基类的虚函数,派生类必须重写基类的虚函数
虚函数:被virtual修饰的类成员函数
重写:派生类中存在与基类函数名,返回值,参数列表完全相同的基函数
class Person{public:virtual void BuyTicket(){cout<<"买票-全价"<<endl;}
}
class Student{public:virtual void BuyTicket(){cout<<"买票-半价"<<endl;}
}
class Soldier{public:virtual void ButTicket(){cout<<"买票-优先"<<endl;}
}
void Func(Person &p)
{p.BuyTicket();//通过基类引用调用虚函数,触发多态
}
int main()
{Person p;Student s;Soldier sol;Func(p);Func(s);Func(sol);return 0;
}虚函数重写的两个例外
1 协变
当基类虚函数返回基类指针或引用,派生类函数返回派生类指针或引用,即使返回值的类型不同,仍构成重写,称为协变。
class A
{
};
class B:public A
{
};
class Person
{
public:virtual A* f(){return new A;//基类虚函数返回A*}
};
class Student:public Person
{public:virtual B* f(){return new B;//派生类虚函数返回B*}
};
说明协变的本质是返回值类型与继承关系一致。仅允许指针或引用的返回值协变,普通类型不支持。
2析构函数的重写
基类与派生类析构函数名称不同,但编译器会将所有的析构函数统一为destructor()
若基类析构函数是虚函数,派生类析构函数无论是否加virtual,都构成重写。
class Person
{public:virtual ~Person(){cout<<"virtual ~Person()"<<endl;}
}
class Student:public Person
{public:~Student(){cout<<"~Student()"<<endl;}
}
int main()
{Person* p1=new Person;// 调用 Person::~Person()Person* p2=new Student;//先调用Person::~Student(),再调用Person::~Person()delete p1;delete p2;return 0;
}override和final
final:禁止虚函数被重写
修饰虚函数:表示该函数不能被派生类重写
修饰类:表示该类不能被继承
class Car
{public:virtual void Drive() final{}
};
class Benz:public Car
{public:virtual void Drive(){}//编译错误Drive()已被final修饰,不能重写
};override
修饰派生类虚函数,检查该类是否严格重写基类的虚函数,若未重写则编译错误。
class Car()
{
public:virtual void Drive(){}
};
Class Benz:public Car()
{public:virtual void Drive() override{cout<<"Benz-舒适"<<endl;}
};
Class BMW:public Car()
{public:virtual void Drive(int speed) override{cout<<"BMW-操控"<<endl;//编译错误:参数列表与基类不同,未构成重写,override 检测报错}
};
重载,重写与隐藏的对比
重载:两个函数在同一个作用域,函数名/参数相同
重写:两个函数分别在派生类和基类的作用域,函数名/参数/返回值都相同(协变例外),两个函数必须是虚函数
隐藏(重定义):两个函数分别在派生类和基类的作用域,函数名相同,两个派生类和基类的函数不构成重写就构成重定义
抽象类
含有纯虚函数的类称为抽象类。纯虚函数是在虚函数后加上=0的函数。
class Car
{public:virtual void Drive=0;//纯虚函数,没有实现,强制派生类重写
};抽象类不能实例化对象
派生类必须重写所有纯虚函数,否则仍为抽象类(无法实例化)
class Car
{
public:virtual void Drive()=0;//纯虚函数};
class Benz:public Car
{
public:void Drive() override{cout<<"Benz-舒适"<<endl;//重写纯虚函数}
};
class BMW : public Car
{
public:void Drive() override { cout << "BMW-操控" << endl; } // 重写纯虚函数
};
int main()
{//Car c;错误,抽象类不能实例化Car* pBenz=new Benz;Car* BMW=new BMW;pBenz->Drive();BMW->Drive();return 0;
}多态的底层逻辑
含有虚函数的类,其对象中会额外存储一个虚表指针(_vptr),该指针指向一张虚函数表。虚表的本质是一个存储虚函数指针的数组,数组末尾通常有一个nullptr作为标志。
基类的虚表指针:
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;
};Base对象的内存布局:
_vfptr虚表指针(4字节)->指向Base的虚表
_b成员变量(4字节)
Base的虚表结构:
Base虚表(存虚函数指针的数组)
&Base::Func1(第一个虚函数指针)
&Base::Func2(第二个虚函数指针)
nullptr(结束标志)
虚函数本身存放在代码段,和普通函数一样。虚表中只存储虚函数的指针,对象中存储的是虚表指针,而非虚表本身(虚表本身存储在代码段)。
派生类的虚表生成规则
派生类Drive继承Base并重写Func1,其虚表重写遵循三步:
class Derive:public Base
{public:virtual void Func1(){cout<<"Drive::Func1()"<<endl;}private:int _d=2;
}
1 拷贝基类虚表:先将基类Base的虚表完全拷贝到派生类Derive的虚表中
2覆盖重写的虚函数:若派生类重写了基类的虚函数,则用派生类的虚函数指针覆盖虚表中对应的基类虚函数指针。
3添加新增虚函数:若派生类有新增的虚函数,按声明顺序将其指针添加到虚表末尾。
Derive的对象内存布局:
继承自Base的部分:
_vptr(虚表指针,4个字节)->指向Base的虚表
_b成员变量
派生类新增部分:
_d
Derive的虚表结构:
Derive 虚表
├─ &Derive::Func1(覆盖后的虚函数指针)
├─ &Base::Func2(继承的虚函数指针,未重写)
└─ nullptr(结束标志)
多态的实现原理
多态的核心是运行时根据对象实际类型,通过虚表指针找到对应虚函数。
当基类指针指向派生类对象,指针p只能访问对象中基类部分的成员,包括虚表指针。
多态调用的三步流程
以 void Func(Person& p) { p.BuyTicket(); } 为例
首先,通过引用p找到对象中的虚表指针_vptr,通过虚表指针找到对应的虚表,在虚表中找到BuyTicket函数的指针,调用对应的指针
根据虚表中存储的函数指针,调用对应的版本(若指向 Person 对象,调用 Person::BuyTicket;若指向 Student 对象,调用 Student::BuyTicket)
虚表指针决定了虚表的地址,虚表的函数指针决定了调用的函数版本,而虚表指针由对象的实际类型决定,因此运行时能动态匹配函数。
单继承与多继承的虚函数表
单继承下,派生类虚函数表遵循“拷贝-覆盖-新增”三步,且仅含一张虚表。
class Base
{public:virtual void func1(){cout<<"Base::func1()"<<endl;virtual void func2(){cout<<"Base::func2()"<<endl;private:int _a;
}
class Derive:public Base
{public:virtual void func1() {cout<<"Derive::func1" <<endl;} // 重写func1virtual void func3() {cout<<"Derive::func3" <<endl;} // 新增虚函数virtual void func4() {cout<<"Derive::func4" <<endl;} // 新增虚函数private:int b;
};虚表结构:
Base虚表:
&Base::func1,&Base::func2,nullptr
Derive虚表;
&Derive::func1(覆盖),&Derive::func2(继承),&Derive::func3(重写),&Derive::func4(新增)
多继承的虚函数表:
多继承下派生类会生成多张虚表,未重写的基类函数存于对应基类的虚表,新增函数默认存于第一个基类的虚表。
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;} // 重写func1virtual void func3() {cout << "Derive::func3" << endl;} // 新增虚函数
private:int d1;
};虚表结构:
Base1对应虚表:&Derive::func1(覆盖),&Derive::func2(继承),&Derive::func3(新增),nullptr
Base2对应虚表:&Derive::func1(覆盖),&Derive::func2(继承),nullptr
