1. 程式人生 > >三種繼承、多型-虛擬函式

三種繼承、多型-虛擬函式

總結一下最近學到的類繼承的知識,包括三種繼承方式、多型的實現、動態聯編和靜態聯編。
歡迎各位指正其中的錯誤。
以後的理解更加深刻了回來更新和修改。

  • 三種繼承

從一個類,派生出另一個類時,原始類稱為基類(父類),繼承類稱為派生類(子類)

派生類物件儲存了基類的資料成員,且可以使用基類的方法,但不能直接訪問基類的私有成員,必須使用基類的公有方法進行訪問。可以根據需要新增額外的成員

因為建構函式不能繼承,那麼 派生類在建立物件時先使用基類的建構函式初始化繼承的基類的資料成員,再使用自己的建構函式( 類的建構函式可以為空,但必須存在

例如:

#include<iostream>
using namespace std; class A { int x; public: A( int xx ) { x = xx; } void show() { cout << " A : " << x << endl; } }; class B:public A { int y; public: B( int yy,int xx) : A( xx ) //成員初始化列表機制 { y = yy; } void
show() { A::show(); cout << " B : " << y << endl; } }; int main() { B b( 3,2 ); b.show(); return 0; }

輸出結果為

A : 2
B : 3

這裡我在父類A裡定義的建構函式只有一個有參型別,所以呼叫時要顯示呼叫。即如果父類有顯示聲明瞭建構函式,那麼子類最低限度實現父類的一個建構函式,如果子類沒有定義建構函式,則呼叫父類無引數的構造方法

派生類建構函式的要點
1.首先建立基類物件
2.派生類建構函式應通過成員初始化列表將資訊傳遞給基類建構函式
3.派生類建構函式應初始化派生類新增的資料成員

公有繼承方式

(1) 基類成員對派生類的可見性:
公有成員和保護成員可見,而私有成員不可見。這裡保護成員同於公有成員。

(2) 基類成員對派生類物件的可見性:
公有成員可見,其他成員不可見。

所以,在公有繼承時,派生類的物件可以訪問基類中的公有成員;派生類的成員函式可以訪問基類中的公有成員和保護成員。這裡,一定要區分清楚派生類的物件和派生類中的成員函式對基類的訪問是不同的。

私有繼承方式

(1) 基類成員對派生類的可見性:
公有成員和保護成員是可見的,而私有成員是不可見的。

(2) 基類成員對派生類物件的可見性:
所有成員都是不可見的。

所以,在私有繼承時,基類的成員只能由直接派生類訪問,而無法再往下繼承。

保護繼承方式

這種繼承方式與私有繼承方式的情況相同。兩者的區別僅在於對派生類的成員而言,對基類成員有不同的可見性。
上述所說的可見性也就是可訪問性。

關於可訪問性還有另的一種說法。這種規則中,稱派生類的物件對基類訪問為水平訪問,稱派生類的派生類對基類的訪問為垂直訪問。

綜上所述:

公有繼承:
對派生類成員共有和保護成員可訪問,對物件只有公有成員可訪問

私有繼承:
對派生類成員公有和保護成員可訪問,對物件所有成員都不可訪問

保護繼承:
保護繼承時基類中各成員屬性均變為protected,並且基類中private成員被隱藏。派生類成員只能訪問基類的公有和保護成員,對派生類物件所有成員均不可訪問

  • 多型的實現

多型,介面的多種不同的實現方式即為多型

多型存在的三個條件:
1.必須存在繼承關係;
2.繼承關係中必須有同名的虛擬函式,並且它們是覆蓋關係(過載不行)。
3.存在基類的指標,通過該指標呼叫虛擬函式。

多型通過虛擬函式實現,引入新關鍵字virtual
在函式宣告前加入virtual,即將其變為虛擬函式

#include<iostream>
using namespace std;
class A
{
    int x;
    public:
    A( int xx )
    {
        x = xx;
    }
    virtual void show()
    {
         cout << " A : " << x << endl;
    }
};
class B:public A
{
    int y;
    public:
    B( int yy,int xx) : A( xx )
    {
        y = yy;
    }
    void show()
    {
        cout << " B : " << y << endl; 
    }
};
int main()
{
    A *b = new B( 3,2 );
    b->show();
    return 0;
}

這裡將上述例子裡的show函式宣告為虛擬函式,輸出結果為B :3

這裡如果show不是虛擬函式,那麼b->show()將執行父類的函式,因為b是一個A型別的指標

也就是說,多型也就是通過父類指標來指向子類物件實現不同的操作,父類就像ATM機,插入不同的卡(子類)要取錢時,ATM機不需要為每一張卡寫不同的函式,只需要用ATM機(父類)指標指向卡(子類)去完成相對應的操作

方法在父類中被宣告為虛的後,在子類中自動成為虛方法

-LZJ:父類指標為什麼能指向子類指標?
這兩個不是型別不一樣麼,為什麼父類指標還能指向子類呢。(子類指標不能指向父類
其實,不止是指標,C++允許基類引用指任何從該基類派生而來的任何型別
型別一致其實不是說一定要型別完全一樣,型別是一種約束,幫助你驗證程式的正確性,類的繼承就是表示一種 “繼承類是基類中更具體的東西”,子類和父類的關係並不是兩個獨立的型別,但無法使用不存在於基類只存在於派生類的元素(所以我們需要虛擬函式、純虛擬函式)

例如

#include<iostream>
using namespace std;
class A
{
    int x;
    public:
    A( int xx )
    {
        x = xx;
    }
    void q()
    {

    }
};
class B:public A
{
    int y;
    public:
    B( int yy,int xx) : A( xx )
    {
        y = yy;
    }
    void w()
    {

    }
};
int main()
{
    A *b = new B( 3,2 );
    b->show();
    b->q();   //ok
    b->w();   //error
    return 0;
}
  • 動態聯編、靜態聯編

程式呼叫函式時,將使用哪個可執行程式碼塊呢?編譯器負責回答你。將原始碼中的函式呼叫解釋為執行特定的函式程式碼塊稱為函式名聯編。
在編譯過程中完成這種聯編被稱為靜態聯編,又稱為早期聯編,那虛擬函式的出現讓編譯器無法在編譯過程確定使用哪一個函式,所以編譯器可以在執行過程中選擇正確的虛方法的程式碼,這被稱為動態聯編,也被稱為晚期聯編。

  • 虛擬函式表
    虛擬函式的工作原理:編譯器處理虛擬函式的方法是,給每一個物件新增一個隱藏成員。隱藏成員儲存了一個指向函式地址陣列的指標。這種陣列稱為虛擬函式表。當一個物件呼叫了虛擬函式,實際的被呼叫函式通過這個隱藏指標到虛擬函式表裡找到真正的函式指標。

一個類只有一個虛擬函式表,子類有多少個父類就有多少個虛擬函式表
虛擬函式表放在程式的常量區。
在GNU C++中,虛指標位於物件的尾部,在Visual C+中在起始位置
所有的類都不會和其他的類共享一張虛擬函式表。

建構函式不需要是虛擬函式,也不允許是虛擬函式

編譯時多型性:過載函式
執行時多型性:虛擬函式
過載並不是多型,多型針對物件,過載針對方法