白话C++系列(27) -- RTTI:运行时类型识别
http://www.cnblogs.com/kkdd-2013/p/5601783.html
RTTI—运行时类型识别
RTTI:Run-Time Type Identification。
那么RTTI如何来体现呢?这就要涉及到typeid和dynamic_cast这两个知识点了。为了更好的去理解,那么我们就通过一个例子来说明。这个例子大家已经非常熟悉了,如下:
首先定义一个Flyable类,在这个类当中有两个纯虚函数:takeoff(起飞)和land(降落)。我们又定义了一个鸟类,并且公有继承了Flyable类,既然public继承了Flyable,就要去实现起飞和降落这两个函数,此外,作为鸟类来说,还有一个自己特有的函数foraging(觅食)。同时,我们还定义了另外一个类Plane,其也以public方式继承了Flyable,并且也实现了起飞和降落这两个函数,此外,作为飞机类来说,其还有一个自己特有的函数Carry(运输)。
在使用的时候,我们假设有如下一个函数dosomething,它的传入参数是Flyable的一个指针,如下:
在这个函数当中,我们可以使用obj这个指针去调用起飞和降落这两个函数。同时,我们可以想一想,如果我们能够对传入的这个指针再做进一步的判断,如如说,我们判断出如果它是一个Bird对象指针,那么我们是不是就可以用这个指针去调用觅食这个函数呢?同理,如果我们判断出它是一个Plane对象指针,那么我们是不是也就可以用这个指针去调用运输这个函数呢?如果想要做到这样的效果,那么就要用到这节课开始提到的知识:运行时类型识别(RTTI)。
我们可以看到,当我们去实现dosomething这个函数的时候,如下:
我们在这调用了起飞函数,最后一行代码调用了降落函数。我们在调用完起飞这个函数之后,我们通过typeid(*obj).name()这样的方法就可以将当前的obj这个指针指向的实际的对象类型打印出来了(比如传入的是飞机,打印出来的就是Plane;如果传入的是Bird,那么打印出来的就是Bird)。当然,我们可以还可以通过if语句对类型进行比对,如果我们想要判断当前的obj是不是一个Bird类型,我们就可以通过上面的if判断语句的方法进行比对,比对完成之后,我们就可以将obj通过dynamic_cast的方式,将其转换为Bird指针。转换的时候,需要注意的是,dynamic_cast<Bird *>(obj),尖括号中是目标类型。转换完之后,我们就可以用bird这个指针去调用觅食这个函数。
总结:
dynamic_cast注意事项:
- l 只能应用于指针和引用的转换
- l 要转换的类型当中必须包含虚函数(如果没有虚函数,转换就会失败)
- l 转换成功返回子类的地址,失败返回NULL
typeid的注意事项:
- l type_id返回一个type_info对象的引用
- l 如果想要通过基类的指针获得派生类的数据类型,基类必须带有虚函数
- l 只能获取对象的实际类型(也就是说,即便这个类含有虚函数,也只能判断当前对象是基类还是子类,而没有办法判断当前指针是基类还是子类)
下面我们来看一看type_info中的内容,如下:
对于type_info这个类来说,当中我们用到一个name()函数。我们在之前的例子当中,通过typeid(*obj)获取到的就是一个type_info的引用,通过这个引用就可以调用name()这个成员函数(typeid(*obj).name()),那么这个被调用的name()成员函数就是在这所看到的name()。语句bool operator == (const type_info& rhs) const;是一个运算符重载(这部分内容后面介绍),大家只要知道,在进行了运算符重载之后,这里的==就可以使得前面的例子中两个type_info对象的比对了(typeid(*obj) = = typeid(Bird))。
RTTI代码实践
/* ************************************************* */
/* RTTI
1. Flyable类,成员函数:takeoff()和land()
2. Plane类,成员函数:takeoff()、land()和carry()
3. Bird类,成员函数:takeoff()、land()和foraging()
4. 全局函数dosomething(Flyable *obj)
*/
/* ************************************************* */
程序结构:
头文件(Flyable.h)
#ifndef FLYABLE_H #define FLYABLE_H//在Flyable这个类中定义两个纯虚函数takeoff()和land() class Flyable { public:virtual void takeoff() = 0;virtual void land() = 0; };#endif
头文件(Bird.h)
#ifndef Bird_H #define Bird_H#include "Flyable.h" #include <string> using namespace std;class Bird:public Flyable //公有继承了Flyable { public:void foraging();//对于Bird类来说,其具有一个特有的成员函数foraging(觅食)virtual void takeoff(); //实现了Flyable中的虚函数takeoff和landvirtual void land(); };#endif
源程序(Bird.cpp)
#include <iostream> #include "Bird.h"using namespace std;void Bird::foraging() {cout << "Bird --> foraging()" << endl; }void Bird::takeoff() {cout << "Bird --> takeoff()" << endl; }void Bird::land() {cout << "Bird --> land()" << endl; }
头文件(Plane.h)
#ifndef PLANE_H #define PLANE_H#include "Flyable.h" #include <string> using namespace std;class Plane:public Flyable //公有继承了Flyable { public:void carry(); //Plane具有一个特有的成员函数carry(运输)virtual void takeoff(); //实现了Flyable中的虚函数takeoff和landvirtual void land();};#endif
源程序(Plane.cpp)
#include <iostream> #include "Plane.h"using namespace std;void Plane::carry() {cout << "Plane --> carry()" << endl; }void Plane::takeoff() {cout << "Plane --> takeoff()" << endl; }void Plane::land() {cout << "Plane --> land()" << endl; }
主调程序(demo.cpp)
#include <iostream> #include "stdlib.h" #include "Bird.h" #include "Plane.h"using namespace std; void dosomething(Flyable *obj) {cout << typeid(*obj).name() << endl; //打印传入的对象指针究竟是什么类型的对象obj->takeoff();if(typeid(*obj) == typeid(Bird)) //这里判断obj这个指针所指向的对象是不是Bird类型{Bird *bird = dynamic_cast<Bird *>(obj); //将obj这个指针通过dynamic_cast强制转换为Bird指针,并且将这个指针赋值给一个新的指针birdbird->foraging(); //通过这个bird指针来调用foraging(觅食)这个成员函数}if(typeid(*obj) == typeid(Plane)) //这里判断obj这个指针所指向的对象是不是Bird类型{Plane *plane = dynamic_cast<Plane *>(obj); //将obj这个指针通过dynamic_cast强制转换为Plane指针,并且将这个指针赋值给一个新的指针planeplane->carry(); //通过这个plane指针来调用carry(运输)这个成员函数}obj->land(); }
我们来到主调函数main()下面,先实例化一个Bird对象b,然后通过调用dosomething函数来传入Bird这个对象b,由于dosoething这个函数传入的参数是一个独享指针,所以这里传入的应该是对象b的地址(&b),如下:
int main() {Bird b;dosomething(&b);system("pause");return 0; }
我们按一下F5,看一下运行结果:
通过运行的结果来比较相应的程序,看一看是如何来运行的。
首先打印出的第一行是 “class Bird”,其是通过dosomething函数中的cout语句打印出来的;接下来打印出的是“Bird –> takeoff()”,其是通过代码obj->takeoff();实现的;第三行打印出的是“Bird –> foraging()”,其运行的一定时dosomething函数中的第一个if判断语句,因为其通过bird这个指针调用了foraging(觅食)这个函数;可见,当前传入的这个obj指针所指向的对象就是一个Bird对象(如果这里我们指向的是一个Plane对象,那么显而易见就会执行第二个if判断语句,从而就会通过plane指针去调用carry(运输)这个函数);最后一行打印出的是“Bird –> land()”,其是通过代码obj->land();实现的。
这里,如果我们实例化一个Plane对象,并将对象指针传入dosomething函数,如下:
int main() {Plane p;dosomething(&p);system("pause");return 0; }
运行结果:
从我们的打印结果就可以反推出RTTI所做的这些工作。
接下来,再通过一些代码再来展示一下关于typeid以及dynamic_cast使用的注意事项。
我们先来看一看typeid,对于typeid来说,它能够看任何一个对象或者指针的类型(包括基本的数据成员的类型)。比如,我们定义一个变量i,就可以通过cout来看一看我们定义的i究竟是什么类型,如下:
int main() {int i = 0;cout << typeid(i).name() << endl;system("pause");return 0; }
我们按F5来看一下运行结果:
打印结果就是int,这就说明i这个变量的数据类型就是int类型(如果我们写成double i;),那么打印出来的就是double类型,如下:
那么,对于typeid来说,它能够打印的指针是指针本身的类型。我们再来看一看typeid打印指针和打印对象的不同之处。
首先,我们用Flyable去定义一个指针p,并且用指针p去指向Bird这样的一个对象(Flyable *p = new Bird();),指向这个对象之后,我们分别来看一看p和*p通过typeid所打印出来的结果如何,看如下代码:
int main() {Flyable *p = new Bird();cout << typeid(p).name() << endl;cout << typeid(*p).name() << endl;system("pause");return 0; }
按一下F5,看一看运行结果:
我们看到,p通过typeid打印出来的结果是“class Flyable *”,也就是说,p是一个Flyable *的数据类型,而对于*p来说,它打印出来的则是“class Bird”,也就是说*p是一个Bird对象。
接着我们再来看一看dynamic_cast有什么使用限制。
为了看到这些限制,我们需要改造一下前面的代码。
修改后的Flyable.h文件如下:
#ifndef FLYABLE_H #define FLYABLE_H//在Flyable这个类中定义两个纯虚函数takeoff()和land() class Flyable { public:void takeoff(){}void land(){} };#endif
修改后的Bird.h文件如下:
#ifndef Bird_H #define Bird_H#include "Flyable.h" #include <string> using namespace std;class Bird:public Flyable //公有继承了Flyable { public:void foraging();//对于Bird类来说,其具有一个特有的成员函数foraging(觅食)void takeoff();void land(); };#endif
此时,对于Bird和Flyable来说,它们之间只是一种普通的子类和父类的关系
那么,当我们用父类的指针去指向一个子类的对象(Flyable *p = new Bird();)也是可以的。那么,我们还能不能通过dynamic_cast来进行指针的转换呢?我们一起来看一看:
int main() {Flyable *p = new Bird();Bird *b = dynamic_cast<Bird *>p; //将Flyable的指针转换为Bird指针,并且将转换完的指针赋值给Bird的一个指针bsystem("pause");return 0; }
此时,我们按F7看一看编译是否通过:
我们看到系统提示“dynamic_cast”:“Flyable”不是多态类型,也就是说,对于Flyable来说,它要求转换的目标类型以及被转换的数据类型都应该是具有虚函数的,如果没有就会报错;当然也不能直接转对象这样的类型,比如说,将Flyable的对象p直接转换为Bird的对象b,如下:
int main() {Flyable p;Bird b = dynamic_cast<Bird>p;system("pause");return 0; }
我们看一看这样是否可行,按F5:
我们看到,这样依然会报错,报错提示依然是“dynamic_cast”:“Flyable”不是多态类型的原因,当然它也不是一个正常的数据类型,因为必须待是引用和指针才可能进行转换,其次还要加上一个条件:这个类当中必须含有虚函数。
白话C++系列(27) -- RTTI:运行时类型识别相关推荐
- java 运行时类型_Java基础之RTTI 运行时类型识别
运行时类型识别(RTTI, Run-Time Type Identification)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息. 多态(polymorphism)是基于R ...
- 深入浅出MFC学习笔记:MFC六大关键技术仿真之RTTI运行时类型识别
RTTI(运行时类型识别) 参考文献:深入浅出MFC-侯捷 怎样去构造类别型录网? 一.定义数据结构: 其中pFirstClass指针属于痊愈变量,所以它应该以static修饰之. 而且我们最终希望达 ...
- C++ 学习笔记之(19) new、delete表达式、RTTI(运行时类型识别)、枚举、类成员指针、嵌套类、局部类、位域、volatile、extern C
C++ 学习笔记之(19) new.delete表达式.RTTI(运行时类型识别).枚举.类成员指针.嵌套类.局部类.位域.volatile.extern C C++ 学习笔记之(19) new.de ...
- Java RTTI运行时类型识别
RTTI(Run-Time Type Identification),通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型. RTTI提供了以下两个非常有用的操作 ...
- java rtti_举例讲解Java的RTTI运行时类型识别机制
1.RTTI:运行时类型信息可以让你在程序运行时发现和使用类型信息. 在Java中运行时识别对象和类的信息有两种方式:传统的RTTI,以及反射.下面就来说下RTTI. RTTI:在运行时,识别一个对象 ...
- MFC六大核心机制之二:运行时类型识别(RTTI)
上一节讲的是MFC六大核心机制之一:MFC程序的初始化,本节继续讲解MFC六大核心机制之二:运行时类型识别(RTTI). typeid运算子 运行时类型识别(RTTI)即是程序执行过程中知道某个对象属 ...
- c++远征之多态篇——运行时类型识别(RTTI)
以下内容源于慕课网的学习整理,如有侵权,请告知删除. 1.RTTI(Run-Time Type Information),运行时类型识别. 涉及typeid.dynamic_cast这两个知识点. R ...
- RTTI机制(运行时类型识别)
RTTI机制(运行时类型识别) 在多态里面,基类里的虚函数和派生类里的虚函数形成了遮蔽,这就导致在主程序运行时,有些表达式的类型没有办法确定.必须等到程序运行结束后,根据具体的环境才能确定.看下面的代 ...
- C++11 的 运行时类型识别type_info
一.type_info与typeid 类type_info保存关于类型的特定于实现的信息,包括类型的名称,以及比较两个类型是否相等或排序顺序的方法. 这是typeid操作符返回的类.具有如下特点: ( ...
最新文章
- java 数组的基本操作
- ASP.NET全球化与本地化 c#多国语言的支持 (项目支持多国语言的开发)
- 《FPGA入门教程》看书随笔——数字电路设计入门
- stract oracle,ORACLE 字符串聚合函数 strCat
- python向服务器请求压缩数据及解压缩数据
- bzoj 4393 Usaco Fruit Feast
- 字符串太长 pep8_Python f字符串– PEP 498 –文字字符串插值
- DOTA版设计模式——责任链
- 传智播客 Web静态服务器-6-epoll
- Leap 使用注意事项
- docker 镜像源_前端 Docker 镜像体积优化
- 数据结构之C语言实现线性表
- 计算机存储数据时2的20次方,2的20次方是多少
- 君康人寿2019年排名_2019中国保险公司竞争力报告出炉 君康人寿盈利能力排名第二...
- Linux学习第一节课
- delphi学习笔记(1)-object pascal语言的数据类型 选择自 xmz2629 的 Blog
- NLP标注工具:【免费:doccano、标注精灵、brat、YEDDA、DeepDive、rasa-nlu-trainer】【收费:Prodigy】
- Allegro设置区域规则的方法
- Pygame实战:BOOM 这有一款超刺激的扎气球游戏等你来玩~
- vue报错Navigating to current location (/login) is not allowed
热门文章
- linux后台不挂断运行 nohup命令
- HTML5笔记——formData
- 即时聊天IM之二 openfire 整合现有系统用户
- BZOJ2435 [Noi2011]道路修建
- linux 文件inode,linux文件系统-inode学习整理
- python pyplot中axis_Python Pyplot xaxis未显示在图形上
- matlab 等分矩阵,用matlab根据列拆分矩阵.
- kotlin 覆盖属性_Kotlin程序| 方法覆盖的示例
- lock_sh 示例_带有示例的Python date __str __()方法
- java 根据类名示例化类_Java即时类| from()方法与示例