14.10 RTTI、dynamic_cast、typeid、type_info与虚函数表

一:RTTI是什么(Run Time Type Identification)

运行时类型识别。通过运行时类型识别,程序能够使用基类的指针或者引用来检查这些指针或者引用所指的对象的实际派生类型。

Human *phuamn = new Men;
Human &q = *phuman;  //*phuman表示指针phuman所指向的对象。

RTTI 我们可以把这种称呼看成是一种系统提供给我们的一种能力,或者一种功能。这种功能或者能力是通过2个运算符来体现:<1>dynamic_cast 运算符:能够将基类的指针或者引用安全的转换为派生类的指针或者引用;
<2>typeid 运算符:返回指针或者引用所指对象的实际类型。

补充:要想让RTTI的两个运算符能够正常工作,那么基类中必须至少要有一个虚函数,不然这两个运算符工作的结果就可能跟我们预测的结果不一样。
因为只有虚函数的存在,这两个运算符才会使用指针或者引用所绑定的对象的动态类型(我们new的类型)。

二:dynamic_cast运算符

如果该运算符能够转换成功,说明这个指针实际上是要转换到子类指针或者引用类型。这个运算符能帮我们做安全检查。

Human.h

#pragma once
#ifndef __HUMAN__
#define __HUMAN__
#include <iostream>
using namespace std;

class Human
{
public:
	Human();
	virtual ~Human();
public:
	//virtual void eat();
};
#endif // !__HUMAN__
//注意:
//将析构函数声明成虚函数也是让类变‘虚’。

Human.cpp

#include "Human.h"

Human::Human()
{
	cout << "Human::Human()" << endl;
}

//void Human::eat()
//{
//	cout << "void Human::eat()" << endl;
//}

Human::~Human()
{
	cout << "Human::~Human()" << endl;
}

Men.h

#pragma once
#ifndef __MEN__
#define __MEN__
#include "Human.h"

class Men : public Human
{
public:
	Men();
	virtual ~Men() override;
public:
	void testfunc();
	//void virtual eat() override;
};
#endif // !__MEN__

Men.cpp

#include "Men.h"

Men::Men()
{
	cout << "Men::Men()" << endl;
}

void Men::testfunc()
{
	cout << "void Men::testfunc()" << endl;
}

//void Men::eat()
//{
//	cout << "void Men::eat()" << endl;
//}

Men::~Men()
{
	cout << "Men::~Men()" << endl;
}

Test.cpp

#include <iostream>
#include "Human.h"
#include "Men.h"

using namespace std;

int main()
{

	//用c语言风格的强制类型转换,硬把指针转换成Men*;
	//能够正常的调用Men类的成员函数testfunc();
	Human *phuamn = new Men;
	Men *p = (Men *)(phuamn);
	p->testfunc();

	//指针类型dynamic_cast使用
	Human* phuman = new Men;
	Men* men = dynamic_cast<Men*>(phuman);
	if (men != nullptr)
	{
		men->testfunc();
		cout << "phuman实际指向一个Men类型对象" << endl;
	}
	else
	{
		cout << "phuman实际指向不是一个Men类型对象" << endl;
	}

	//引用类型dynamic_cast使用
	//对于引用,如果用dynamic_cast转换失败,则系统会抛出一个std::bad_cast异常。
	Men mymen;
	Human& qhuman = mymen;
	try
	{
		Men& ifmen = dynamic_cast<Men&>(qhuman);
		//在这里操作类Men里边的成员函数,成员变量都能够操作并且安全的操作
		//转换不成功,则流程直接进入到catch里边去,如果转换成功,流程继续往下走。
		ifmen.testfunc();
		cout << "qhuman实际是一个Men类型对象" << endl;
	}
	catch (const std::bad_cast&)
	{
		cout << "qhuman不是一个Men类型对象" << endl;
	}
	cin.get();
	return 0;
}

三:typeid运算符

typeid(类型[指针/引用]);也可能typeid(表达式)
拿到对象类型信息。typeid就会返回一个常量对象的引用这个常量对象,是一个标准库类型 type_info(类/类型)。

#include <iostream>
#include "Human.h"
#include "Men.h"
#include "Women.h"

using namespace std;
//演示使用,指针未delete
int main()
{
	Human* phuman = new Men;
	Human& qhuman = *phuman;
	cout << typeid(*phuman).name() << endl;  //class Men
	cout << typeid(qhuman).name() << endl;  //class Men

	char a[10] = { 5, 1, 10 };
	int b = 120;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(19.6).name() << endl;
	cout << typeid("abcd").name() << endl;

	//<1> 两个指针定义的类型相同(Human), 不管它们new的是什么, typeid都相等。该例不太符合我们的期盼和要求。
	Human* phuman1 = new Men;
	Human* phuman2 = new Women;
	if (typeid(phuman1) == typeid(phuman2))
	{
		cout << "phuman1和phuman2指针的定义类型相同" << endl;
	}

	//<2> 比较对象时,看的是new出来的是哪个对象或者该指针指向的是哪个对象,和定义该指针时定义的类型没有关系。
	Human* phuman3 = new Men;
	Human* phuman4 = new Men;
	Human* phuman5 = phuman3;
	if (typeid(*phuman3) == typeid(*phuman4))
	{
		cout << "phuman3和phuman4指向同一种类型对象" << endl;
	}
	if (typeid(*phuman4) == typeid(*phuman5))
	{
		cout << "phuman4和phuman5指向同一种类型对象" << endl;
	}

	Human* phuman6 = new Men;
	if (typeid(*phuman6) == typeid(Men))
	{
		cout << "phuman6和Men指向同一种类型对象" << endl;
	}

	cin.get();
	return 0;
}

注意:基类必须要有虚函数,否则上边的条件不成立。
切记:只有当基类有虚函数时,编译器才会对typeid()中的表达式求值,否则如果某个类型不含有虚函数,则typeid()返回的是表达式的静态类型(定义时的类型),既然是定义的类型,编译器就不需要对表达式求值也能知道表达式的静态类型的值。在编译阶段就可以知道。

四:type_info类

typeid就会返回一个常量对象的引用,这个常量对象是一个标准库类型 type_info(类/类类型)
<1>name:名字:返回一个c风格字符串。

Human* phuman = new Men;
const std::type_info& tp = typeid(*phuman);
cout << tp.name() << endl;  //class Men

<2>== 、!=

Human* phuman1 = new Men;
Human* phuman2 = new Men;
const std::type_info& tp1 = typeid(*phuman1);
const std::type_info& tp2 = typeid(*phuman2);
if (tp1 == tp2)
{
	cout << "类型相同" << endl;  //成立,都是Men
}
Human* phuman3 = new Women;
const std::type_info& tp3 = typeid(*phuman3);
if (tp1 == tp3)
{
	cout << "tp1 == tp3 类型相同" << endl;  //不成立,因为是Men和Women比较,但同样,
										  //如果基类中没有虚函数,则就成立。
}
else
{
	cout << "tp1和tp3类型不相同" << endl;			
}

注意:必须要有虚函数

五:RTTI与虚函数表

c++如果类里含有虚函数。编译器就会对该"类"产生一个虚函数表。虚函数表里有很多项,每一项都是一个指针。每个指针指向的是这个类里的各个虚函数的入口地址。
ps:每个指针指向一个虚函数入口地址;如果这个类中有多个虚函数,虚函数表中就有多个项。虚函数表项里,第一个表项很特殊,它指向的不是虚函数的入口地址,他指向实际上是我们这个类所关联的type_info对象。

Human *phuamn = new Men;  //phuman指向的是类的"对象"
const type_info &tp = typeid(*phuman);

phuman(实际指向Men对象)对象里有一个我们看不见的指针,这个指针指向的是这个对象所在的类Men里的虚函数表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值