轉載內容。另有其他更好的轉載參考資料,在博文最末尾。

    面向對象編程語言中的多重繼承指的是一個類別可以同時從多于一個父類繼承行為與特征的功能。與單一繼承相對,單一繼承指一個類別只可以繼承自一個父類。
    重溫Java,發現Java竟然不支持類多重繼承(直接繼承類),卻允許接口的多重繼承。。
    C++中類可以多重繼承,Java中為什么不實現這個功能呢?多重繼承會帶來哪些問題,從而導致Java放棄類的多重繼承?
    再一深究,發現多年以來,多重繼承一直是個敏感話題,贊成者看到的是免去笨拙的混合繼承的利處,反對者看到的是多處混淆的弊端,例如變量的二義性,并且是多個變量。所以關于它的好處與風險之間孰輕孰重成為OO界多年爭論的焦點。
    其實最大的問題是出現拓補圖,也就是出現鉆石型繼承結構(DOD),個人感覺這是個致命傷。正如其名:Diamond of Death。
 
舉個簡單的例子:
    比如一個基類:動物。它有三個派生類:哺乳類動物,卡通類動物,寵物(確實都形成ISA關系)。現在有一個子類貓,從關系上推,它可以繼承自哺乳類,卡通類,寵物,都符合ISA,如果要體現所有的特性,就需要全部繼承,這樣就形成了多重繼承,卻也形成了DOD,這樣以后問題就出現了,從貓到動物的繼承有三條路徑,如果哺乳類,卡通類與寵物類中有相同的成員函數或變量,這樣的數據組織方式會形成多義。
 
    C++怎么解決這個問題的呢?虛繼承。結果就是不得不犧牲一些內存開銷,因為一個功能要在多處被重寫。并且函數表里的函數指針必須調整,這樣即使可以滿足功能,在后期的維護也很復雜。
    所以,Java才會采用這樣折中的方法,硬生生的將類多重繼承題了出去。
    并且,網上也有不少建議,要盡可能避免多重繼承,不惜一切代價去避免鉆石結構,以避免后期不可挽回的大返工。
 
 
    多重繼承的概念:C++允許為一個派生類指定多個基類,這樣的繼承結構被稱做多重繼承
    舉個例子,交通工具類可以派生出汽車和船連個子類,但擁有汽車和船共同特性水陸兩用汽車就必須繼承來自汽車類與船類的共同屬性。
  由此我們不難想出如下的圖例與代碼:

當一個派生類要使用多重繼承的時候,必須在派生類名和冒號之后列出所有基類的類名,并用逗好分隔。 

 

//程序作者:管寧   
//站點:www.cndev-lab.com   
//所有稿件均有版權,如要轉載,請務必著名出處和作者   
 
#include <iostream> 
using namespace std; 
 
class Vehicle 

    public: 
        Vehicle(int weight 0) 
        
            Vehicle::weight weight; 
        
        void SetWeight(int weight) 
        
            cout<<"重新設置重量"<<endl; 
            Vehicle::weight weight; 
        
        virtual void ShowMe() 0; 
    protected: 
        int weight; 
}; 
class Car:public Vehicle//汽車 

    public: 
        Car(int weight=0,int aird=0):Vehicle(weight) 
        
            Car::aird aird; 
        
        void ShowMe() 
        
            cout<<"我是汽車!"<<endl; 
        
    protected: 
        int aird; 
}; 
 
class Boat:public Vehicle//船 

    public: 
        Boat(int weight=0,float tonnage=0):Vehicle(weight) 
        
            Boat::tonnage tonnage; 
        
        void ShowMe() 
        
            cout<<"我是船!"<<endl; 
        
    protected: 
        float tonnage; 
}; 
 
class AmphibianCar:public Car,public Boat//水陸兩用汽車,多重繼承的體現 

    public: 
        AmphibianCar(int weight,int aird,float tonnage) 
        :Vehicle(weight),Car(weight,aird),Boat(weight,tonnage) 
        //多重繼承要注意調用基類構造函數 
        
         
        
        void ShowMe() 
        
            cout<<"我是水陸兩用汽車!"<<endl; 
        
}; 
int main() 

    AmphibianCar a(4,200,1.35f);//錯誤 
    a.SetWeight(3);//錯誤 
    system("pause");  
}

  上面的代碼從表面看,看不出有明顯的語法錯誤,但是它是不能夠通過編譯的。這有是為什么呢?
  這是由于多重繼承帶來的繼承的模糊性帶來的問題。

    先看如下的圖示:

 

  在圖中深紅色標記出來的地方正是主要問題所在,水陸兩用汽車類繼承了來自Car類與Boat類的屬性與方法,Car類與Boat類同為AmphibianCar類的基類,在內存分配上AmphibianCar獲得了來自兩個類的SetWeight()成員函數,當我們調用a.SetWeight(3)的時候計算機不知道如何選擇分別屬于兩個基類的被重復擁有了的類成員函數SetWeight()。

  由于這種模糊問題的存在同樣也導致了AmphibianCar a(4,200,1.35f);執行失敗,系統會產生Vehicle”不是基或成員的錯誤。

  以上面的代碼為例,我們要想讓AmphibianCar類既獲得一個Vehicle的拷貝,而且又同時共享用Car類與Boat類的數據成員與成員函數就必須通過C++所提供的虛擬繼承技術來實現。

  我們在Car類和Boat類繼承Vehicle類出,在前面加上virtual關鍵字就可以實現虛擬繼承,使用虛擬繼承后,當系統碰到多重繼承的時候就會自動先加入一個Vehicle的拷貝,當再次請求一個Vehicle的拷貝的時候就會被忽略,保證繼承類成員函數的唯一性。
  修改后的代碼如下,注意觀察變化:

//程序作者:管寧   
//站點:www.cndev-lab.com   
//所有稿件均有版權,如要轉載,請務必著名出處和作者   
 
#include <iostream> 
using namespace std; 
 
class Vehicle 

    public: 
        Vehicle(int weight 0) 
        
            Vehicle::weight weight; 
            cout<<"載入Vehicle類構造函數"<<endl; 
        
        void SetWeight(int weight) 
        
            cout<<"重新設置重量"<<endl; 
            Vehicle::weight weight; 
        
        virtual void ShowMe() 0; 
    protected: 
        int weight; 
}; 
class Car:virtual public Vehicle//汽車,這里是虛擬繼承 

    public: 
        Car(int weight=0,int aird=0):Vehicle(weight) 
        
            Car::aird aird; 
            cout<<"載入Car類構造函數"<<endl; 
        
        void ShowMe() 
        
            cout<<"我是汽車!"<<endl; 
        
    protected: 
        int aird; 
}; 
 
class Boat:virtual public Vehicle//船,這里是虛擬繼承 

    public: 
        Boat(int weight=0,float tonnage=0):Vehicle(weight) 
        
            Boat::tonnage tonnage; 
            cout<<"載入Boat類構造函數"<<endl; 
        
        void ShowMe() 
        
            cout<<"我是船!"<<endl; 
        
    protected: 
        float tonnage; 
}; 
 
class AmphibianCar:public Car,public Boat//水陸兩用汽車,多重繼承的體現 

    public: 
        AmphibianCar(int weight,int aird,float tonnage) 
        :Vehicle(weight),Car(weight,aird),Boat(weight,tonnage) 
        //多重繼承要注意調用基類構造函數 
        
            cout<<"載入AmphibianCar類構造函數"<<endl; 
        
        void ShowMe() 
        
            cout<<"我是水陸兩用汽車!"<<endl; 
        
        void ShowMembers() 
        
            cout<<"重量:"<<weight<<"噸,"

                <<"空氣排量:"<<aird<<"CC,"

                <<"排水量:"<<tonnage<<"噸"<<endl; 
        
}; 
int main() 

    AmphibianCar a(4,200,1.35f); 
    a.ShowMe(); 
    a.ShowMembers(); 
    a.SetWeight(3); 
    a.ShowMembers(); 
    system("pause");  
}

  注意觀察類構造函數的構造順序。
  雖然說虛擬繼承與虛函數有一定相似的地方,但讀者務必要記住,他們之間是絕對沒有任何聯系的!

================================================================== 

補充:

1、 當一個類有多個父類時,每個父類在內存中依次排列,然后該類自己的成員。
2、 每一個父類的鏡像中,都包含有獨立的虛函數表
3、 當把子類的指針Upcast的時候,兩種Upcast的方式得到的結果分別指向各自的父類鏡像
4、 當兩個父類重載的虛函數不同時,會使用Thunk機制,也就是說,虛函數表中的函數指針并不指向實際的虛函數,而是指向一小段代碼。在這一小段代碼中,會修改This指針(ECX寄存器),使之指向合適的父類鏡像,然后再跳轉到實際的虛函數體。
5、 當不使用虛繼承時,共同基類的成員對象,在子類中會有獨立兩分(從兩個父類各自繼承了一份)。
6、 當使用虛繼承時,共同基類的成員對象也會在虛函數表中記錄,訪問它必須先查找虛函數表。

普通多重繼承下的虛函數表:
http://blog.csdn.net/tangaowen/article/details/5830803
http://www.cnblogs.com/itech/archive/2009/02/28/1399995.html
虛繼承下的虛函數表:
http://www.cnblogs.com/itech/archive/2009/02/27/1399996.html