目录
1. 多态的概念
2. 多态的定义和实现
2.1 构成多态的条件
2.2 虚函数
2.3 虚函数的重写(覆盖)
2.4 小试牛刀
3. 重载/重写/隐藏的对比
4. 纯虚函数和抽象类
5.多态的原理
5.1 虚表
5.2 虚表指针
5.3 对比虚函数、虚表、虚表指针
1. 多态的概念
多态(Polymorphism)是面向对象编程的三大基本特征(继承,多态,封装)之一,指的是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
多态的字面意思是"多种形态",在编程中表现为:
同一个接口,使用不同的实例而执行不同操作
同一消息可以根据发送对象的不同而采用多种不同的行为方式
2. 多态的定义和实现
2.1 构成多态的条件
●必须是基类指针或者引用调用虚函数
●被调用的函数必须是虚函数,并完成了虚函数的重写/覆盖
2.2 虚函数
基类中加virtual修饰的类成员函数就是虚函数。
如图中的shout函数:
2.3 虚函数的重写(覆盖)
派生类中有有一个与基类完全相同(即返回类型、函数名、参数列表完全相同)的虚函数,则称派生类的虚函数重写了基类的虚函数。
注意:重写虚函数时,派生类中的虚函数可以不加virtual关键字,也可以构成重写,因为这样也可以构成重写,因为基类的虚函数属性被派生类虚函数继承了,但是这种写法并不规范。
2.4 小试牛刀
下面我们来试着做一道题,加深我们对虚函数的理解
下面的程序输出结果是什么:
class A{public:virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}virtual void test(){ func();}};class B : public A{public:void func(int val = 0){ std::cout<<"B->"<< val <<std::endl; }};int main(int argc ,char* argv[]){B*p = new B;p->test();return 0;}
解析:
p->test() 调用 A::test()(test() 是继承自 A 的虚函数,但未被 B 重写)。
A::test() 内部调用 func(),由于 func() 是虚函数且 p 指向 B 对象,实际调用 B::func()。
在 C++ 中,默认参数是静态绑定(编译时确定),而虚函数是动态绑定(运行时确定)。因此,当通过基类指针或引用调用虚函数时:调用的是派生类(实际对象类型)的重写版本。使用的是基类函数声明的默认值,而非派生类的默认值。
所以默认参数 val 的值来自 A::func() 的定义(val = 1),而非 B::func() 的 val = 0。
因此,执行 B::func(1),输出 B->1。
3. 重载/重写/隐藏的对比
重载:两个函数在同一作用域;函数名相同,参数相同,参数类型或个数不同;返回值可同可不同
重写(覆盖):两个函数分别在父类和子类不同作用域;函数名,参数,返回值必须相同,协变例外;两个函数都必须是虚函数
隐藏:两个函数分别在父类和子类不同作用域;函数名相同,只要不构成重写,就是隐藏;父子类的成员变量相同也是隐藏
4. 纯虚函数和抽象类
在虚函数后面加上=0,这类函数就是纯虚函数。纯虚函数语法上是可以定义实现的,但是也没必要,因为要被派生类重写,所以一般只需声明即可。
包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。如果派生类继承抽象类后不重写纯虚函数,那么派生类也是抽象类。
//抽象类
class Car
{
public://纯虚函数virtual void Drive() = 0;
};//库里南
class Cullinan:public Car
{
public:virtual void Drive(){cout << "Cullinan" << endl;}
};//宾利
class Bentley :public Car
{
public:virtual void Drive(){cout << "Bentley" << endl;}
};int main()
{//Car car;//抽象类不能实例化Car* pC = new Cullinan;pC->Drive();Car* pB = new Bentley;pB->Drive();return 0;
}
5.多态的原理
5.1 虚表
虚表(vTable):存储虚函数地址的表,每个类一份。
虚表是编译器为每个包含虚函数的类创建的一个隐藏数据结构,它是一个函数指针数组,存储了该类所有虚函数的实际地址。
虚表的特点包括:
每个具有虚函数的类都有自己的虚表
虚表在编译阶段生成,并存储在程序的只读数据段
派生类的虚表会继承基类的虚表内容,并替换掉被重写的虚函数地址
5.2 虚表指针
虚表指针(vptr):每个对象内部隐藏的指针,指向所属类的虚表。
虚表指针特点:
在对象构造时被初始化,指向该对象所属类的虚表
通常位于对象内存布局的最前面
大小通常为一个指针的大小(32位系统4字节,64位系统8字节)
在VS2022中调试下列代码
class animal
{
public:virtual void shout(){cout << "喊叫" << endl;}
};class cat : public animal
{
public:virtual void shout(){cout << "喵喵" << endl;}
};void say(animal& an)
{an.shout();
}int main()
{animal a;cat c;say(a);say(c);return 0;
}