1. 程式人生 > >C++繼承與派生

C++繼承與派生

規則 方法 三種 賦值兼容 順序 spa 構造 指針 rtu

2017-06-25 23:00:59

c++中的繼承和派生是面向對象編程中的一個重要內容,通過繼承可以實現代碼的復用,同時繼承也是實現多態性的基礎。

一、c++繼承的基本形式

class 派生類名:繼承方式 基類名,繼承方式 基類名

{};

繼承方式主要有三種,public ,private ,protected。

缺省條件下是private繼承,三種中public繼承用的最多,不同的繼承方式決定了子類中從基類繼承過來的成員的訪問屬性。

public繼承:

基類的public,protected成員在子類中訪問屬性不變,子類新增的成員函數可以直接訪問,對於基類的private成員依然是基類的私有,子類無法直接進行訪問。

技術分享

private繼承:

基類的public,protected成員轉變為子類的private成員,子類新增的成員函數可以進行訪問,對於基類的private成員依然是基類的私有,子類無法直接進行訪問。

技術分享

protected繼承:

基類的public,protected成員轉變為子類的protected成員,子類新增的成員函數可以進行訪問,對於基類的private成員依然是基類的私有,子類無法直接進行訪問。

技術分享

private繼承和protected繼承的區別是private繼承的子類如果繼續被繼承,那麽這些從其分類繼承得到的數據將不會被其子類繼承,而protected則是可以的。

二、c++繼承的註意事項

(1)父類的構造函數和析構函數是不會被繼承的,需要重寫派生類的構造函數和析構函數。

派生類的成員數據中有來自父類的成員數據,因此在寫派生類的構造函數的時候需要調用其父類的構造函數。

如果派生類的成員中有成員對象,那麽也需要用成員對象名來進行初始化。

這兩種都是用初始化表來進行初始化工作的。

技術分享

(2)派生類構造函數、析構函數的調用順序

  • 構造函數中首先先調用各個直接基類的構造函數,之後再調用成員對象的構造函數,最後才是新增成員的初始化。
  • 對於多繼承有多個基類,那麽其構造函數的調用順序是按被繼承時的聲明順序,從左到右一次調用,與初始化表的順序無關
  • 對於成員對象的初始化也是一樣,與他們的聲明順序有關,和構造函數中的初始化表的順序無關。
  • 如果沒有進行顯示的調用,那麽會調用其默認的構造函數
  • 子類的拷貝構造函數也要為各個直接基類的拷貝構造函數傳遞參數
//若 B 類是 C 類的基類,則 C 類的自定義拷貝構造函數可定義為:
C:: C (  C  &c1  )  : B ( c1 )  {   函數體   }
  • 在派生類的析構函數中不會顯示調用基類的析構函數,系統會自動隱式調用,調用順序和構造函數的調用順序正好相反。先構造的後析構。
class A
{
    int x1,y1;
public:
    //構造函數
    A(int a=0,int b=0)
    {
        x1=a;
        y1=b;
        cout<<"調用A的構造函數了\n";
    }

    //拷貝構造函數
    A(A& a)
    {
        x1=a.x1;
        y1=a.y1;
        cout<<"調用A的拷貝構造函數了"<<endl;
    }

    //展示內容
    void show()
    {
        cout<<x1<<" "<<y1<<endl;
    }

    //析構函數,析構函數無參數且無返回值
    ~A()
    {
        cout<<"調用A的析構函數了"<<endl;
    }
};

class B
{
    int x2,y2;
public:
    //構造函數
    B(int a=0,int b=0)
    {
        x2=a;
        y2=b;
        cout<<"調用B的構造函數了\n";
    }

    //拷貝構造函數
    B(B& b)
    {
        x2=b.x2;
        y2=b.y2;
        cout<<"調用B的拷貝構造函數了"<<endl;
    }

    //展示內容
    void show()
    {
        cout<<x2<<" "<<y2<<endl;
    }

    //析構函數,析構函數無參數且無返回值
    ~B()
    {
        cout<<"調用B的析構函數了"<<endl;
    }
};

class C:public A,public B
{
    int x3,y3;
public:
    //形參寫成int x3,int y3,也就是和成員函數重名的話,內部的數據會出錯,所以,不要重名,改成a,b即可
    C(int x1,int y1,int x2,int y2,int a,int b):A(x1,y1),B(x2,y2)
    {
        x3=a;
        y3=b;
        cout<<"調用C的構造函數了"<<endl;
    }

    C(C& c):A(c),B(c)
    {
        x3=c.x3;
        y3=c.y3;
        cout<<"調用C的拷貝構造函數了"<<endl;
    }

    void show()
    {
        A::show();
        B::show();
        cout<<x3<<" "<<y3<<endl;
    }

    ~C()
    {
        cout<<"調用C的析構函數了"<<endl;
    }
};



int _tmain(int argc, _TCHAR* argv[])
{
    C c1(1,2,3,4,5,6);
    c1.show();
    C c2(c1);
    c2.show();
    return 0;
}

技術分享

(3)賦值兼容規則

規定了基類和其子類之間的賦值規則。

對於公有派生,可以將派生類對象賦值給其基類,反之則是不被允許的。

具體體現為:

  • 基類對象=派生類對象
  • &基類對象的別名=派生類對象
  • *基類對象的指針=&派生類對象的地址

對於這種賦值,基類只能訪問子類中從基類繼承過來的那部分成員。

(4)繼承過程中的二義性問題

繼承過程中可能會出現二義性的問題。主要有兩種形式的二義性。

一是在多繼承中,兩個父類中有同名的變量,這時如果是公有繼承,那麽子類中就會出現兩個同名變量。解決方法有兩種,一是用域作用符進行限定,二是使用同名隱藏。

二是在多繼承中,兩個父類的父類是同一個類,這時就需要使用虛基類的手段來進行解決。虛基類可以保證在間接繼承的時候只保留一份共同基類的成員數據。

虛基類的定義:

class 派生類:virtual 繼承方式 基類名

{};

  • 由虛基類直接或者間接派生出來的子類都必須列出對虛基類的構造函數,若未顯示列出則調用默認構造函數
  • 最終建立對象的類稱為最終派生類,只在最終派生類中調用基類的構造函數,而在基類的直接派生類中不掉用基類的構造函數,這樣就避免了基類成員的重復繼承問題
class B
{
    int b;
public:
    B(int x)
    {
        b=x;
        cout<<"調用了B的構造函數"<<endl;
    }

    ~B()
    {
        cout<<"調用了B的析構函數"<<endl;
    }
};

class B1:virtual public B
{
    int b1;
public:
    B1(int x,int y):B(x)
    {
        b1=y;
        cout<<"調用B1的構造函數了"<<endl;
    }

    ~B1()
    {
        cout<<"調用B1的析構函數了"<<endl;
    }
};

class B2:virtual public B
{
    int b2;
public:
    B2(int x,int y):B(x)
    {
        b2=y;
        cout<<"調用B2的構造函數了"<<endl;
    }

    ~B2()
    {
        cout<<"調用B2的析構函數了"<<endl;
    }
};


class C:public B1,public B2
{
    int c;
public:
    C(int a,int b,int c,int d):B1(a,b),B2(a,c),B(a)
    {
        c=d;
        cout<<"調用C的構造函數了"<<endl;
    }

    ~C()
    {
        cout<<"調用C的析構函數了"<<endl;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    C c1(1,2,3,4);
    return 0;
}

技術分享

C++繼承與派生