目录
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;
}