1. 程式人生 > >Effective C++:面向對象與繼承

Effective C++:面向對象與繼承

har 編程 caption 構造 bsp str 第一次 靜態 函數

在看《Effective C++》這本書的過程中,我無數次的發出感嘆,寫得太好了,句句一針見血,直接說到點上。下面序號代表書的章節號(原書更新時間不明確,實際請以原書為準)

技術分享圖片

1:子類不要覆寫父類的非虛函數。

為了解釋方便,先看一個簡單的例子。

class A

{

    public:

        A(int d):data(d){  }

 

        void print()

        {

            cout<<"A print..."<<data<<endl;

        }

 

        
virtual void test(int i=2) { cout<<"A test..."<<i<<endl; } private: int data; }; class B:public A { public : B(int d):A(d){ } void print() { cout<<"
B print..."<<endl; } virtual void test(int i=4) { cout<<"B test..."<<i<<endl; } }; //測試代碼 int main() { { B b(5); b.print(); A *a=&b; a->print(); cout
<<endl; b.test(); a->test(); cout<<endl; A a1=b; a1.test(); } getchar(); return 0; }

運行結果截圖:

技術分享圖片

例子中指針a是指向對象b的,但是他們調用的print方法卻不是同一個。這裏涉及到靜態綁定和動態綁定的問題。a的靜態類型是A,a的動態的類型卻是B,b的靜態類型和動態類型都是B,因為靜態類型就是申明時的類型,動態類型是其真正指向的類型。還有一點就是非虛方法是靜態綁定,虛擬方法是動態綁定。Print是非虛方法,它是靜態綁定,調用的是自己的對象申明類型的方法,所以a調用的是A的print,b調用的是B的print方法。我想我們更想知道C++是怎麽實現動態綁定。我們都知道含有虛方法的類都有一個虛擬方法表,每個對象的實例都有一個指針指向這個虛擬方法表,子類會繼承父類的virtual方法,也可以覆寫父類的虛擬方法,如果子類覆寫父類的虛擬方法,那麽在虛擬表中對應的指針就指向子類覆寫父類的方法,如果子類不覆寫父類的虛擬方法,則還是指向父類的方法,這樣就形成了動態綁定。不同的子類按照自己的方式覆寫父類的虛擬方法,表現出不同的行為這就是多態。在多重繼承中,每個對象可能有多個虛擬表,那麽它的實例就會有多個指向虛擬表的指針,如果多個父類有一個相同的方法,那麽你就不能直接用這個實例調用這個方法,因為編譯器根本不知道它該調用哪個方法,你要指定是那個父類的方法,當你指明了哪個父類,編譯就可以通過對應的指針調用對應的虛擬表中對應的方法。那麽實例調用虛擬方法的過程是怎麽樣的呢,你有沒有想過?其實上面也提到一點,大致三步:

1:根據對象的vptr指針找到其虛擬方法表vtbl;

2:找到被調用方法在vtbl中對應的指針;

3:調用2中指針指向的方法。

2:子類不要覆寫從父類繼承過來的默認參數

這一條其實還是涉及到靜態綁定和動態綁定的問題,關於這個問題我想上面已經說得比較清楚了,默認值也是靜態綁定,這是毫無疑問的,因為它在編譯期就已經確定了,而虛擬方法確實動態綁定,你把靜態綁定的東西和動態綁定的東西攪在一起沒有問題,但是你還有得寸進尺的在子類中覆寫靜態的東西就會出問題,對不起,父類不管子類中靜態的東西,它只管自己靜態的東西,所以當子類不要覆寫從父類繼承過來的默認參數時,子類就可能出現精神分裂的行為,上面那個列子就是證明。

上面更多提到的都是關於虛擬方法的,那麽非虛擬方法呢,對象實例時怎麽調用非虛擬方法的呢?非虛擬方法是怎麽實現的呢?非虛擬方法就像一般的C函數那樣被實現的,所以他們的調用不需要像虛擬方法一樣先要找到一個指針,然後在通過這個指針調用對應的方法。

3:子類與父類之間的賦值問題

首先將父類轉換成子類的事最好不要做,因為子類的很多特性父類根本沒有,當你把一個從父類轉換過來的子類,當做子類來用的話,很可能出問題。接下來我們重點討論將子類轉換成父類。還是通過上面例子來說明問題。

B b(2);

A a=b;//調用copy constructor

a=b;//調用 operator=

上面兩行代碼,第一行先實例化了一個對象b,第二行將b賦給a,那麽是怎麽將b賦給a的呢,這裏其實調用的不是operator=,而是copy constructor,因為構造一個對象必須調用constructor,或是copy constructor,那麽這裏肯定是調用copy constructor,operator=只是一個賦值動作,一個對象還沒有構造出來怎麽給他賦值呢,在operator=可不是用來幫你構造對象的哦,在第三行的時候a已經被構造出來了,那麽這裏真的就是賦值了調用的就是operator=。總之一句話,一個對象作為左值時,第一次肯定調用的是copy constructor,被初始化後(分配了內存),之後的操作才是賦值。一個對象作為by value形式的參數,那麽每次調用的都是copy constructor,而不是operator=,我們一般都會說將實參賦給形參,其實是用實參構造一個形參。

將b賦給a,就是將b的A部分賦給a,a就是一個完全的A了,它對B一無所知,更不會表現出B的任何行為,所以by value是很暴力並且很耗性能的,也不會出現多態的行為。所以要避免使用by value,盡量用by reference。

博主是一個有著7年工作經驗的架構師,對於c++,自己有做資料的整合,一個完整學習C語言c++的路線,學習資料和工具。可以進我的Q群7418,18652領取,免費送給大家。希望你也能憑自己的努力,成為下一個優秀的程序員!另外博主的微信公眾號是:C語言編程學習基地,歡迎關註!

Effective C++:面向對象與繼承