C++学习笔记 | 多态
前言
在基类的成员函数实现基本功能,并且把基类的成员函数设置为虚函数,留给派生类去扩展功能,提升性能,或者实现个性化的功能,因此产生了多态的概念。即通过对虚函数的重写实现多态。
多态的基本概念
通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举一个例子:同样是买相同区间的火车票,成人和学生的票价不相同。
在继承中构成多态
1.多态的两个构成条件
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数重写。(不重写相当于没有变化,只是继承,无法产生不同结果)
- 必须通过基类的指针或引用调用虚函数
普通继承关系:
#include <iostream> //包含头文件
using namespace std; //指定缺省的命名空间
class CAllComers { //定义报名者类
public:
int m_bh = 0;
void show(){ cout << m_bh << "号" << endl; }
};
class CGirl :public CAllComers {
public:
int m_age = 0;
void show() { cout << m_bh << "号", cout << m_age << "岁" << endl; }
};
int main()
{
CGirl g;
g.m_age = 18;
g.m_bh = 1;
g.show();
}
多态:
#include <iostream> //包含头文件
using namespace std; //指定缺省的命名空间
class CAllComers { //定义报名者类
public:
int m_bh = 0;
virtual void show(){ cout << m_bh << "号" << endl; }
};
class CGirl :public CAllComers {
public:
int m_age = 0;
void show() { cout << m_bh << "号", cout << m_age << "岁" << endl; }
};
int main()
{
CAllComers a; a.m_bh = 3;
CGirl g; g.m_age = 23; g.m_bh = 8;
CAllComers* p; //声明基类指针
p = &a; p->show(); //让基类指针指向基类对象
p = &g; p->show(); //让基类指针指向派生类对象
}
运行结果:
当基类指针指向派生类对象时,会调用派生类中同名的成员函数。
多态与指向对象有关,指向哪个对象调用谁的成员函数
2.虚函数
虚函数:被virtual修饰的类成员函数称为虚函数。
一旦定义了虚函数,该基类的派生类中同名函数也自动成为了虚函数。也就是说在派生类中有一个和基类同名的函数,只要基类加了virtual修饰,派生类不加virtual修饰也是虚函数。
3.注意:
- 只需要在基类的函数声明中加上virtual,函数定义时不能加。
- 在派生类中重定义虚函数时,函数特征要相同。
- 当在基类中定义了虚函数时,如果派生类,没有重定义该函数,那么将使用基类的虚函数。
- 在派生类中重定义了虚函数的情况下,如果想使用基类的虚函数,可以加类名和域解析符。
- 如果要在派生类中重新定义基类的函数,则将它设置为虚函数;否则,不要设置为虚函数。
- 基类引用也可以使用多态。
多态的应用场景
在基类的成员函数实现基本功能,并且把基类的成员函数设置为虚函数,留给派生类去扩展功能,提升性能,或者实现个性化的功能。
使用多态可以让编程更方便简单。
例子:
在这里插入代码片
在这里插入代码片
多态的对象模型
如何析构派生类
纯虚函数和抽象类
运行阶段类型识别dynamic_cast
运行阶段类型识别(RTTI)为程序在运行阶段确定对象的类型。
dynamic_cast运算符把基类指针强制转换到一个派生类指针,它可以安全的将对象的地址赋给特定类型的指针。
在运行过程中,我们真正在使用的是哪个类的对象,这时,可以明确地使用dynamic_cast来得到运行时类型的信息。(通过返回值判断,如果转换成功,dynamic_cast返回对象地址,如果失败,返回nullptr。)
dynamic_cast使用条件
dynamic_cast是为多态场景设计的,所以只适用于包含虚函数的类。
dynamic_cast使用方法和实际应用场景
例子:
#include <iostream> //包含头文件
using namespace std; //指定缺省的命名空间
class Hero //英雄基类
{
public:
int via; //生存能力
int att; //攻击伤害
virtual void skill1() { cout << "英雄释放了一技能。\n"; }
virtual void skill2() { cout << "英雄释放了二技能。\n"; }
virtual void skill3() { cout << "英雄释放了三技能。\n"; }
};
class XS :public Hero
{
public:
void skill1() { cout << "西施释放了一技能。\n"; }
void skill2() { cout << "西施释放了二技能。\n"; }
void skill3() { cout << "西施释放了三技能。\n"; }
void show() { cout << "woshi.\n"; }
};
class HX :public Hero
{
public:
void skill1() { cout << "韩信释放了一技能。\n"; }
void skill2() { cout << "韩信释放了二技能。\n"; }
void skill3() { cout << "韩信释放了三技能。\n"; }
};
class LB :public Hero
{
public:
void skill1() { cout << "李白释放了一技能。\n"; }
void skill2() { cout << "李白释放了二技能。\n"; }
void skill3() { cout << "李白释放了三技能。\n"; }
};
int main()
{
//根据用户选择的英雄,施展对应技能
int id = 0; //英雄的id
cout << "请输入英雄id(1-西施,2-韩信,3-李白):";
cin >> id;
//创建基类指针,让它指向派生类对象,用基类指针调用派生类的成员函数
Hero* ptr = nullptr;
if (id == 1) {
ptr = new XS;
}
else if(id == 2) {
ptr = new HX;
}
else if (id == 3) {
ptr = new LB;
}
if (ptr != nullptr) {
ptr->skill1();
ptr->skill2();
ptr->skill3();
//如果基类指针指向的对象是西施,那么就调用西施的show()函数
if (id == 1){
XS* pxs = (XS*)ptr;//C语言风格,程序员必须保证目标类型正确
pxs->show();
}
delete ptr;
}
}
但是当我们不知道英雄id的时候,我们无法强制转换,且这个是C语言风格,程序员必须保证目标类型正确。
C++的dynamic_cast解决这个问题。
语法:
派生类指针=dynamic_cast<派生类类型*>(基类指针);//如果转换成功,dynamic_cast返回对象地址,失败返回nullptr.
if (ptr != nullptr) {
ptr->skill1();
ptr->skill2();
ptr->skill3();
//如果基类指针指向的对象是西施,那么就调用西施的show()函数
XS*xsptr = dynamic_cast<XS*>(ptr); //把基类指针转换为派生类
if (xsptr != nullptr)xsptr->show();
delete ptr;
}