【C++】多态

目录

[1. 多态的概念](#1. 多态的概念)

[2. 多态的定义和实现](#2. 多态的定义和实现)

[2.1 构成多态的条件](#2.1 构成多态的条件)

[2.2 虚函数](#2.2 虚函数)

[2.3 虚函数的重写(覆盖)](#2.3 虚函数的重写(覆盖))

[2.4 小试牛刀](#2.4 小试牛刀)

[3. 重载/重写/隐藏的对比](#3. 重载/重写/隐藏的对比)

[4. 纯虚函数和抽象类](#4. 纯虚函数和抽象类)

5.多态的原理

[5.1 虚表](#5.1 虚表)

[5.2 虚表指针](#5.2 虚表指针)

[5.3 对比虚函数、虚表、虚表指针](#5.3 对比虚函数、虚表、虚表指针)

1. 多态的概念

多态(Polymorphism)是面向对象编程的三大基本特征(继承,多态,封装)之一,指的是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

多态的字面意思是"多种形态",在编程中表现为:

同一个接口,使用不同的实例而执行不同操作

同一消息可以根据发送对象的不同而采用多种不同的行为方式

2. 多态的定义和实现

2.1 构成多态的条件

●必须是基类指针或者引用调用虚函数

●被调用的函数必须是虚函数,并完成了虚函数的重写/覆盖

2.2 虚函数

基类中加virtual修饰的类成员函数就是虚函数。

如图中的shout函数:

2.3 虚函数的重写(覆盖)

派生类中有有一个与基类完全相同(即返回类型、函数名、参数列表完全相同)的虚函数,则称派生类的虚函数重写了基类的虚函数。

**注意:**重写虚函数时,派生类中的虚函数可以不加virtual关键字,也可以构成重写,因为这样也可以构成重写,因为基类的虚函数属性被派生类虚函数继承了,但是这种写法并不规范。

2.4 小试牛刀

下面我们来试着做一道题,加深我们对虚函数的理解

下面的程序输出结果是什么:

cpp 复制代码
 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,这类函数就是纯虚函数。纯虚函数语法上是可以定义实现的,但是也没必要,因为要被派生类重写,所以一般只需声明即可。

包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。如果派生类继承抽象类后不重写纯虚函数,那么派生类也是抽象类。

cpp 复制代码
//抽象类
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中调试下列代码

cpp 复制代码
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;
}

5.3 对比虚函数、虚表、虚表指针

相关推荐
钟离墨笺5 分钟前
Go语言学习-->编译器安装
开发语言·后端·学习·golang
why15111 分钟前
百度golang研发一面面经
开发语言·golang
钟离墨笺1 小时前
Go语言学习-->从零开始搭建环境
开发语言·后端·学习·golang
whoarethenext1 小时前
使用 C++/OpenCV 图像直方图比较两个图片相似度
开发语言·c++·opencv·直方图·相似度对比
csdndenglu2 小时前
QT 5.9.2+VTK8.0实现等高线绘制
开发语言·qt
某某2 小时前
DashBoard安装使用
大数据·开发语言·kubernetes
@Turbo@2 小时前
【QT】QString& 与QString区别
开发语言·qt
明月看潮生5 小时前
青少年编程与数学 02-020 C#程序设计基础 15课题、异常处理
开发语言·青少年编程·c#·编程与数学
鸽子炖汤5 小时前
LRC and VIP
c++·算法·图论
鑫鑫向栄6 小时前
[蓝桥杯]机器人塔
数据结构·c++·算法·蓝桥杯