C++之繼承

C++ 繼承 語法 程序設計 軟件工程


若有理解不當,歡迎各位指正!


作者:吳博文 中山大學數據科學與計算機學院
本人博客:http://blog.csdn.net/bowen_wu_

一、繼承基本概念

通過繼承,所構建的新的類是基於可靠的基類(base class),所以通過這種機制,可以提高代碼的複用率(reuseability),從而減少新類的調試的工作量,從而提高編碼的效率。同時,由於存在繼承這個機制,有很多的組織專門編寫這些基類,進一步提高編碼的效率。
值得注意的是,繼承的過程中也應該注意派生類與基類之間的邏輯關係。比如,一輛奔馳是一輛車,一輛車是一種交通工具,那麼這其中就可以是車繼承交通工具、奔馳繼承車。

二、基本語法

class 派生類名:繼承訪問控制 基類類名{
成員訪問控制:
成員聲明列表;
};
其中一個類可以同時繼承多個類,只用用逗號隔開即可。如下:
class 派生類名:繼承訪問控制 基類類名, 繼承訪問控制 基類類名, 。。。{
成員訪問控制:
成員聲明列表;
};

C++所支持的基本繼承形式包括以下幾種
這裏寫圖片描述
個人認爲其中最麻煩的是多重繼承,本人還沒有搞懂,以後大家一起探討。
繼承中成員的訪問方法:

// Created by BowenWu in 20160419
#include <iostream>
using namespace std;
class base_first {
    public:
        int base_1;
        int get() {
            return base_1;
        }
    public:
        base_first() {
            cout << "base_first" << endl;
            base_1 = 10;
        }
};

class derived_first : public base_first{
    public:
        int derived_1;
        int get() {
            return derived_1;
        }
    public:
        derived_first() {
            cout << "derived_first" << endl;
            derived_1 = 9;
        }
};
class derived_second:public derived_first {
    public:
        int derived_1;
        int get() {
            return derived_1;
        }
    public:
        derived_second() {
            cout << "derived_second" << endl;
            derived_1 = 8;
        }
};

int main() {
    derived_second aaa;
    //  try to use int base_1 in class base_first
    // since there is no same name variable in the
    // inheritance relation, so we can use it directly
    cout << aaa.base_1 << endl;
    // try to use int derived_1 in class derived_second
    // easy to see that compiler use derived_1 in
    // class derived_second
    // but how to use derived_1 in class derived_first?
    cout << aaa.derived_1 << endl;
    // in this way
    // but I don't think it is a good practice
    cout << aaa.derived_first::derived_1 << endl;
    cout << aaa.derived_first::base_first::base_1 << endl;
    cout << endl << "Use Function:" << endl;
    // end
    // use function from base class is similar
    cout << aaa.get() << endl;
    cout << aaa.derived_first::get() << endl;
    cout << aaa.derived_first::base_first::get() << endl;

}

這就是基本的訪問方式, 但是在C++編程中,我們不應該使用這種方式,因爲這種方式將數據成員放在public中,破壞了class的封裝性,也失去了面向對象編程的重要價值。
代碼中使用的一個操作符”::”稱作域作用符,個人理解一個類中稱作一個域,而“aaa.derived_first::base_first::base_1”中aaa後面的點,可能表示derived_first看做aaa的一個數據成員。
其實使用函數與訪問數據成員等效,可以將函數和數據成員看做同一個系列。

各種繼承的訪問權限

這裏其實很好理解,個人認爲可以總結成private < protected < public,當繼承方式與基類中的聲明方式不同時,只用取相對權限較小的那個就是了。這裏權限應該理解成這個數據成員變成了派生類中相應權限的成員一部分,即對於客戶端代碼來講的權限,而不是對於這個派生類而言是private、public。
值得注意的是在基類中private的成員無論如何都不能訪問。
繼承成員的訪問權限

protected訪問控制

protected是C++繼承中特有的一種訪問控制,對於客戶端代碼來講,他與private相同,但在繼承的體系中,他與public類似,也就是說他是介於private和public之間的一種訪問控制。

恢復基類的訪問權限

基類中的public或protected成員,因使用protected或private繼承訪問控制而導致在派生類中的訪問方式發生改變,可以使用“訪問聲明”恢復爲原來的訪問控制方式
訪問聲明的形式
基類名::成員名;(放於適當的成員訪問控制後)
使用情景
在派生類中希望大多數繼承成員爲protected或private,只有少數希望保持爲基類原來的訪問控制方式。
對於訪問權限的恢復,各個編譯器有一些細節的差異,應該注意處理。
例:

// Created by BowenWu in 20160419
#include <iostream>
using namespace std;
class base_first {
    public:
        int base_1;
        int get() {
            return base_1;
        }
    public:
        base_first() {
            cout << "base_first" << endl;
            base_1 = 10;
        }
};

class derived_first : private base_first{
    public:
        // 恢復訪問權限
        // 基類名加域作用符加成員
        base_first::base_1;
        int derived_1;
        int get() {
            return derived_1;
        }
    public:
        derived_first() {
            cout << "derived_first" << endl;
            derived_1 = 9;
        }
};

int main() {
    derived_first aaa;
    cout << aaa.base_1++ << endl;
    cout << aaa.base_1 << endl;
}

基類成員方法的重定義

其實就是和定義一個成員方法一樣,當定義的成員方法的聲明與函數首部與基類的成員方法完全相同時,成爲重定義。若函數名相同,但是函數首部不同,稱作重載。其實在調用一個成員方法時,編譯器是按照一定的規則尋找該調用哪個函數。
這裏寫圖片描述

屏蔽繼承成員

目的:
使得客戶代碼通過派生類對象不能訪問繼承成員。
方法:
–使用繼承訪問控制protected和private(真正屏蔽)
–在派生類中成員訪問控制protected或private之後定義與繼承成員函數相同的函數原型,而函數體爲空(非真正屏蔽,仍可通過使用“基類名::成員名”訪問)

繼承成員重命名

目的:
解決名字衝突。
在派生類中選擇更合適的術語命名繼承成員。
方法:
–在派生類中定義新的函數,該函數調用舊函數;屏蔽舊函數。
–在派生類中定義新的函數,該函數的函數體與舊函數相同。

類型兼容性

賦值運算符的兼容性

可以將後代類的對象賦值給祖先類對象,反之不可。
每個派生類對象包含一個基類部分,這意味着可以將派生類對象當作基類對象使用。

指針與引用的兼容性

指向基類對象的指針也可指向公有派生類對象,要特別注意是公有派生!!

強制類型轉換

派生類可以強制轉換爲基類,而基類不能強制轉換爲派生類。

命名衝突

當派生類繼承的兩個基類中有相同名字的數據成員或成員方法時,會發生這種情況。可以通過重定義或者域作用符來解決這個問題。詳見下面的樣例代碼:

class BASE1 {
public: void show() { cout << i << "\n"; }
protected:  int i;
};
class BASE2 {
public: void show() {   cout << j << "\n"; }
protected:  int j;
};
// 多重繼承引起名字衝突:DERIVED的兩個基類BASE1和//BASE2有相同的名字show
class DERIVED: public BASE1, public BASE2 {
public: 
    void set(int x, int y)  {  i = x;  j = y;   }
};// 派生類在編譯時不出錯:C++語法不禁止名字衝突。
int main()
{   
           DERIVED obj;      // 聲明一個派生類的對象
    obj.set(5, 7);          // set()是DERIVED類自身定義的

           // obj.show();
    // 二義性錯誤,編譯程序無法決定調用哪一個版本

           obj.BASE1::show();   
    // 正確,顯式地調用從BASE1繼承下來show()

           obj.BASE2::show();       
    // 正確,顯式地調用從BASE2繼承下來show()
                      :
}                                                               

三、重複繼承時的數據成員

這裏寫圖片描述
對於這種情況,需要引進虛繼承的概念

// Created by BowenWu in 20160419
#include <iostream>
using namespace std;
class base_first {
    public:
        int base_1;
        int get() {
            return base_1;
        }
    public:
        base_first() {
            cout << "base_first" << endl;
            base_1 = 10;
        }
};

class derived_first : public base_first{
    public:
        int derived_1;
        int get() {
            return derived_1;
        }
    public:
        derived_first() {
            cout << "derived_first" << endl;
            derived_1 = 9;
        }
};
class derived_second:public base_first {
    public:
        int derived_1;
        int get() {
            return derived_1;
        }
    public:
        derived_second() {
            cout << "derived_second" << endl;
            derived_1 = 8;
        }
};
class derived_third:public derived_first, public derived_second {};

int main() {
    derived_third aaa;
    // first let's try to use the variable that derived_first
    // and derived_second both have.
    cout << aaa.derived_1 << endl; //compile error
    // since this is ambiguous

}

對於上面這段代碼,有二義性的錯誤。
但我們可以通過下面這種方式來分別訪問derived_first和derived_second 中的base_first


int main() {
    derived_third aaa;
    // derived_first可以理解成aaa中的一個數據成員,故使用.訪問
    // 由於繼承關係的存在,base_1可以看做derived_first的一個數據成員
    cout << aaa.derived_first::base_1 << endl;
    cout << aaa.derived_second::base_1 << endl;
    aaa.derived_first::base_1 = 1;
    aaa.derived_second::base_1 = 2;
    cout << aaa.derived_first::base_1 << endl;
    cout << aaa.derived_second::base_1 << endl;

}

這段程序的運行結果是:
這裏寫圖片描述
從這段程序我們可以看出derived_third中保存了兩個base_first的副本。

四、虛繼承

Virtual inheritance is a technique used in C++, where a particular base class in an inheritance hierarchy is declared to share its member data instances with any other inclusions of that same base in further derived classes.
From Wikipedia
簡單來說,虛繼承就是當一個類同時繼承同一個基類兩次時,只保存一份數據成員,以避免Diamond Problem
這裏寫圖片描述

// Created by BowenWu in 20160419
#include <iostream>
using namespace std;
class base_first {
    public:
        int base_1;
        int get() {
            return base_1;
        }
    public:
        base_first() {
            cout << "base_first" << endl;
            base_1 = 10;
        }
};
// keyword virtual
class derived_first : virtual public base_first{
    public:
        int derived_1;
        int get() {
            return derived_1;
        }
    public:
        derived_first() {
            cout << "derived_first" << endl;
            derived_1 = 9;
        }
};
class derived_second: virtual public base_first {
    public:
        int derived_1;
        int get() {
            return derived_1;
        }
    public:
        derived_second() {
            cout << "derived_second" << endl;
            derived_1 = 8;
        }
};
class derived_third:public derived_first, public derived_second {};

int main() {
    derived_third aaa;
    // base_1 在aaa中只有一份,可以直接訪問
    cout << aaa.base_1;
}

在重複繼承沒有發生時,虛繼承並不會有什麼特別的。

發佈了24 篇原創文章 · 獲贊 9 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章