【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 对比虚函数、虚表、虚表指针

相关推荐
Mr_Xuhhh26 分钟前
信号与槽的总结
java·开发语言·数据库·c++·qt·系统架构
纳兰青华36 分钟前
bean注入的过程中,Property of ‘java.util.ArrayList‘ type cannot be injected by ‘List‘
java·开发语言·spring·list
好开心啊没烦恼38 分钟前
Python 数据分析:DataFrame,生成,用字典创建 DataFrame ,键值对数量不一样怎么办?
开发语言·python·数据挖掘·数据分析
liulilittle41 分钟前
VGW 虚拟网关用户手册 (PPP PRIVATE NETWORK 基础设施)
开发语言·网络·c++·网关·智能路由器·路由器·通信
Devil枫1 小时前
Kotlin高级特性深度解析
android·开发语言·kotlin
ChinaDragonDreamer1 小时前
Kotlin:2.1.20 的新特性
android·开发语言·kotlin
安之若素^1 小时前
启用不安全的HTTP方法
java·开发语言
ruanjiananquan991 小时前
c,c++语言的栈内存、堆内存及任意读写内存
java·c语言·c++
一个天蝎座 白勺 程序猿2 小时前
Python(28)Python循环语句指南:从语法糖到CPython字节码的底层探秘
开发语言·python
持梦远方2 小时前
C 语言基础入门:基本数据类型与运算符详解
c语言·开发语言·c++