C++類 --- 繼承的構造函數、多重繼承、虛繼承

一、繼承

繼承性是面向對象程序設計的第二大特性,它允許在既有類的基礎上創建新類,新類可以繼承既有類的數據成員和成員函數,可以添加自己特有的數據成員和成員函數,還可以對既有類中的成員函數重新定義。利用類的繼承和派生實現了更高層次的代碼可重用性,符合現代軟件開發的思想。

C++語言同時支持單一繼承和多重繼承。單一繼承是指派生類只從一個基類繼承而來;相應的,多重繼承指派生類同時從兩個或更多的基類繼承而來。

繼承的特性:

(1)定義派生類關鍵字可以是class或者是struct,兩者區別是:用class定義派生類,默認的繼承方式是private,用struct定義派生類,默認的繼承方式爲public。新增加的成員默認屬性也是class對應private屬性,struct對應public屬性。

(2)基類不能被派生類繼承的兩類函數是構造函數和析構函數。

實例:單一繼承

#include"stdafx.h"
#include<iostream>
using namespace std;

class Other
{
public:
    Other()
    {
        cout<<"constructing Other class"<<endl;
    }
    ~Other()
    {
        cout<<"destructing Other class"<<endl;
    }
};

class Base
{
public:
    Base()
    {
        cout<<"constructing Base class"<<endl;
    }
    ~Base()
    {
        cout<<"destructing Base class"<<endl;
    }
};

class Derive:public Base
{
private:
    Other ot;
public:
    Derive()
    {
        cout<<"constructing Derive class"<<endl;
    }
    ~Derive()
    {
        cout<<"destructing Derive class"<<endl;
    }
};

int main()
{
    Derive d;

    return 0;
}

運行結果:

 可以看到定義派生類對象時,構造函數的調用順序:

      a.先調用基類的構造函數

      b.然後調用派生類對象成員所屬類的構造函數(如果有對象成員)

      c.最後調用派生類的構造函數

      析構函數的調用順序正好與構造函數調用順序相反。

 

二、多重繼承

1、多重繼承概述

多重繼承:常規情況,一個類只有一個基類,而C++支持多重繼承,即一個類可以繼承自多個類。

人(Person)可以吃飯和睡覺,既可以是作家也可以是程序員,作家可以寫文章,程序員可以寫程序,

即是作家又是程序員的人能夠寫文章和寫程序。

多重繼承的優缺點:

多重繼承的優點很明顯,就是對象可以調用多個基類中的接口;

多重繼承的缺點是什麼呢?如果派生類所繼承的多個基類有相同的基類,而派生類對象需要調用這個祖先類的接口方法,就會容易出現二義性。

對於二義性,通常有兩個解決方案:

(1)加上全局符確定調用哪一份拷貝。

(2)使用虛擬繼承,使得多重繼承類只擁有基類類的一份拷貝。

2、靜態成員變量

在C++中(以及其他一些語言,如 C#,Java 等面向對象的語言中)類的成員變量被聲明爲static(稱爲靜態成員變量),意味着它爲該類的所有實例所共享,也就是說當某個類的實例修改了該靜態成員變量,其修改值爲該類的其它所有實例所見。

靜態成員變量特性:

靜態成員變量屬於整個類所有;

靜態成員的生命週期不依賴於任何對象(程序包運行的整個週期);

可以通過類名直接訪問公有靜態成員變量;

所有對象共享類的靜態成員變量;

可以通過對象名訪問公有靜態成員變量;

在定義時直接通過static關鍵字修飾;

靜態成員變量需要在類外單獨分配空間;

靜態成員變量在程序內部位於全局數據區(但是文件間無法共享)。

父類的static變量和函數在派生類中依然可用,但是受訪問性控制(比如,父類的private域中的就不可訪問),而且對static變量來說,派生類和父類中的static變量是共用空間的,這點在利用static變量進行引用計數的時候要特別注意。  

static函數沒有“虛函數”一說。因爲static函數實際上是“加上了訪問控制的全局函數”,全局函數哪來的什麼虛函數?

派生類的friend函數可以訪問派生類本身的一切變量,包括從父類繼承下來的protected域中的變量。但是對父類來說,他並不是friend的。

 

3、靜態成員函數

靜態成員函數是類中的特殊的成員函數;

靜態成員函數沒有隱藏的this指針;

靜態成員函數可以通過類名直接訪問;

靜態成員函數可以通過對象訪問;

靜態成員函數只能直接訪問靜態成員變量(函數),而不能直接訪問普通成員變量(函數)。

3、派生類構造函數與析構函數

在定義一個派生類的對象時,在派生類中新增加的數據成員當然用派生類的構造函數初始化,但是對於從基類繼承來的數據成員的初始化工作就必須由基類的構造函數完成,這就需要在派生類的構造函數中完成對基類構造函數的調用。同樣,派生類的析構函數只能完成派生類中新增加數據成員的掃尾、清理工作,而從基類繼承來的數據成員的掃尾工作也應有基類的析構函數完成。由於析構函數不能帶參數,因此派生類的析構函數默認直接調用了基類的析構函數。

派生類構造函數與析構函數的特點:

(1)一般情況下,基類名後面的參數表中的實際參數來自前面派生類構造函數形式參數總表,當然也可能是與前面形式參數無關的常量;

(2)多層次繼承中,每一個派生類只需要負責向直接基類的構造函數提供參數;如果一個基類有多個派生類,則每個派生類都要負責向該基類的構造函數提供參數。

定義派生類對象時,構造函數的調用順序:

(1)先調用基類的構造函數

(2)然後調用派生類對象成員所屬類的構造函數(如果有對象成員)

(3)最後調用派生類的構造函數

析構函數的調用順序正好與構造函數調用順序相反。

4、從多個父類繼承構造函數

在多重繼承中,派生類有多個平行的基類,這些處於同一層次的基類構造函數的調用順序,取決於聲明派生類時所指定的各個基類的順序,而與派生類構造函數的成員初始化列表中調用基類構造函數的順序無關。

實例:多重繼承

#include"stdafx.h"
#include<iostream>
using namespace std;

class Grand
{
    int g;
public:
    Grand(int n):g(n)
    {
        cout<<"Constructor of class Grand g="<<g<<endl;
    }
    ~Grand()
    {
        cout<<"Destructor of class Grand"<<endl;
    }
};

class Father:public Grand
{
    int f;
public:
    Father(int n1,int n2):Grand(n2),f(n1)
    {
        cout<<"Constructor of class Father f="<<f<<endl;
    }
    ~Father()
    {
        cout<<"Destructor of class Father"<<endl;
    }
};

class Mother
{
    int m;
public:
    Mother(int n):m(n)
    {
        cout<<"Constructor of class Mother m="<<m<<endl;
    }
    ~Mother()
    {
        cout<<"Destructor of class Mother"<<endl;
    }
};

class Son:public Father,public Mother
{
    int s;
public:
    Son(int n1,int n2,int n3,int n4):Mother(n2),Father(n3,n4),s(n1)
    {
        cout<<"Constructor of class Son s="<<s<<endl;
    }
    ~Son()
    {
        cout<<"Destructor of class Son"<<endl;
    }
};

int main()
{
    Son s(1,2,3,4);
    return 0;
}

運行結果:

可以看到,與單一繼承不同的是:在多重繼承中,派生類有多個平行的基類,這些處於同一層次的基類構造函數的調用順序,取決於聲明派生類時所指定的各個基類的順序,而與派生類構造函數的成員初始化列表中調用基類構造函數的順序無關。

三、虛基類、虛繼承(虛派生)

1、虛基類

如果一個派生類有多個直接基類,而這些直接基類又有一個共同的基類,則在最終的派生類中會保留該間接共同基類數據成員的多份同名成員。

在引用這些同名的成員時,必須在派生類對象名後增加直接基類名,以避免產生二義性,使其惟一地標識一個成員,如

c1.A::display( )。

在一個類中保留間接共同基類的多份同名成員,這種現象是人們不希望出現的。C++提供虛基類(virtual base class )的方法,使得在繼承間接共同基類時只保留一份成員。

注意:

虛基類並不是在聲明基類時聲明的,而是在聲明派生類時,指定繼承方式時聲明的。因爲一個基類可以在生成一個派生類時作爲虛基類,而在生成另一個派生類時不作爲虛基類。

聲明虛基類的一般形式爲

   class 派生類名: virtual 繼承方式

基類名

經過這樣的聲明後,當基類通過多條派生路徑被一個派生類繼承時,該派生類只繼承該基類一次。

需要注意: 爲了保證虛基類在派生類中只繼承一次,應當在該基類的所有直接派生類中聲明爲虛基類。否則仍然會出現對基類的多次繼承。

如果在派生類B和C中將類A聲明爲虛基類,而在派生類D中沒有將類A聲明爲虛基類,則在派生類E中,雖然從類B和C路徑派生的部分只保留一份基類成員,但從類D路徑派生的部分還保留一份基類成員。

虛基類的初始化如果在虛基類中定義了帶參數的構造函數,而且沒有定義默認構造函數,則在其所有派生類(包括直接派生或間接派生的派生類)中,通過構造函數的初始化表對虛基類進行初始化。

在最後的派生類中不僅要負責對其直接基類進行初始化,還要負責對虛基類初始化。C++編譯系統只執行最後的派生類對虛基類的構造函數的調用,而忽略虛基類的其他派生類。

對虛基類的構造函數的調用,這就保證了虛基類的數據成員不會被多次初始化。

2、虛繼承

虛繼承是面向對象編程中的一種技術,是指一個指定的基類,在繼承體系結構中,將其成員數據實例共享給也從這個基類型直接或間接派生的其它類。

1)D繼承了B,C也就繼承了兩個虛基類指針

2)虛基類表存儲的是,虛基類相對直接繼承類的偏移(D並非是虛基類的直接繼承類,B,C纔是)

實例:

#include<iostream>
using namespace std;
 
class A  //大小爲4
{
public:
	int a;
};
class B :virtual public A  //大小爲12,變量a,b共8字節,虛基類表指針4
{
public:
	int b;
};
class C :virtual public A //與B一樣12
{
public:
	int c;
};
class D :public B, public C //24,變量a,b,c,d共16,B的虛基類指針4,C的虛基類指針4
{
public:
	int d;
};
 
int main()
{
	A a;
	B b;
	C c;
	D d;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	cout << sizeof(d) << endl;
	system("pause");
	return 0;
}

運行結果分析:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章