<<從0到1學C++>> 第6篇 繼承和派生

本篇要學習的內容和知識結構概覽


繼承和派生的概念

派生

經過特殊化已有的類來創建新類的過程, 叫作」類的派生」, 原有的類叫作」基類」, 新創建的類叫作」派生類」. 從類的成員角度看, 派生類自動地將基類的全部成員做爲本身的成員, 這叫作」繼承」. 基類和派生類也能夠叫作」父類」和」子類」, 也能夠叫作」通常類」和」特殊類」.bash

繼承

類的繼承是指派生類繼承基類的數據成員和成員函數. 繼承用來表示類屬關係, 不能將繼承理解爲構成關係函數

繼承派生的做用

  • 增長新的成員(數據成員和成員函數)
  • 從新定義已有的成員函數
  • 改變基類成員的訪問權限

單一繼承

通常形式

class 派生類名: 訪問控制 基類名 {	private:		成員聲明列表	protected:		成員聲明列表	public:		成員聲明列表}複製代碼

"冒號"表示新類是哪一個基類的派生類學習

"訪問控制"指繼承方式. 三個方式: public, protected, privateui

派生類的構造函數和析構函數

// 基類
class Point {
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
        cout << "init Point" << endl;
    }
    void showPoint() {
        cout << "x = " << x << ", y = " << y << endl;
    }
    ~Point() {
        cout << "delete Point" << endl;
    }
};

// 派生類
class Rect: public Point {
    int w;
    int h;
    
    public:
    // 調用基類的構造函數對基類成員進行初始化
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
        cout << "init Rect" << endl;
    }
    void showRect() {
        cout << "w = " << w << ", h = " << h << endl;
    }
    ~Rect() {
        cout << "delete Rect" << endl;
    }
};

int main() {
    Rect r(3, 4, 5, 6);
    r.showPoint();
    r.showRect();
    
    /** 輸出結果
     init Point // 當定義一個派生類的對象時, 首先調用基類的構造函數, 完成對基類成員的初始化
     init Rect // 而後執行派生類的構造函數, 完成對派生類成員的初始化
     x = 3, y = 4 // 調用基類成員函數showPoint();
     w = 5, h = 6 // 調用派生類成員函數showRect();
     delete Rect // 構造函數的執行順序和構造函數的執行順序相反, 首先調用派生類的析構函數
     delete Point // 其次調用基類的析構函數
     */
}
複製代碼

類的保護成員

若是但願Rect中的showRect()函數能夠一次顯示x, y , w, h. 咱們直接修改showRect()函數是不行的.spa

void showRect() {
	cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
}複製代碼

報錯 error: 'x' is a private member of ‘Point' 'y' is a private member of ‘Point', x, y爲Point類的私有成員, 公有派生時, 在Rect類中是不可訪問的指針

咱們還須要將基類Point中的兩個成員聲明爲protected的屬性code

像這樣cdn

// 基類
class Point {
    // 公有數據成員
    protected:
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
        cout << "init Point" << endl;
    }
    void showPoint() {
        cout << "x = " << x << ", y = " << y << endl;
    }
};

// 派生類
class Rect: public Point {
    int w;
    int h;
    
    public:
    // 調用基類的構造函數對基類成員進行初始化
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
        cout << "init Rect" << endl;
    }
    
    // 公有派生, Point類中的受保護數據成員, 在Rect類中也是受保護的, 因此能夠訪問  // 而經過公有繼承的基類私有的成員, 在派生類中是不可被訪問的    void showRect() {
        cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
    }
};

int main() {
    Rect r(3, 4, 5, 6);
    r.showPoint();
    r.showRect();
}複製代碼

訪問權限和賦值兼容規則

在根類中, 對於成員的訪問級別有三種, public, protected, private對象

在派生類中, 對於成員的訪問級別有四種, public(公有), protected(受保護), private(私有), inaccessible(不可訪問)blog

  • 公有派生和賦值兼容規則

在公有派生狀況下, 基類成員的訪問權限在派生類中基本保持不變

  1. 基類的公有成員在派生類中仍然是公有的
  2. 基類的保護成員在派生類中仍然是受保護的
  3. 基類的不可訪問的成員在派生類中仍然是不可訪問的
  4. 基類的私有成員在派生類中變成了不可訪問的

總結: 在公有派生的狀況下, 經過派生類本身的成員函數能夠訪問繼承過來的公有和保護成員, 可是不能訪問繼承來的私有成員, 由於繼承過程的私有成員, 變成了第四個級別, 不可訪問的.

賦值兼容規則:

在公有派生的狀況下, 一個派生類的對象能夠做爲基類的對象來使用的狀況. 

像這樣

// 基類
class Point {
    // 這裏聲明成員屬性爲受保護的
    protected:
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
    }
    
    void show() {
        cout << "x = " << x << ", y = " << y << endl;
    }
};

// 派生類
class Rect: public Point {
    int w;
    int h;
    
    public:
    // 調用基類的構造函數對基類成員進行初始化
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
    }
    
    void show() {
        cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
    }
};

int main() {
    Point a(1, 2);
    Rect b(3, 4, 5, 6);
    a.show();
    b.show();
    
    Point & pa = b; // 派生類對象初始化基類的引用
    pa.show(); // 實際調用基類的show()函數
    
    Point * p = &b; // 派生類對象的地址賦值給指向基類的指針
    p -> show(); // 實際也是調用基類的show()函數
    
    Rect * pb = &b; // 派生類指針
    pb -> show(); // 調用派生類的show()函數
    
    a = b; // 派生類對象的屬性值, 更新基類對象的屬性值
    a.show(); // 調用基類的show()函數
    /**
     x = 1, y = 2
     x = 3, y = 4, w = 5, h = 6
     x = 3, y = 4
     x = 3, y = 4
     x = 3, y = 4, w = 5, h = 6
     x = 3, y = 4
     */
}
複製代碼

  • 「isa」和」has-a「的區別

繼承和派生 isa

好比一個Person類, 派生出一個Student類, 咱們能夠說Student就是Person, 也就是 Student isa Person, 而反過來則不行.

一個類用另外一個類的對象做爲本身的數據成員或者成員函數的參數 has-a

像這樣

// 地址類
class Address {};
class PhoneNumber {};

// 職工類
class Worker {
    String name;
    Address address;
    PhoneNumber voiceNumber;
};
複製代碼

表示一個Worker對象有一個名字, 一個地址, 一個電話號碼, has-a的關係, 包含的關係

  • 私有派生

經過私有派生, 基類的私有和不可訪問成員在派生類中是不可訪問的, 而公有和保護成員這裏就成了派生類的私有成員

// 基類
class Point {
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
    }

    void show() {
        cout << "x = " << x << ", y = " << y << endl;
    }
};

// 派生類
class Rect: private Point {
    int w;
    int h;
    
    public:
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
    }
    
    void show() {
        Point::show(); // 經過私有繼承, Point類中的公有成員show(), 在Rect中爲私有
        cout << "w = " << w << ", h = " << h << endl;
    }
};

class Test: public Rect {
    
    public:
    Test(int a, int b, int c, int d):Rect(a, b, c, d) {
        
    }
    void show() {
        Rect::show();
//        Point::show();
        /** error: 'Point' is a private member of ‘Point’
         標明: 不可訪問基類Point中的成員
         Rect類私有繼承自Point類, 因此Point中的私有成員x, 私有成員y, 在Rect類中爲不可訪問: Point類中公有成員show(), 在Rect中變私有
         Test類公有繼承自Rect類, 因此在Rect中成員x, 成員y, 仍然是不可訪問, Rect::show()仍是public, 可是Point::show()不可訪問 */
    }
};
複製代碼

由於私有派生不利於進一步派生, 於是實際中私有派生用得並很少

  • 保護派生

保護派生使原來的權限都降一級使用, 即private變爲不可訪問, protected變爲private, public變爲protected. 限制了數據成員和成員函數的訪問權限, 所以在實際中保護派生用得也很少.

好比: 咱們在上個例子中, Rect類保護派生於Point, 則在Test類中Point::show();就可使用啦!

多重繼承

一個類從多個基類派生

格式

class 派生類名: 訪問控制 基類名1, 訪問控制 基類名2, … {
	定義派生類本身的成員
}複製代碼

像這樣

// 基類A, 也叫根類
class A {
    int a;
    
    public:
    void setA(int x) {
        a = x;
    }
    
    void showA() {
        cout << "a = " << a << endl;
    }
};

// 基類B, 也叫根類
class B {
    int b;
    
    public:
    void setB(int x) {
        b = x;
    }
    
    void showB() {
        cout << "b = " << b << endl;
    }
};

// 多重繼承, 公有繼承自類A, 私有繼承自類B
class C: public A, private B {
    int c;
    
    public:
    void setC(int x, int y) {
        c = x;
        setB(y);
    }
    
    void showC() {
        showB();
        cout << "c = " << c << endl;
    }
};

int main() {
    C c;
    c.setA(53); // 調用基類setA()函數
    c.showA(); // 調用基類showA()函數
    
    c.setC(55, 58); // 調用派生類C的setC()函數
    c.showC(); // 調用派生類C的showC()函數
    
    // 派生類C私有繼承自基類B, 因此基類B中私有成員b, 在派生類C中不可訪問, 基類B中公有成員setB(), showB()在派生類C中變私有. 在main()函數中不可訪問
//    c.setB(60); // error: 'setB' is a private member of 'B'
//    c.showB(); // 'showB' is a private member of 'B'
    /**
     a = 53
     b = 58
     c = 55
     */
}
複製代碼

二義性及其支配規則

對基類成員的訪問必須是無二義性的, 若是一個表達式的含義能夠解釋爲能夠訪問多個基類中的成員, 則這種對基類成員的訪問就是不肯定的, 稱這種訪問具備二義性

做用域分辨符和成員名限定

// 基類A, 也叫根類
class A {
    public:
    void func() {
        cout << "A func" << endl;
    }
};

// 基類B, 也叫根類
class B {
    public:
    void func() {
        cout << "B func" << endl;
    }
    
    void gunc() {
        cout << "B gunc" << endl;
    }
};

// 多重繼承
class C: public A, public B {
    public:
    void gunc() {
        cout << "C gunc" << endl;
    }
    
    void hunc() {
        /**
         這裏就具備二義性, 它便可以訪問A類中的func(), 也能夠訪問類B中的func()
         */
//        func(); // error: Member 'func' found in multiple base classes of different types
    }
    
    void hunc1() {
        A::func();
    }
    
    void hunc2() {
        B::func();
    }
};

int main() {
    C c;
//    c.func(); // 具備二義性
    c.A::func();
    c.B::func();
    c.B::gunc();
    c.C::gunc();
    
    c.gunc();
    c.hunc1();
    c.hunc2();
    
    /** 輸出結果
     A func
     B func
     B gunc
     C gunc
     
     C gunc // 若是基類中的名字在派生類中再次聲明, 則基類中的名字就被隱藏. 若是咱們想要訪問被隱藏的基類中的成員則使用做用域分辨符B::gunc();
     A func
     B func
     */
}複製代碼

格式: 類名::標識符

:: 爲做用域分辨符, "類名"能夠是任一基類或派生類名, 「標識符」是該類中聲明的任一成員名

派生類支配基類的同名函數

若是派生類定義了一個同基類成員函數同名的新成員函數(具備相同參數表的成員函數), 派生類的新成員函數就覆蓋了基類的同名成員函數.

在這裏, 直接使用成員名只能訪問派生類中的成員函數, 使用做用域運算符, 才能訪問基類的同名成員函數.

派生類中的成員函數名支配基類中的同名的成員函數名, 這稱爲名字支配規則.

若是一個名字支配另外一個名字, 則兩者之間不存在二義性, 當選擇該名字時, 使用支配者的名字.

例如上個例子中

c.gunc() // 輸出」C gunc」, 基類B中的gunc成員函數被支配了
c.B::gunc(); // 加上做用域分辨符, 來使用被支配的成員複製代碼

來自一張表的總結


總結

C++中的多重繼承可能更靈活, 而且支持三種派生方式, 咱們在學習一門語言的時候, 更應該把精力放在它的特性上面, 不該該用什麼語言, 都用本身所擅長語言的思考方式, 實現方式等, 要學會發揮該語言的優點所在. 

本系列文章會持續更新! 你們踊躍的留下本身的腳印吧!

👣👣👣👣👣👣👣👣

相關文章
相關標籤/搜索