本博客将记录:类的相关知识点的第10节的笔记!
今天 总结的这5个知识点都是之前我coding中接触得非常少的,因此务必要重视这一章!
今天总结的知识分为以下5个点:
一、RTTI是什么?
二、dynamic_cast运算符
三、typeid运算符
四、type_info类
五、RTTI与虚函数表
一、RTTI是什么?
所谓的RTTI(Run Time Type Identification):运行时类型识别。
具体一点就是:在程序运行时,程序能够使用基类的指针or引用来检查这些指针or引用所指向的对象的实际派生类型。我们也可以把RTTI这种称呼看成啥一种系统提供给我们的一种能力,或者说是一种功能。这种功能是通过2个运算符来体现到:
①dynamic_cast运算符:能够将基类的指针or引用安全地转换为派生类的指针or引用。
②typeid运算符:返回指针or引用所指向对象的实际的类型。
补充:
要想让上述两个运算符能够正常地工作,那么基类中必须至少要有一个虚函数,不然这2个运算符工作的结果就可能与我们预想的有天壤之别。因为,只有存在虚函数时,这2个运算符才会使用指针or引用所绑定的对象的动态类型。
结论:基类中必须有至少一个虚函数,才能让RTTI的这2个运算符发挥正确的作用!
(更加深层次的原理可能得到我学习深度探索对象模型的第三章才会总结,但是其实我们不用这么折腾地把这种概念性的东西理解地很深奥,说白了你当前阶段会用虚函数来写重写子类中与基类同名的函数就可以了)
二、dynamic_cast运算符
格式:
类名 * 指针名 = dynamic_cast<另一个类名* >(对应的指针);
注意:
在上面我其实已经提及到了,在我们程序运行时,dynamic_cast关键字的操作数必须要包含多态类的类型(什么叫多态?什么叫多态类的类型呢?其实我们并不需要这么学术地去称呼这些听起来这么深奥的概念,就是一句话:要使用dynamic_cast关键字对基类的指针or引用do类型转换的话就必须要让基类的函数中至少有一个是虚函数,否则你根本就没法通过dynamic_cast关键字do类型转换进而简化你写的多态代码)
为什么我说用dynamic_cast关键字可以简化我们写的多态的代码呢?其实很简单,你用基类指针指向子类的对象时,要是想让子类重写其与基类中同名的函数的话,就必须要用虚函数(有时我们甚至直接把基类用do是一个抽象类,直接用写为纯虚函数呢),那么你每一个同名函数就都需要给它在基类的声明中加上virtual关键字,有时这样的操作就会是复杂的繁琐的操作。因此我们才有dynamic_cast关键字来给我们do简化的事情!
请看以下代码:(以上讲解了这么多文字,那么下面我就直接通过代码来带你学习一下哈!)
1)用dynamic_cast转指针类型:
Human.h
#ifndef __HUMAN_H__
#define __HUMAN_H__
#include<iostream>
using namespace std;
class Human {
public:
int m_Age;
Human():m_Age(0) {}
virtual void eat() { cout << "Human要吃饭!" << endl; }
};
#endif __HUMAN_H__
Man.h
#ifndef __MAN_H__
#define __MAN_H__
#include<iostream>
#include"Human.h"
using namespace std;
class Man :public Human{
public:
Man() {}
virtual void eat() { cout << "Man要吃饭!" << endl; }
void manfunc() { cout << "调用了manfunc()!" << endl; }
};
#endif __MAN_H__
main.cpp
#include <iostream>
#include"Human.h"
#include"Man.h"
using namespace std;
void test() {
Human* pHuman = new Man;
Man* pman = dynamic_cast<Man*>(pHuman);
if (pman != nullptr) {//类型转换成功!
cout << "pHuman实际上是指向一个Man类型的指针!" << endl;
//在这里因为用dynamic_cast动态的把pHuman指针指向Man类的对象了,因此
//在这里边去操作Man类的成员函数、成员变量就是非常安全的了!
pman->manfunc();
}
else {//类型转换失败!
cout << "pHuman实际上并不是指向一个Man类型的指针!" << endl;
//pHuman->manfunc();//do不了!因为dynamic_cast类型转换失败了!
}
}
int main(){
test();
return 0;
}
2)用dynamic_cast转引用类型:
Human* pHuman = new Man;
Human& q = *pHuman;
//用try-catch语句来接受转换失败时可能抛出的bad_cast的异常!
try {
Man& manbm = dynamic_cast<Man&>(q);
//if转换不成功,则流程直接进入到catch里边去。如果转换成功,则流程继续走下去!
//走到这里,表示转换成功
cout << "pHuman实际上是一个Man类型!" << endl;
manbm.manfunc();
}
catch (std::bad_cast) {
cout << "pHuman实际上不是一个Man类型!" << endl;
}
三、typeid运算符
typeid的作用:拿到对象类型信息,并返回一个 常量const对象的引用。这个常量对象 其实就是一个标准库中定义的type_info类的类型。(我们如果想输出这个常对象的引用,就可以调用该type_info类的成员函数.name()来do输出!)
格式:
typeid(内置/自定义类型);or typeid(表达式)
请看以下代码:(直接通过代码来学习)
当在Human类和Man类中不声明任何的虚函数时
Human.h
#ifndef __HUMAN_H__
#define __HUMAN_H__
#include<iostream>
using namespace std;
class Human {
public:
int m_Age;
Human():m_Age(0) {}
void eat() { cout << "Human要吃饭!" << endl; }
};
#endif __HUMAN_H__
Man.h
#ifndef __MAN_H__
#define __MAN_H__
#include<iostream>
#include"Human.h"
using namespace std;
class Man :public Human{
public:
Man() {}
void eat() { cout << "Man要吃饭!" << endl; }
void manfunc() { cout << "调用了manfunc()!" << endl; }
};
#endif __MAN_H__
main.cpp
#include <iostream>
#include"Human.h"
#include"Man.h"
#include"Woman.h"
using namespace std;
void test() {
Human* pHuman = new Man;
cout << typeid(*pHuman).name() << endl;
delete pHuman;
}
int main(){
test();
return 0;
}
运行结果:
当在Human类中声明一个虚函数时(只需要在Human.h中将eat函数声明为virtual)
Human.h
#ifndef __HUMAN_H__
#define __HUMAN_H__
#include<iostream>
using namespace std;
class Human {
public:
int m_Age;
Human():m_Age(0) {}
virtual void eat() { cout << "Human要吃饭!" << endl; }
};
#endif __HUMAN_H__
运行结果:
通过上述学习typeid的使用的代码,这也侧面地说明了如果你在写多态代码时,如果写了virtual虚函数时,就可以让基类指针动态绑定到子类的对象上;而如果你没写virtual虚函数的话,那么你就算do了多态(也即让基类指针指向子类对象),你也没办法让基类指针动态绑定到子类的对象上,也即该基类指针实际上还只是指向基类的对象而已。上面的运行结果很好地说明了这一点。
typeid可以用于查看内置的数据类型的类型:(此时typeid就是简单地把定义该变量/对象时的数据类型返回回来而已)
int arr[10]{ 5,1 };
int b = 120;
cout << typeid(arr).name() << endl;//int [10]
cout << typeid(b).name() << endl;//int
cout << typeid(19.68).name() << endl;//double
cout << typeid(pair<int,int>(19, 68)).name() << endl;//std::pair<int,int>
cout << typeid("MyNameIs").name() << endl;//const char [9] or char const [9]
但是,typeid多用于比较两个指针是否都指向同一种类型的对象
case①若两个指针定义的类型相同,则不管他们new的是啥,其指针的typeid都相同
//还是以上述写的类作为代码的例子:
Human* phuman = new Man;
Human* phuman2 = new Human;
if (typeid(phuman).name() == typeid(phuman2).name()) {
cout<<"phuman和phuman2是同一种类型[看指针定义的类型是啥"
<<"那么typeid(pointer_Name)就是啥指针类型]"<<endl;
}
运行结果:
case②若两个指针所指向的对象的类型相同,其*指针(解引用)的typeid都相同
Human* phuman = new Man;
Human* phuman2 = new Man;
Man* pman3 = new Man;
if (typeid(*phuman).name() == typeid(*phuman2).name()) {
cout << "*phuman和*phuman2所指向的对象是同一种类型[看指针所指向的实际对象的类型是啥"
<< "那么typeid(pointer_Name)就是啥指针类型]" << endl;
}
if (typeid(*pman3).name() == typeid(*phuman2).name()) {
cout << "*pman3和*phuman2所指向的对象是同一种类型[看指针所指向的实际对象的类型是啥"
<< "那么typeid(*pointer_Name)就是啥对象类型]" << endl;
}
所以,从上面的代码我们()可以看出来,比较两个指针所指向的对象的类型是否一样时,一定要这样写:typeid(*指针1名).name() == typeid(*指针2名).name()
否则的话如果你把*星号拉下了的话,这只是比较一下两个指针定义时的类型而已,这样是不能达到我们的原始目的的。
四、type_info类:
在我们使用typeid时,其会返回一个常量对象的引用,而这个常量对象引用就是一个标准库中定义好了的类的类型,而这个类就是type_info类!
so下面就介绍一下该类的一些常用的成员变量/方法!
1).name():返回typeid(里面的类型)的类型名!
请看以下代码:
int a = 1, arr[10]{ 1,2, };
float f = 1.1;
double b = 2.288;
//常量 对象的引用
const type_info& tpi1 = typeid(a);
cout << tpi1.name() << endl;//int
cout << typeid(a).name() << endl;//int
cout << "-------------------------------" << endl;
const type_info& tpi2 = typeid(arr);
cout << tpi2.name() << endl;//int [10]
cout << typeid(arr).name() << endl;//int [10]
cout << "-------------------------------" << endl;
const type_info& tpi3 = typeid(f);
cout << tpi3.name() << endl;//float
cout << typeid(f).name() << endl;//float
cout << "-------------------------------" << endl;
const type_info& tpi4 = typeid(b);
cout << tpi4.name() << endl;//double
cout << typeid(b).name() << endl;//double
运行结果:
这里继续再次重复上述我强调过的重点:如果说在写多态的代码时,你的基类不加些任何的虚函数,那么你的就算用基类指针指向子类的对象,实际上这个基类的指针也还是指向的事基类的对象的!一旦你在基类中写了至少一个虚函数,那么此时你的基类指针实际上就回动态地绑定到子类的对象中,也即此时你的基类指针会指向子类对象了!
当Human基类中不含有任何虚函数时,运行下面这段代码:
Human* phuman1 = new Human;
Human* phuman2 = new Man;
const type_info& tpiHuman1 = typeid(*phuman1);
cout << tpiHuman1.name() << endl;
const type_info& tpiHuman2 = typeid(*phuman2);
cout << tpiHuman2.name() << endl;
运行结果:
当Human基类中含有至少一个虚函数时,运行刚才那段代码:
运行结果:
2).operator==()等号以及.operator!=()不等号。(这是标准库中给type_info对象重载的运算符函数)
请看以下代码:
int a1 = 1, a2 = 1;
double a3 = 2.1;
const type_info& tpia1 = typeid(a1);
const type_info& tpia2 = typeid(a2);
const type_info& tpia3 = typeid(a3);
if (tpia1 == tpia2)cout << "tpia1 == tpia2" << endl;
if (tpia1.operator==(tpia2))cout << "tpia1 == tpia2" << endl;
cout<<"--------------------------" << endl;
if (tpia1 != tpia3)cout << "tpia1 != tpia3" << endl;
if (tpia1.operator!=(tpia3)) cout << "tpia1 != tpia3" << endl;
运行结果:
3).operator==()等号以及.operator!=()不等号。(这是标准库中给type_info对象重载的运算符函数)
五、RTTI与虚函数表(virtual-Table):
在C++中,如果类中含有虚函数,那么编译器就会为该类产生一个虚函数表。而虚函数表中有很多项,每一项都是一个指针。每个指针所指向的是这个类中的各个虚函数的入口地址。虚函数表的项中,第一个表项很特殊,它指向的不是虚函数的入口地址,它指向的实际上是咱们这个类所关联的type_info对象。现在我只是简要地介绍一下虚函数表的概念而已,具体学习的话还是要等学习到《深度探索对象模型》时我再深入地学习并总结!
好,那么以上就是这一3.10小节我所回顾的内容的学习笔记,希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~