1) 什么是多态性?C++中如何实现多态?
函数重写(覆盖):
定义:子类重新定义父类中有相同名称,返回值和参数的虚函数,主要在继承关系中出现。
父类中的fun函数是虚函数,子类中有返回值,名字 参数 相同的fun函数,那么子类中的fun函数(无论加不加virtual)都是虚函数
基本条件:
- 重写的函数和被重写的函数必须都虚(virtual)函数,并分别位于基类和派生类中
- 重写的函数和被重写的函数,返回值,函数名和函数参数必须完全一致;
函数隐藏:子类中只要和父类函数名字相同不是重写,一定是函数隐藏。
多态的基本概念:
- 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态(父类的指针或引用指向子类对象,并且调用子类的重写函数)
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
父类指针或引用指向子类对象时,函数隐藏不会调用子类中的函数而是调用父类中的函数
调用的函数是隐藏函数时,调用函数的指针或对象是什么类型调用哪里的函数
总结:
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
多态的实现:
为了实现 C++ 的多态,C++ 使用了一种动态绑定的技术。这个技术的核心是虚函数表。下面介绍虚函数表是如何实现动态绑定的。
类的虚函数表:
- 每个包含了虚函数的类都包含一个虚表(存放虚函数指针的数组)。
- 当一个类(B)继承另一个类(A)时,类 B 会继承类 A 的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。
- 以下的代码。类 A 包含虚函数vfunc1,vfunc2,由于类 A 包含虚函数,故类 A 拥有一个虚表。
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
class B : public A{//此时类B也拥有自己的虚表
};
纯虚函数和抽象类:
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类拥有纯虚函数,这个类也称为抽象类(一般作为基类)
抽象类特点:
- 无法实例化对象(抽象类不能创建对象)
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
- 纯虚函数在虚表中存放的是 0
虚函数在虚表中存放的是函数地址
虚析构和纯虚析构:
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码(如果不加virtual将父类的析构改为虚析构或者纯虚析构 子类的析构会将父类的析构隐藏 从而造成内存泄露)
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
父类指针指向子类对象,只能调用父类成员函数
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法: virtual ~类名(){}
纯虚析构语法: virtual ~类名() = 0;
示例:
#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "animal构造" << endl;
}
//如果不加virtual 子类的析构会将父类的析构隐藏 从而造成内存泄露
virtual ~Animal()
{
cout << "animal析构" << endl;
}
};
class cat :public Animal
{
public:
cat()
{
cout << "cat构造" << endl;
}
~cat()
{
cout << "cat析构" << endl;
}
};
int main() {
Animal *a = new cat;//父类指针指向子类对象
delete a;
return 0;
}
2)多态性的好处是什么?
多态性在面向对象编程中有以下几个主要好处:
一、提高代码的可扩展性和可维护性
1. 可扩展性:
当需要添加新的功能或类型时,可以在不修改现有代码的基础上进行扩展。例如,在一个图形绘制系统中,如果要添加一种新的图形类型,只需要从基类派生一个新的类并实现相应的虚函数,而不需要修改已有的绘制代码。
这样可以避免对现有系统的大规模修改,降低引入新错误的风险。
2. 可维护性:
由于多态性使得代码的结构更加清晰,不同类型的对象的行为被封装在各自的类中,通过统一的接口进行调用。这使得代码更容易理解和维护。
当需要修改某个特定类型的行为时,只需要在该类型的类中进行修改,而不会影响其他类型的对象。
二、增强代码的灵活性和通用性
1. 灵活性:
多态性允许根据不同的情况动态地选择不同的实现。例如,在一个游戏中,不同的角色可能有不同的攻击方式,可以通过多态性在运行时根据角色的类型选择合适的攻击函数。
这种灵活性使得代码能够适应不同的场景和需求变化。
2. 通用性:
可以编写通用的代码来处理不同类型的对象。例如,可以定义一个函数,接受一个基类类型的参数,然后通过多态性调用不同派生类的虚函数,实现对不同类型对象的统一处理。
这样的通用代码可以提高代码的复用性,减少重复编写类似代码的工作量。
三、实现代码的封装和信息隐藏
1. 封装:
多态性可以将不同类型的实现细节封装在各自的类中,只通过基类的接口暴露必要的功能。这样可以避免外部代码直接依赖于具体的实现细节,提高代码的稳定性。
例如,在一个图形绘制系统中,外部代码只需要知道如何调用绘制函数,而不需要了解不同图形的具体绘制算法。
2. 信息隐藏:
通过多态性,可以隐藏不同类型对象的内部状态和实现细节,只暴露必要的接口。这有助于提高代码的安全性,防止外部代码意外地修改内部状态。
同时,信息隐藏也使得代码更加易于理解和维护,因为外部代码不需要关心复杂的内部实现。