C++学习笔记 | 多态

前言

  在基类的成员函数实现基本功能,并且把基类的成员函数设置为虚函数,留给派生类去扩展功能,提升性能,或者实现个性化的功能,因此产生了多态的概念。即通过对虚函数的重写实现多态。

多态的基本概念

   通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

举一个例子:同样是买相同区间的火车票,成人和学生的票价不相同。

在继承中构成多态

1.多态的两个构成条件

  1. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数重写。(不重写相当于没有变化,只是继承,无法产生不同结果)
  2. 必须通过基类的指针或引用调用虚函数

普通继承关系:

#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.注意:

  1. 只需要在基类的函数声明中加上virtual,函数定义时不能加。
  2. 在派生类中重定义虚函数时,函数特征要相同。
  3. 当在基类中定义了虚函数时,如果派生类,没有重定义该函数,那么将使用基类的虚函数。
  4. 在派生类中重定义了虚函数的情况下,如果想使用基类的虚函数,可以加类名和域解析符。
  5. 如果要在派生类中重新定义基类的函数,则将它设置为虚函数;否则,不要设置为虚函数。
  6. 基类引用也可以使用多态。

多态的应用场景

  在基类的成员函数实现基本功能,并且把基类的成员函数设置为虚函数,留给派生类去扩展功能,提升性能,或者实现个性化的功能。

使用多态可以让编程更方便简单。
例子:

在这里插入代码片
在这里插入代码片

多态的对象模型

如何析构派生类

纯虚函数和抽象类

运行阶段类型识别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;
	}

typeid运算符和type_info类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值