【C++】多态

发布时间:2024年01月12日


一、什么是多态?

多态是面向对象编程中的一个重要概念,它指的是通过一个基类指针或引用调用一个虚函数时,会根据具体对象的类型来调用该虚函数的不同实现。在多态中,相同的操作可以作用于不同的对象,而具体执行的操作则取决于对象的类型和特性。

多态的好处是可以提高代码的可扩展性和复用性,使得程序更加灵活和通用。多态也是实现多态继承和接口继承的基础。

举个例子:

#include <iostream>
using namespace std;

class Person 
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "成年人买票-全价" << endl; 
	}
};

class Student : public Person 
{
public:
	virtual void BuyTicket()
	{ 
		cout << "学生买票-半价" << endl; 
	}
	
};
void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person p;
	Student s;
	Func(p);
	Func(s);
	return 0;
}

运行结果:
在这里插入图片描述

二、如何实现多态?

  1. 构成继承关系
  2. 必须通过基类的指针或者引用调用虚函数
  3. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

2.1 虚函数

virtual修饰的类成员函数称为虚函数

2.2 虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
注意:参数类型相同,但是与函数有无缺省参数和缺省参数相不相等无关

虚函数重写的两个例外:

  1. 协变(基类与派生类虚函数返回值类型不同)
    派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
    针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
  2. 析构函数的重写(基类与派生类析构函数的名字不同)
    如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
    都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
    看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
    理,编译后析构函数的名称统一处理成destructor。

2.3 C++11 override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮
助用户检测是否重写。

  • final:修饰虚函数,表示该虚函数不能再被重写
  • override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

2.4 重载、覆盖(重写)、隐藏(重定义)的对比

  1. 重载:
    重载是指在同一个作用域内,函数名相同,但参数列表不同(参数个数不同,或者参数类型不同,或者参数个数和参数类型都不同),返回值类型可相同也可不同的情况。重载可以提高代码的可读性和灵活性,实现不同的功能。重载是一种静态多态,也就是编译时多态,编译器会根据函数名和参数列表来确定调用哪个函数。

  2. 重写
    指在派生类中,定义了一个与基类虚函数完全相同的函数(函数名,参数列表,返回值类型都相同),或者满足协变返回类型的条件(函数名,参数列表相同,返回值类型为基类虚函数返回值类型的子类型)。覆盖(重写)可以实现动态多态,也就是运行时多态,运行时会根据对象的实际类型来动态绑定虚函数的实现。

  3. 隐藏
    指在不同的作用域内,定义了一个与另一个函数同名的函数,但参数列表可以相同也可以不同,返回值类型也可以相同也可以不同的情况。隐藏(重定义)会导致同名函数的屏蔽现象,也就是说,如果在派生类中定义了一个与基类同名的函数,那么基类的同名函数就会被隐藏,无论基类的函数是否为虚函数。如果要调用被隐藏的函数,需要使用作用域运算符::来指定。

三、抽象类

3.1 概念

在虚函数的后面写上 = 0 ,则这个函数为纯虚函数包含纯虚函数的类叫做抽象类(也叫接口
类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象只有重写纯虚函数,派生
类才能实例化出对象
。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

3.2 接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成
多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

四、多态的原理

4.1 虚函数表

  • C++虚函数表是一种用于实现多态的机制,它是一个存储虚函数地址的数组,每个含有虚函数的类或者虚继承的子类都有一个自己的虚函数表。
  • 每个含有虚函数的对象都有一个指向虚函数表的指针,称为虚函数表指针,它是编译器自动添加的一个隐藏成员。

派生类的虚函数表是怎么生成的呢?

  1. 先将基类中的虚表内容拷贝一份到派生类虚表中
  2. 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
  3. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后

4.2 多态的原理

当传父类的的对象时,调用父类自己的虚函数;传子类的对象时,将子类的对象赋值给父类,但子类已经完成虚函数的重写,子类的虚函数表会覆盖继承自父类的虚函数表,这样调用的便是子类的虚函数了,从而实现了多态调用。


文章来源:https://blog.csdn.net/lyq2632750277/article/details/135531177
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。