目錄
前言
1. 繼承的基本概念
2. 繼承方式與訪問控制
3. 派生類與基類的對象轉換
4. 繼承中的作用域
5. 派生類的默認成員函數
6. 繼承中的特殊關系
6.1 繼承與友元
6.2 繼承與靜態成員
7. 復雜的菱形繼承問題
8. 繼承與組合的選擇
9. 常見面試題
總結
前言
繼承是面向對象編程中最重要的概念之一,它允許我們創建新的類(派生類)基于已有類(基類)的特性進行擴展。在C++中,繼承機制提供了代碼復用的強大手段,同時也帶來了許多需要注意的細節和復雜性。本文將全面介紹C++中的繼承機制,從基本概念到高級應用,幫助讀者深入理解這一重要主題。
1. 繼承的基本概念
繼承允許派生類復用基類的成員(包括成員變量和成員函數),同時可以添加新的特性或修改現有行為。這種機制體現了面向對象編程中"由簡單到復雜"的認知過程。
?
class Person {
public:void Print() {cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";int _age = 18;
};class Student : public Person {
protected:int _stuid; // 學號
};
?
在這個例子中,`Student`類通過公有繼承獲得了`Person`類的所有成員,同時添加了自己的特有成員`_stuid`。
2. 繼承方式與訪問控制
C++提供了三種繼承方式:public、protected和private。不同的繼承方式會影響基類成員在派生類中的訪問權限。
基類成員/繼承方式 | public繼承 | protected繼承 | private繼承 |
public成員 | public | protected | private |
protected成員 | protected | protected | private |
private成員 | 不可見 | 不可見 | 不可見 |
重要規則:
1. 基類的private成員在派生類中不可見
2. 成員在派生類中的訪問權限 = min(成員在基類的訪問權限, 繼承方式)
3. class默認private繼承,struct默認public繼承(但建議顯式指定)
3. 派生類與基類的對象轉換
派生類對象可以賦值給基類對象/指針/引用,這種現象稱為"切片"或"切割"。
?
Student sobj;
Person pobj = sobj; ?// 切片
Person* pp = &sobj; ?// 指針
Person& rp = sobj; ? // 引用
?
但反過來不行,除非使用強制類型轉換(需謹慎使用)。
4. 繼承中的作用域
基類和派生類有獨立的作用域。當派生類與基類有同名成員時,派生類成員會隱藏基類成員(稱為"隱藏"或"重定義")。
?
class Person {
protected:int _num = 111; // 身份證號
};class Student : public Person {
public:void Print() {cout << "身份證號:" << Person::_num << endl; // 顯式訪問cout << "學號:" << _num << endl;}
protected:int _num = 999; // 學號
};
?
5. 派生類的默認成員函數
派生類的6個默認成員函數有其特殊性:
1. 構造函數必須調用基類構造函數初始化基類部分
2. 拷貝構造必須調用基類拷貝構造
3. operator=必須調用基類operator=
4. 析構函數會自動調用基類析構函數(先派生后基類)
5. 對象初始化順序:先基類構造,再派生類構造
6. 對象析構順序:先派生類析構,再基類析構
6. 繼承中的特殊關系
6.1 繼承與友元
友元關系不能繼承,基類的友元不能訪問派生類的私有和保護成員。
6.2 繼承與靜態成員
基類定義的靜態成員在整個繼承體系中只有一個實例,無論派生出多少子類。
?
class Person {
public:static int _count; // 統計人數
};
int Person::_count = 0;class Student : public Person { /*...*/ };
class Graduate : public Student { /*...*/ };// 所有類共享同一個_count
?
7. 復雜的菱形繼承問題
菱形繼承是多繼承的一種特殊情況,會導致數據冗余和二義性問題。
class Person { /*...*/ };
class Student : public Person { /*...*/ };
class Teacher : public Person { /*...*/ };
class Assistant : public Student, public Teacher { /*...*/ };
?
解決方案是使用**虛擬繼承**:
?
class Student : virtual public Person { /*...*/ };
class Teacher : virtual public Person { /*...*/ };
class Assistant : public Student, public Teacher { /*...*/ };
?
虛擬繼承通過虛基表指針和虛基表解決數據冗余和二義性問題。
8. 繼承與組合的選擇
- 繼承表示"is-a"關系(如BMW是一種Car)
- 組合表示"has-a"關系(如Car有Tire)
設計原則:
1. 優先使用對象組合而非類繼承
2. 繼承會破壞封裝,增加耦合度
3. 組合保持類封裝,耦合度低
4. 需要多態時必須使用繼承
?
// 繼承示例
class BMW : public Car { /*...*/ };// 組合示例
class Car {
protected:Tire _t; // 輪胎
};
?
9. 常見面試題
1. 什么是菱形繼承?它的問題是什么?
2. 虛擬繼承如何解決數據冗余和二義性?
3. 繼承和組合的區別?何時使用它們?
總結
C++的繼承機制強大但復雜,特別是多繼承和菱形繼承。理解繼承的各種細節對于編寫健壯、可維護的面向對象代碼至關重要。在實際開發中,應當謹慎使用多繼承,優先考慮組合而非繼承,只有在確實需要表達"is-a"關系或實現多態時才使用繼承。
通過本文的學習,希望讀者能夠掌握C++繼承的核心概念,理解其底層原理,并能夠在實際項目中做出恰當的設計選擇。