程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++筆記:面向對象編程(Visual)

C++筆記:面向對象編程(Visual)

編輯:C++入門知識

面向對象編程(Visual)

  • 在C++中,基類必須指出希望派生類重寫哪些函數,定義為virtual的函數是基類期待派生類重新定義的,基類希望派生類繼承的函數不能定義為虛函數。
  • 在C++中,通過基類的引用(或指針)調用虛函數時,發生動態綁定。引用(或指針)既可以指向基類對象也可以指向派生類對象,這一事實是動態綁定的關鍵。用引用(或指針)調用的虛函數在運行時確定,被調用的函數是引用(或指針)所指對象的實際類型所定義的。
  • 保留字virtual的目的是啟用動態綁定。成員默認為非虛函數,對非虛函數的調用在編譯時確定。
  • 為了指明函數為虛函數,在其返回類型前面加上保留字virtual。除了構造函數之外,任意非static成員函數都可以是虛函數
  • 基類通常應將派生類需要重定義的任意函數定義為虛函數。
  • 盡管不是必須這樣做,派生類一般會重定義所繼承的虛函數。派生類沒有重定義某個虛函數,則使用基類中定義
  • 派生類中虛函數的聲明必須與基類中的定義方式完全匹配, 但有一個例外:返回對基類型的引用(或指針)的虛函數。派生類中的虛函數可以返回基類函數所返回類型的派生類的引用(或指針)。
  • 一旦函數在基類中聲明為虛函數,它就一直為虛函數,派生類無法改變該函數為虛函數這一事實。派生類重定義虛函數時,可以使用virtual保留字,但不是必須這樣做。


    virtual與其他成員函數

    • C++中的函數調用默認不使用動態綁定,觸發動態綁定條件:
      • 只有指定為虛函數的成員函數才能進行動態綁定
      • 必須通過基類類型的引用或指針進行函數調用
      • 因為每個派生類對象都包含基類部分,所以可將基類類型的引用綁定到派生類對象的基類部分,也可以用指向基類的指針指向派生類對象。
      • 使用基類類型的引用或指針時,不知道指針或引用所綁定的對象的類型,編譯器都將它當作基類類型對象
      • 基類類型引用和指針的關鍵點在於靜態類型(在編譯時可知的引用類型或指針類型)和動態類型(指針或引用所綁定的對象的類型這是僅在運行時可知的)可能不同。
      • 非虛函數總是在編譯時根據調用該函數的對象、引用或指針的類型而確定
      • 覆蓋虛函數機制
        • 使用作用域操作符強制函數調用使用虛函數的特定版本[Code1]
        • 在派生類中虛函數調用基類版本時,必須顯式使用作用域操作符,如果派生類函數忽略了這樣做,則函數調用會在運行時確定並且將是一個自身調用,從而導致無窮遞歸
        • 虛函數與默認實參
          • 像其他任何函數一樣,虛函數也可以有默認實參,如果有用在給定調用中的默認實參值,該值將在編譯時確定
          • 如果一個調用省略了具有默認值的實參,則所用的值由調用該函數的類型定義,與對象的動態類型無關
          • 通過基類的引用或指針調用虛函數時,默認實參為在基類虛函數聲明中指定的值,如果通過派生類的指針或引用調用虛函數,則默認實參是在派生類的版本中聲明的值
          • 在同一虛函數的基類版本和派生類版本中使用不同的默認實參幾乎一定會引起麻煩。

            為什麼會希望覆蓋虛函數機制?
            最常見的理由是為了派生類虛函數調用基類中的版本。在這種情況下,基類版本可以完成繼承層次中所有類型的公共任務,而每個派生類型只添加自己的特殊工作。
            例如,可以定義一個具有虛操作的Camera類層次。Camera類中的display函數可以顯示所有的公共信息,派生類(如PerspectiveCamera)可能既需要顯示公共信息又需要顯示自己的獨特信息。可以顯式調用Camera版本以顯示公共信息,而不是在PerspectiveCamera的display實現中復制Camera的操作。 在這種情況下,已經確切知道調用哪個實例,因此,不需要通過虛函數機制。


            虛析構函數

            • 處理繼承層次中的對象時,指針的靜態類型可能與被刪除對象的動態類型不同,可能會刪除實際指向派生類對象的基類類型指針。
            • 如果刪除基類指針,則需要運行基類析構函數並清除基類的成員,如果對象實際是派生類型的,則沒有定義該行為。要保證運行適當的析構函數,基類中的析構函數必須為虛函數。[Code2]
            • 如果層次中根類的析構函數為虛函數,則派生類析構函數也將是虛函數,無論派生類顯式定義析構函數還是使用合成析構函數,派生類析構函數都是虛函數。
            • 即使析構函數沒有工作要做,繼承層次的根類最好也應該定義一個虛析構函數。
            • 在復制控制成員中,只有析構函數應定義為虛函數,構造函數不能定義為虛函數。構造函數是在對象完全構造之前運行的,在構造函數運行的時候,對象的動態類型還不完整。
            • 將類的賦值操作符設為虛函數很可能會令人混淆,而且不會有什麼用處。
              • 雖然可以在基類中將成員函數 operator= 定義為虛函數,但這樣做並不影響派生類中使用的賦值操作符。
              • 每個類有自己的賦值操作符,派生類中的賦值操作符有一個與類本身類型相同的形參,該類型必須不同於繼承層次中任意其他類的賦值操作符的形參類型。
              • 如果該操作符為虛函數,則每個類都將得到一個虛函數成員,該成員定義了參數為一個基類對象的 operator= 。但是,對派生類而言,這個操作符與賦值操作符是不同的。


                構造函數和析構函數中的虛函數

                • 盡量不要在構造函數和析構函數中調用虛函數,因為構造或析構期間的對象類型對虛函數的綁定有影響。
                  • 基類構造函數或析構函數中,將派生類對象當作基類類型對象對待。
                    • 構造派生類對象時首先運行基類構造函數初始化對象的基類部分。在執行基類構造函數時,對象的派生類部分是未初始化的。實際上,此時對象還不是一個派生類對象。
                    • 撤銷派生類對象時,首先撤銷它的派生類部分,然後按照與構造順序的逆序撤銷它的基類部分。 在這兩種情況下,運行構造函數或析構函數的時候,對象都是不完整的。
                    • 為了適應這種不完整,編譯器將對象的類型視為在構造或析構期間發生了變化。在基類構造函數或析構函數中,將派生類對象當作基類類型對象對待。
                    • 如果在構造函數或析構函數中調用虛函數,則運行的是為構造函數或析構函數自身類型定義的版本


                      虛函數與作用域

                      • 要獲得動態綁定,必須通過基類的引用或指針調用虛成員。當我們這樣做時,編譯器器將在基類中查找函數。假定找到了名字,編譯器就檢查實參是否與形參匹配。
                      • 虛函數必須在基類和派生類中擁有同一原型。如果基類成員與派生類成員接受的實參不同,就沒有辦法通過基類類型的引用或指針調用派生類函數。[Code3]
                      • 通過基類調用被屏蔽的虛函數。[Code4]
                      • 名字查找與繼承
                        • 首先確定進行函數調用的對象、引用或指針的靜態類型。
                        • 在該類中查找函數,如果找不到,就在直接基類中查找,如此循著類的繼承鏈往上找,直到找到該函數或者查找完最後一個類。 如果不能在類或其相關基類中找到該名字,則調用是錯誤的。
                        • 一旦找到了該名字,就進行常規類型檢查,查看如果給定找到的定義,該函數調用是否合法。
                        • 假定函數調用合法,編譯器就生成代碼。如果函數是虛函數且通過引用或指針調用,則編譯器生成代碼以確定根據對象的動態類型運行哪個函數版本,否則,編譯器生成代碼直接調用函數。


                          純虛函數

                          • 含有(或繼承)一個或多個純虛函數的類是抽象基類。除了作為抽象基類的派生類的對象的組成部分,不能創建抽象類型的對象
                          • 純虛函數為後代類型提供了可以覆蓋的接口,但是這個類中的版本決不會調用。重要的是,用戶將不能創建對象象。
                          • 純虛函數的定義:在函數形參表後面寫上 = 0,double net_price(std::size_t) const = 0;


                            虛繼承

                            • 虛繼承是一種機制,類通過虛繼承指出它希望共享其虛基類的狀態。在虛繼承下,對給定虛基類,無論該類在派生層次中作為虛基類出現多少次,只繼承一個共享的基類子對象。共享的基類子對象稱為虛基類。[Code5]
                            • 指定虛派生只影響從指定了虛基類的類派生的類。除了影響派生類自己的對象之外,它也是關於派生類與自己的未來派生類的關系的一個陳述。
                            • 任何可被指定為基類的類也可以被指定為虛基類,虛基類可以包含通常由非虛基類支持的任意類元素。虛繼承支持到基類到常規轉換。
                            • 使用虛基類的多重繼承層次比沒有虛繼承的引起更少的二義性問題
                            • 虛基類成員的可見性,假定通過多個派生路徑繼承名為 X 的成員
                              • 如果在每個路徑中 X 表示同一虛基類成員,則沒有二義性,因為共享該成員的單個實例。
                              • 如果在某個路徑中 X 是虛基類的成員,而在另一路徑中 X 是後代派生類的成員,也沒有二義性——特定派生類實例的優先級高於共享虛基類實例。
                              • 如果沿每個繼承路徑 X 表示後代派生類的不同成員,則該成員的直接訪問是二義性的。

                                Code

                                Code1:覆蓋虛函數機制

                                Item_base *baseP = &derived;
                                 //calls version from the base class regardless of the dynamic type of baseP
                                 double d = baseP->Item_base::net_price(42);

                                Code2:虛析構函數

                                /*如果析構函數為虛函數,那麼通過指針調用時,運行哪個析構函數將因指針所指對象類型的不同而不同:*/
                                Item_base *itemP = new Item_base; // same static and dynamic type 
                                delete itemP; // ok: destructor for Item_base called 
                                itemP = new Bulk_item; // ok: static and dynamic types differ 
                                delete itemP; // ok: destructor for Bulk_item called
                                

                                Code3:虛函數必須在基類和派生類中擁有同一原型

                                class Base {
                                 public:
                                     virtual int fcn();
                                 };
                                 class D1 : public Base {
                                 public:
                                      // hides fcn in the base; this fcn is not virtual
                                      int fcn(int); // parameter list differs from fcn in Base
                                 // D1 inherits definition of Base::fcn()
                                 };
                                 class D2 : public D1 {
                                 public:
                                     int fcn(int); // nonvirtual function hides D1::fcn(int)
                                     int fcn();    // redefines virtual fcn from Base
                                 };
                                /*D1 中的 fcn 版本沒有重定義 Base 的虛函數 fcn,相反,它屏蔽了基類的 fcn。結果 D1 有兩個名   為 fcn 的函數:類從 Base 繼承了一個名為 fcn 的虛 函數,類又定義了自己的名為 fcn 的非虛成員函   數,該函數接受一個 int 形參。 但是,從 Base 繼承的虛函數不能通過 D1 對象(或 D1 的引用或指針) 調用, 因為該函數被 fcn(int) 的定義屏蔽了。
                                類 D2 重定義了它繼承的兩個函數,它重定義了 Base 中定義的 fcn 的原始版本並重定義了 D1 中定義 的非虛版本。*/

                                Code4:通過基類調用被屏蔽的虛函數

                                Base bobj;  D1 d1obj;  D2 d2obj;
                                Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
                                bp1->fcn();
                                bp2->fcn();
                                bp3->fcn();
                                // ok: virtual call, will call Base::fcnat run time // ok: virtual call, will   call Base::fcnat run time // ok: virtual call, will call D2::fcnat run time
                                三個指針都是基類類型的指針,因此通過在 Base 中查找 fcn 來確定這三 個調用,所以這些調用是合法    的。另外,因為 fcn 是虛函數,所以編譯器會生成代碼,在運行時基於引用指針所綁定的對象的實際類型進    行調用。在 bp2 的情況,基本對象是 D1 類的,D1 類沒有重定義不接受實參的虛函數版本,通過 bp2 的 函數調用(在運行時)調用 Base 中定義的版本。

                                Code5:虛繼承

                                /*每個 IO 庫類都繼承了一個共同的抽象基類,那個抽象基類管理流的條件狀態並保存流所讀寫的緩沖區。    istream 和 ostream 類直接繼承這個公共基類,庫定義了另一個名為 iostream 的類,它同時繼承   istream 和 ostream,iostream 類既可以對流進行讀又可以對流進行寫。
                                我們知道,多重繼承的類從它的每個父類繼承狀態和動作,如果 IO 類 型使用常規繼承,則每個 iostream    對象可能包含兩個 ios 子對象:一個包含 在它的 istream 子對象中,另一個包含在它的 ostream 子對  象中,從設計角度講,這個實現正是錯誤的:iostream 類想要對單個緩沖區進行讀和寫,它希望跨越輸入和輸   出操作符共享條件狀態。如果有兩個單獨的 ios 對象,這種共享是不可能的。
                                在 C++ 中,通過使用虛繼承解決這類問題。*/
                                class istream : public virtual ios { ... };
                                class ostream : virtual public ios { ... };
                                // iostream inherits only one copy of its ios base class
                                class iostream: public istream, public ostream { ... };
                                

                                From:
                                http://blog.csdn.net/liufei_learning/article/details/22708639



  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved