白話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.pswp.cn/news/383708.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383708.shtml
英文地址,請注明出處:http://en.pswp.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;與最高次項相乘的常…