1. 程式人生 > >C++多型的概念以及用途(通俗易懂)

C++多型的概念以及用途(通俗易懂)

基類的指標也可以指向派生類物件,請看下面的例子:
  1. #include <iostream>
  2. using namespace std;
  3. //基類People
  4. classPeople{
  5. public:
  6. People(char *name, int age);
  7. void display();
  8. protected:
  9. char *m_name;
  10. int m_age;
  11. };
  12. People::People(char *name, int age): m_name(name), m_age(age){}
  13. void People::display(){
  14. cout<<m_name<<
    "今年"<<m_age<<"歲了,是個無業遊民。"<<endl;
  15. }
  16. //派生類Teacher
  17. classTeacher: public People{
  18. public:
  19. Teacher(char *name, int age, int salary);
  20. void display();
  21. private:
  22. int m_salary;
  23. };
  24. Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
  25. void Teacher::display(){
  26. cout<<
    m_name<<"今年"<<m_age<<"歲了,是一名教師,每月有"<<m_salary<<"元的收入。"<<endl;
  27. }
  28. int main(){
  29. People*p = new People("王志剛", 23);
  30. p -> display();
  31. p = new Teacher("趙巨集佳", 45, 8200);
  32. p -> display();
  33. return 0;
  34. }
執行結果:
王志剛今年23歲了,是個無業遊民。
趙巨集佳今年45歲了,是個無業遊民。

我們直觀上認為,如果指標指向了派生類物件,那麼就應該使用派生類的成員變數和成員函式,這符合人們的思維習慣。但是本例的執行結果卻告訴我們,當基類指標 p 指向派生類 Teacher 的物件時,雖然使用了 Teacher 的成員變數,但是卻沒有使用它的成員函式,導致輸出結果不倫不類(趙巨集佳本來是一名老師,輸出結果卻顯示人家是個無業遊民),不符合我們的預期。


換句話說,通過基類指標只能訪問派生類的成員變數,但是不能訪問派生類的成員函式。

為了消除這種尷尬,讓基類指標能夠訪問派生類的成員函式,C++ 增加了虛擬函式(Virtual Function)。使用虛擬函式非常簡單,只需要在函式宣告前面增加 virtual 關鍵字。

更改上面的程式碼,將 display() 宣告為虛擬函式:
  1. #include <iostream>
  2. using namespace std;
  3. //基類People
  4. classPeople{
  5. public:
  6. People(char *name, int age);
  7. virtual void display(); //宣告為虛擬函式
  8. protected:
  9. char *m_name;
  10. int m_age;
  11. };
  12. People::People(char *name, int age): m_name(name), m_age(age){}
  13. void People::display(){
  14. cout<<m_name<<"今年"<<m_age<<"歲了,是個無業遊民。"<<endl;
  15. }
  16. //派生類Teacher
  17. classTeacher: public People{
  18. public:
  19. Teacher(char *name, int age, int salary);
  20. virtual void display(); //宣告為虛擬函式
  21. private:
  22. int m_salary;
  23. };
  24. Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
  25. void Teacher::display(){
  26. cout<<m_name<<"今年"<<m_age<<"歲了,是一名教師,每月有"<<m_salary<<"元的收入。"<<endl;
  27. }
  28. int main(){
  29. People*p = new People("王志剛", 23);
  30. p -> display();
  31. p = new Teacher("趙巨集佳", 45, 8200);
  32. p -> display();
  33. return 0;
  34. }
執行結果:
王志剛今年23歲了,是個無業遊民。
趙巨集佳今年45歲了,是一名教師,每月有8200元的收入。

和前面的例子相比,本例僅僅是在 display() 函式宣告前加了一個virtual關鍵字,將成員函式宣告為了虛擬函式(Virtual Function),這樣就可以通過 p 指標呼叫 Teacher 類的成員函數了,執行結果也證明了這一點(趙巨集佳已經是一名老師了,不再是無業遊民了)。

有了虛擬函式,基類指標指向基類物件時就使用基類的成員(包括成員函式和成員變數),指向派生類物件時就使用派生類的成員。換句話說,基類指標可以按照基類的方式來做事,也可以按照派生類的方式來做事,它有多種形態,或者說有多種表現方式,我們將這種現象稱為多型(Polymorphism)。

上面的程式碼中,同樣是p->display();這條語句,當 p 指向不同的物件時,它執行的操作是不一樣的。同一條語句可以執行不同的操作,看起來有不同表現方式,這就是多型。

多型是面向物件程式設計的主要特徵之一,C++中虛擬函式的唯一用處就是構成多型。

C++提供多型的目的是:可以通過基類指標對所有派生類(包括直接派生和間接派生)的成員變數和成員函式進行“全方位”的訪問,尤其是成員函式。如果沒有多型,我們只能訪問成員變數。

前面我們說過,通過指標呼叫普通的成員函式時會根據指標的型別(通過哪個類定義的指標)來判斷呼叫哪個類的成員函式,但是通過本節的分析可以發現,這種說法並不適用於虛擬函式,虛擬函式是根據指標的指向來呼叫的,指標指向哪個類的物件就呼叫哪個類的虛擬函式。

但是話又說回來,物件的記憶體模型是非常乾淨的,沒有包含任何成員函式的資訊,編譯器究竟是根據什麼找到了成員函式呢?我們將在《C++虛擬函式表vtable以及動態繫結》一節中給出答案。

藉助引用也可以實現多型

引用在本質上是通過指標的方式實現的,這一點已在《引用在本質上是什麼,它和指標到底有什麼區別》中進行了講解,既然藉助指標可以實現多型,那麼我們就有理由推斷:藉助引用也可以實現多型。

修改上例中 main() 函式內部的程式碼,用引用取代指標:
  1. int main(){
  2. Peoplep("王志剛", 23);
  3. Teachert("趙巨集佳", 45, 8200);
  4. People&rp = p;
  5. People&rt = t;
  6. rp.display();
  7. rt.display();
  8. return 0;
  9. }
執行結果:
王志剛今年23歲了,是個無業遊民。
趙巨集佳今年45歲了,是一名教師,每月有8200元的收入。

由於引用類似於常量,只能在定義的同時初始化,並且以後也要從一而終,不能再引用其他資料,所以本例中必須要定義兩個引用變數,一個用來引用基類物件,一個用來引用派生類物件。從執行結果可以看出,當基類的引用指代基類物件時,呼叫的是基類的成員,而指代派生類物件時,呼叫的是派生類的成員。

不過引用不像指標靈活,指標可以隨時改變指向,而引用只能指代固定的物件,在多型性方面缺乏表現力,所以以後我們再談及多型時一般是說指標。本例的主要目的是讓讀者知道,除了指標,引用也可以實現多型。

多型的用途

通過上面的例子讀者可能還未發現多型的用途,不過確實也是,多型在小專案中鮮有有用武之地。

接下來的例子中,我們假設你正在玩一款軍事遊戲,敵人突然發動了地面戰爭,於是你命令陸軍、空軍及其所有現役裝備進入作戰狀態。具體的程式碼如下所示:
  1. #include <iostream>
  2. using namespace std;
  3. //軍隊
  4. classTroops{
  5. public:
  6. virtual void fight(){ cout<<"Strike back!"<<endl; }
  7. };
  8. //陸軍
  9. classArmy: public Troops{
  10. public:
  11. void fight(){ cout<<"--Army is fighting!"<<endl; }
  12. };
  13. //99A主戰坦克
  14. class_99A: public Army{
  15. public:
  16. void fight(){ cout<<"----99A(Tank) is fighting!"<<endl; }
  17. };
  18. //武直10武裝直升機
  19. classWZ_10: public Army{
  20. public:
  21. void fight(){ cout<<"----WZ-10(Helicopter) is fighting!"<<endl; }
  22. };
  23. //長劍10巡航導彈
  24. classCJ_10: public Army{
  25. public:
  26. void fight(){ cout<<"----CJ-10(Missile) is fighting!"<<endl; }
  27. };
  28. //空軍
  29. classAirForce: public Troops{
  30. public:
  31. void fight(){ cout<<"--AirForce is fighting!"<<endl; }
  32. };
  33. //J-20隱形殲擊機
  34. classJ_20: public AirForce{
  35. public:
  36. void fight(){ cout<<"----J-20(Fighter Plane) is fighting!"<<endl; }
  37. };
  38. //CH5無人機
  39. classCH_5: public AirForce{
  40. public:
  41. void fight(){ cout<<"----CH-5(UAV) is fighting!"<<endl; }
  42. };
  43. //轟6K轟炸機
  44. classH_6K: public AirForce{
  45. public:
  46. void fight(){ cout<<"----H-6K(Bomber) is fighting!"<<endl; }
  47. };
  48. int main(){
  49. Troops*p = new Troops;
  50. p ->fight();
  51. //陸軍
  52. p = new Army;
  53. p ->fight();
  54. p = new _99A;
  55. p -> fight();
  56. p = new WZ_10;
  57. p -> fight();
  58. p = new CJ_10;
  59. p -> fight();
  60. //空軍
  61. p =

    相關推薦

    C++概念以及用途通俗易懂

    基類的指標也可以指向派生類物件,請看下面的例子: #include <iostream>using namespace std;//基類PeopleclassPeople{public: People(char *name, int age);

    C++呼叫實現原理虛擬函式表詳解)

    1.帶有虛擬函式的基類物件模型 我們先看段程式碼: #include<iostream> using namespace std; class B1 { public: void func1() {} int _b; }; class B2 { pub

    的典型例子向上轉型

    /*多型的例子*/ abstract class Animal{abstract void eat();abstract void sleep(); } class Dog extends Animal{void eat(){System.out.println("豬吃飼料

    C++中類與物件的講解通俗易懂

    #include<iostream>usingnamespace std;classBox{public:staticint objectCount;// 建構函式定義Box(double l=2.0,double b=2.0,double h=2.0){ cout <&l

    7-8 哈利·波特的考試 25 分Floyedc++描述,超詳細註釋,通俗易懂

    哈利·波特要考試了,他需要你的幫助。這門課學的是用魔咒將一種動物變成另一種動物的本事。例如將貓變成老鼠的魔咒是haha,將老鼠變成魚的魔咒是hehe等等。反方向變化的魔咒就是簡單地將原來的魔咒倒過來念,例如ahah可以將老鼠變成貓。另外,如果想把貓變成魚,可以通過念一個直接魔咒lalala,也可以將

    Spring AOP概念理解 通俗易懂

    源地址:http://www.verydemo.com/demo_c143_i20837.html  1.我所知道的aop  初看aop,上來就是一大堆術語,而且還有個拉風的名字,面向切面程式設計,都說是OOP的一種有益補充等等。一下子讓你不知所措,心想著:怪不得很多人都和我

    c#執行緒使用IOCP完成埠的簡單示例

    在c#使用IOCP(完成埠)的簡單示例 上次給大家發了利用winsock原生的api來做一個同步的socket伺服器的例子,大致上只是貼了一些程式碼,相信大家這麼冰雪聰明,已經研究的差不多了。因為winsock的api使用在msdn或者google上都能很方便的查到,所以我

    C++的 RTTI 觀念和用途非常詳細

    自從1993年Bjarne Stroustrup 〔注1 〕提出有關C++ 的RTTI功能之建議﹐以及C++的異常處理(exception handling)需要RTTI;最近新推出的C++ 或多或少已提供RTTI。 然而,若不小心使用RTTI,可能會導致軟體彈性的降低。本文

    HDFS檔案讀寫操作 通俗易懂

    首先來介紹兩個概念 ▪NameNode:領導級別。管 NameNode:領導級別。管理資料塊對映;處理客戶端的讀寫請求;配置副本策略;管理HDFS的名稱空間; DataNode:員工級別。負責儲存客戶端發來的資料塊block;執行資料塊的讀寫操作。 理資料 寫詳細步驟: 1、首先

    傅立葉變換的意義和理解通俗易懂

    這篇文章的核心思想就是:要讓讀者在不看任何數學公式的情況下理解傅立葉分析。 傅立葉分析不僅僅是一個數學工具,更是一種可以徹底顛覆一個人以前世界觀的思維模式。但不幸的是,傅立葉分析的公式看起來太複雜了,所以很多大一新生上來就懵圈並從此對它深惡痛絕。老實說,這麼有意思的東西居然成了大學裡的殺

    SIR疾病傳播模型理論通俗易懂

    youtube上看的視訊,然後截下圖做的筆記。 視訊中用的是一個學校,兒童爆發麻疹的的例子。 S是易感染的人群(即目前是健康,未感染的人), I是感染了的人群 R是感染後好了的,被移除了的人。(即不會再被感染) 他們各自的速率 由這張圖可以很清楚地看到他們的關

    資料庫三大正規化詳解通俗易懂

    ◆ 第一正規化(1NF):     強調的是列的原子性,即列不能夠再分成其他幾列。考慮這樣一個表:【聯絡人】(姓名,性別,電話)如果在實際場景中,一個聯絡人有家庭電話和公司電話,那麼這種表結構設計就沒有達到 1NF。要符合 1NF 我們只需把列(電話)拆分,即:【聯絡人】(

    什麼是分散式系統通俗易懂

    最近做了一些分散式的專案,但還沒有真正的理解和認識什麼是分散式,以及為什麼要這麼設計等等一系統問題,在看過大神的貼子了,如夢初醒,受益匪淺! 著作權歸作者所有。 商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 作者:林建入 連結:http://www.zhihu.c

    JavaScript 觀察者模式 通俗易懂

    觀察者模式又叫做釋出-訂閱模式。這是一種一對多的物件依賴關係,當被依賴的物件的狀態發生改變時,所有依賴於它的物件都將得到通知。 生活中的觀察者模式 就如我們在專賣店預定商品(如:蘋果手機),我們會向專賣店提交預定申請,然後店家受申請,正常這樣就完事了。假如,近段時間蘋果手機的需求

    websocket 簡介通俗易懂

    偶然在知乎上看到一篇回帖,瞬間覺得之前看的那麼多資料都不及這一篇回帖讓我對 websocket 的認識深刻有木有。所以轉到我部落格裡,分享一下。比較喜歡看這種部落格,讀起來很輕鬆,不枯燥,沒有佈道師的陣仗,純粹為分享。廢話這麼多了,最後再贊一個~ 一、websocket與h

    關於java中的鎖的理解通俗易懂

    轉載自:http://blog.csdn.net/u012291108/article/details/51348603 一段synchronized的程式碼被一個執行緒執行之前,他要先拿到執行這段程式碼的許可權,在Java裡邊就是拿到某個同步物件的鎖(一個物件只有一把鎖); 如果

    epoll底層實現原理通俗易懂

    2013-10-27更新:由於此文陸陸續續收到贊同,而且其中有些地方並不完全正確,特在本文最後予以訂正 我不瞭解樓主的層次,我必須從很多基礎的概念開始構建這個答案,並且可能引申到很多別的問題。 首先我們來定義流的概念,一個流可以是檔案,socket,pipe等等可以進行I/O操作的核心物件。 不管是檔案,還是

    我徹底服了,大牛講解訊號與系統通俗易懂

    我徹底服了,大牛講解訊號與系統(通俗易懂) | 貿澤工程師社群 http://mouser.eetrend.com/content/2018/100011813.html 引子 很多朋友和我一樣,工科電子類專業,學了一堆訊號方面的課,什麼都沒學懂,背了公式考了試,然後畢業了。 先說"卷積

    執行緒同步之互斥物件通俗易懂

         先看售票系統的程式,看看多執行緒容易出什麼問題: #include <windows.h> #include <iostream.h> DWORD WINAPI Fun1Proc(LPVOID lpParameter); DWORD WI

    如何從零開始搭建自己的部落格通俗易懂

    序 作為一名合(zhuang)格(bi)的程式猿,經常寫點東西,肯定少不了各種網站、部落格,通俗的CSDN,文藝的簡書,強大的GitHub,以及微信、掘金、知乎等等風格迥異的平臺。不過,再多的地方,也容不下一顆想捯飭的心,不管什麼網站,都有自己固定的模板,統一的風格,這怎麼