白话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”不是多态类型的原因,当然它也不是一个正常的数据类型,因为必须待是引用和指针才可能进行转换,其次还要加上一个条件:这个类当中必须含有虚函数。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用头文件的原因和规范

原因 通过头文件来调用库功能。在很多场合&#xff0c;源代码不便&#xff08;或不准&#xff09;向用户公布&#xff0c;只 要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库 功能&#xff0c;而不必关心接口怎么实现的。编译器会从库中提取相应…

转圈踢人问题

https://www.cnblogs.com/lanxuezaipiao/p/3339603.html 有N个人围一圈依次报数&#xff0c;数到3的倍数的人出列&#xff0c;问当只剩一个人时他原来的位子在哪里&#xff1f; 解答&#xff1a;经典的转圈踢人问题&#xff0c;好吧专业一点&#xff0c;约瑟夫环问题&#xff0…

Linux系统【三】回收子进程

孤儿进程 父进程先于子进程结束&#xff0c;则子进程成为孤儿进程&#xff0c;子进程的父进程成为init进程&#xff0c;则称init进程领养孤儿进程。现在好像是用户进程中的system进程。 僵尸进程 进程终止&#xff0c;父进程不进行回收&#xff0c;自己成残留资源(PCB)存放在…

string类的基本实现

https://blog.csdn.net/qq_29503203/article/details/52265829在面试中面试官常常会让你写出string类的基本操作&#xff0c;比如&#xff1a;构造函数&#xff0c;析构函数&#xff0c;拷贝构造等等.下面是除此之外的一些操作&#xff0c;希望可以帮助你更好的理解string以便以…

Python3常用数据结构

Python3中有三种组合数据类型&#xff0c;分别为&#xff1a; 序列类型&#xff1a;字符串&#xff08;str&#xff09;、元组&#xff08;tuple&#xff09;、列表&#xff08;list&#xff09;集合类型&#xff1a;集合&#xff08;set&#xff09;映射类型&#xff1a;字典…

Linux C++ 回射服务器

http://blog.csdn.net/qq_25425023/article/details/53914820回射服务器就是服务端将客户端的数据发送回去。我实现的回射服务器返回增加了时间。服务端代码&#xff0c;可以很容易看懂&#xff1a;[cpp] view plaincopy#include <sys/socket.h> #include <stdio.h&g…

TCP第四次挥手为什么要等待2MSL

当客户端进入TIME-WAIT状态的时候(也就是第四次挥手的时候)&#xff0c;必须经过时间计数器设置的时间2MSL(最长报文段寿命)后&#xff0c;才能进入关闭状态&#xff0c;这时为什么呢&#xff1f;&#xff1f;&#xff1f; 这最主要是因为两个理由&#xff1a; 1、为了保证客户…

计算机网络【一】概述+OSI参考模型

网络概述 局域网:覆盖范围小(100m以内)&#xff0c;自己花钱买设备&#xff0c;带宽固定(10M,100M,1000M)&#xff0c;自己维护&#xff08;接入层交换机直接连接电脑、汇聚层交换机直接连接接入层交换机&#xff09; 广域网:距离远&#xff0c;花钱买服务&#xff0c;租带宽&…

单链表逆序的多种方式

https://www.cnblogs.com/eniac12/p/4860642.htmltemplate<class T> void List<T>::Inverse() {if(first NULL) return;LinkNode<T> *p, *prev, *latter; p first->link;   // 当前结点prev NULL;   // 前一结点l…

Linux系统【四】进程间通信-管道

进程间通信&#xff08;IPC Interprocess Communication&#xff09; 进程和进程之间的通信只能通过内核&#xff0c;在内核中提供一块缓冲区进行通信。内核提供的这种机制叫做IPC 在进程间完成数据传输需要借助操作系统提供的特殊方法&#xff0c;如&#xff1a;文件&#xf…

单链表各种操作详解

#include "stdio.h" #include "stdlib.h"#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0#define MAXSIZE 20 /* 存储空间初始分配量 */typedef int Status;/* Status是函数的类型,其值是函数结果状态代码&#xff0c;如OK等 */ typedef int…

Linux系统【五】进程间通信-共享内存mmap

mmap函数 #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);参数&#xff1a; void *addr建立映射区的首地址&#xff0c;由Linux内核指定&#xff0c;所以我们直接传递NULL。也就是说虽然这是一个参宿但是并不…

socket编程 -- epoll模型服务端/客户端通信的实现

https://blog.csdn.net/y396397735/article/details/50680359 本例实现如下功能&#xff1a; 支持多客户端与一个服务端进行通信&#xff0c;客户端给服务端发送字符串数据&#xff0c;服务端将字符串中小写转为大写后发送回客户端&#xff0c;客户端打印输出经转换后的字符串。…

Python3 面向对象程序设计

类的定义 Python使用class关键字来定义类 class Car:def infor(self):print("This is a car") car Car() car.infor()内置方法isinstance()来测试一个对象是否为某个类的实例 self参数 类的 所有实例方法都有一个默认的self参数&#xff0c;并且必须是方法的第一…

计算机网络【二】物理层基础知识

计算机网络的性能 速率&#xff1a;连接在计算机网络上的主机在数字信道上传送数据位数的速率&#xff0c;也成为data rate 或bit rate&#xff0c;单位是b/s,kb/s,Mb/s,Gb/s。 我们平时所讲的宽带的速度是以字为单位的&#xff0c;但是实际中应用一般显示的是字节 &#xff0…

Linux网络编程——tcp并发服务器(多进程)

https://blog.csdn.net/lianghe_work/article/details/46503895一、tcp并发服务器概述一个好的服务器,一般都是并发服务器&#xff08;同一时刻可以响应多个客户端的请求&#xff09;。并发服务器设计技术一般有&#xff1a;多进程服务器、多线程服务器、I/O复用服务器等。二、…

求序列第K大算法总结

参考博客&#xff1a;传送门 在上面的博客中介绍了求序列第K大的几种算法&#xff0c;感觉收益良多&#xff0c;其中最精巧的还是利用快速排序的思想O(n)查询的算法。仔细学习以后我将其中的几个实现了一下。 解法 1&#xff1a; 将乱序数组从大到小进行排序然后取出前K大&a…

Linux网络编程——tcp并发服务器(多线程)

https://blog.csdn.net/lianghe_work/article/details/46504243tcp多线程并发服务器多线程服务器是对多进程服务器的改进&#xff0c;由于多进程服务器在创建进程时要消耗较大的系统资源&#xff0c;所以用线程来取代进程&#xff0c;这样服务处理程序可以较快的创建。据统计&a…

计算机网络【三】物理层数据通信

物理层传输媒介 导向传输媒体&#xff0c;比如光纤和铜线 双绞线&#xff08;屏蔽双绞线STP 五屏蔽双绞线UTP&#xff09;电线扭曲在一起可以降低互相之间的电磁干扰 同轴电缆 (50欧姆的基带同轴电缆&#xff0c;75欧姆的宽带同轴电缆) 10M和100M网络只使用了四根线&#xf…

02_算法分析

02_算法分析 0.1 算法的时间复杂度分析0.1.1 函数渐近增长概念&#xff1a;输入规模n>2时&#xff0c;算法A1的渐近增长小于算法B1 的渐近增长随着输入规模的增大&#xff0c;算法的常数操作可以忽略不计测试二&#xff1a;随着输入规模的增大&#xff0c;与最高次项相乘的常…