C++繼承詳解
廢話不多說直接上代碼
class 派生類名:[繼承方式] 基類名{
派生類新增加的成員
};
繼承方式限定了基類成員在派生類中的訪問權限,包括 public(公有的)、private(私有的)和 protected(受保護的)。此項是可選項,如果不寫,默認為 private(成員變量和成員函數默認也是 private)。
現在我們知道,public、protected、private 三個關鍵字除了可以修飾類的成員,還可以指定繼承方式。
繼承方式
不同的繼承方式會影響基類成員在派生類中的訪問權限。
1) public繼承方式
基類中所有 public 成員在派生類中為 public 屬性;
基類中所有 protected 成員在派生類中為 protected 屬性;
基類中所有 private 成員在派生類中不能使用。
2) protected繼承方式
基類中的所有 public 成員在派生類中為 protected 屬性;
基類中的所有 protected 成員在派生類中為 protected 屬性;
基類中的所有 private 成員在派生類中不能使用。
3) private繼承方式
基類中的所有 public 成員在派生類中均為 private 屬性;
基類中的所有 protected 成員在派生類中均為 private 屬性;
基類中的所有 private 成員在派生類中不能使用。
通過上面的分析可以發現:
1)?基類成員在派生類中的訪問權限不得高于繼承方式中指定的權限。例如,當繼承方式為 protected 時,那么基類成員在派生類中的訪問權限最高也為 protected,高于 protected 的會降級為 protected
2) 基類中的 private 成員在派生類中始終不能使用(不能在派生類的成員函數中訪問或調用)。
3) 如果希望基類的成員能夠被派生類繼承并且毫無障礙地使用,那么這些成員只能聲明為 public 或 protected;只有那些不希望在派生類中使用的成員才聲明為 private。
4) 如果希望基類的成員既不向外暴露(不能通過對象訪問),還能在派生類中使用,那么只能聲明為 protected。
注意,我們這里說的是基類的 private 成員不能在派生類中使用,并沒有說基類的 private 成員不能被繼承。實際上,基類的 private 成員是能夠被繼承的,并且(成員變量)會占用派生類對象的內存,它只是在派生類中不可見,導致無法使用罷了。
改變訪問權限
使用 using 關鍵字可以改變基類成員在派生類中的訪問權限,例如將 public 改為 private、將 protected 改為 public。
注意:using 只能改變基類中 public 和 protected 成員的訪問權限,不能改變 private 成員的訪問權限,因為基類中 private 成員在派生類中是不可見的,根本不能使用,所以基類中的 private 成員在派生類中無論如何都不能訪問。
#include
using namespace std;
//基類People
class People {
public:
void show();
protected:
char *m_name;
int m_age;
};
void People::show() {
cout << m_name << "的年齡是" << m_age << endl;
}
//派生類Student
class Student : public People {
public:
void learning();
public:
using People::m_name; //將protected改為public
using People::m_age; //將protected改為public
float m_score;
private:
using People::show; //將public改為private
};
void Student::learning() {
cout << "我是" << m_name << ",今年" << m_age << "歲,這次考了" << m_score << "分!" << endl;
}
int main() {
Student stu;
stu.m_name = "小明";
stu.m_age = 16;
stu.m_score = 99.5f;
stu.show(); //compile error
stu.learning();
return 0;
}
代碼中首先定義了基類 People,它包含兩個 protected 屬性的成員變量和一個 public 屬性的成員函數。定義 Student 類時采用 public 繼承方式,People 類中的成員在 Student 類中的訪問權限默認是不變的。
不過,我們使用 using 改變了它們的默認訪問權限,如代碼第 21~25 行所示,將 show() 函數修改為 private 屬性的,是降低訪問權限,將 name、age 變量修改為 public 屬性的,是提高訪問權限。
因為 show() 函數是 private 屬性的,所以代碼第 36 行會報錯。把該行注釋掉,程序輸出結果為:
我是小明,今年16歲,這次考了99.5分!
繼承時的名字遮蔽問題
如果派生類中的成員(包括成員變量和成員函數)和基類中的成員重名,那么就會遮蔽從基類繼承過來的成員。所謂遮蔽,就是在派生類中使用該成員(包括在定義派生類時使用,也包括通過派生類對象訪問該成員)時,實際上使用的是派生類新增的成員,而不是從基類繼承來的。
基類和派生類的構造函數
類的構造函數不能被繼承。因為即使繼承了,它的名字和派生類的名字也不一樣,不能成為派生類的構造函數。
在設計派生類時,對繼承過來的成員變量的初始化工作也要由派生類的構造函數完成,但是大部分基類都有 private 屬性的成員變量,它們在派生類中無法訪問,更不能使用派生類的構造函數來初始化。
這種矛盾在C++繼承中是普遍存在的,解決這個問題的思路是:在派生類的構造函數中調用基類的構造函數。
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
People(name, age)就是調用基類的構造函數,并將 name 和 age 作為實參傳遞給它,m_score(score)是派生類的參數初始化表,它們之間以逗號,隔開。
也可以將基類構造函數的調用放在參數初始化表后面:
Student::Student(char *name, int age, float score): m_score(score), People(name, age){ }
但是不管它們的順序如何,派生類構造函數總是先調用基類構造函數再執行其他代碼(包括參數初始化表以及函數體中的代碼),總體上看和下面的形式類似:
Student::Student(char *name, int age, float score){
People(name, age);
m_score = score;
}
當然這段代碼只是為了方便大家理解,實際上這樣寫是錯誤的,因為基類構造函數不會被繼承,不能當做普通的成員函數來調用。換句話說,只能將基類構造函數的調用放在函數頭部,不能放在函數體中。
另外,函數頭部是對基類構造函數的調用,而不是聲明,所以括號里的參數是實參,它們不但可以是派生類構造函數參數列表中的參數,還可以是局部變量、常量等,例如:
Student::Student(char *name, int age, float score): People("小明", 16), m_score(score){ }
構造函數的調用順序
從上面的分析中可以看出,基類構造函數總是被優先調用,這說明創建派生類對象時,會先調用基類構造函數,再調用派生類構造函數,如果繼承關系有好幾層的話,例如:
A --> B --> C
那么創建 C 類對象時構造函數的執行順序為:
A類構造函數 --> B類構造函數 --> C類構造函數
構造函數的調用順序是按照繼承的層次自頂向下、從基類再到派生類的。
還有一點要注意,派生類構造函數中只能調用直接基類的構造函數,不能調用間接基類的。以上面的 A、B、C 類為例,C 是最終的派生類,B 就是 C 的直接基類,A 就是 C 的間接基類。
C++ 這樣規定是有道理的,因為我們在 C 中調用了 B 的構造函數,B 又調用了 A 的構造函數,相當于 C 間接地(或者說隱式地)調用了 A 的構造函數,如果再在 C 中顯式地調用 A 的構造函數,那么 A 的構造函數就被調用了兩次,相應地,初始化工作也做了兩次,這不僅是多余的,還會浪費CPU時間以及內存,毫無益處,所以 C++ 禁止在 C 中顯式地調用 A 的構造函數。
調用規則:事實上,通過派生類創建對象時必須要調用基類的構造函數,這是語法規定。換句話說,定義派生類構造函數時最好指明基類構造函數;如果不指明,就調用基類的默認構造函數;如果沒有默認構造函數,那么編譯失敗。
基類和派生類的析構函數
和構造函數類似,析構函數也不能被繼承。與構造函數不同的是,在派生類的析構函數中不用顯式地調用基類的析構函數,因為每個類只有一個析構函數,編譯器知道如何選擇,無需程序員干涉。
另外析構函數的執行順序和構造函數的執行順序也剛好相反:
創建派生類對象時,構造函數的執行順序和繼承順序相同,即先執行基類構造函數,再執行派生類構造函數。
而銷毀派生類對象時,析構函數的執行順序和繼承順序相反,即先執行派生類析構函數,再執行基類析構函數。
多繼承
容易讓代碼邏輯復雜、思路混亂,一直備受爭議,中小型項目中較少使用,后來的 Java、C#、PHP 等干脆取消了多繼承。
多繼承的語法也很簡單,將多個基類用逗號隔開即可。例如已聲明了類A、類B和類C,那么可以這樣來聲明派生類D:
class D: public A, private B, protected C{
//類D新增加的成員
}
D 是多繼承形式的派生類,它以公有的方式繼承 A 類,以私有的方式繼承 B 類,以保護的方式繼承 C 類。D 根據不同的繼承方式獲取 A、B、C 中的成員,確定它們在派生類中的訪問權限。
多繼承下的構造函數
多繼承形式下的構造函數和單繼承形式基本相同,只是要在派生類的構造函數中調用多個基類的構造函數。以上面的 A、B、C、D 類為例,D 類構造函數的寫法為:
D(形參列表): A(實參列表), B(實參列表), C(實參列表){
//其他操作
}
基類構造函數的調用順序和和它們在派生類構造函數中出現的順序無關,而是和聲明派生類時基類出現的順序相同。仍然以上面的 A、B、C、D 類為例,即使將 D 類構造函數寫作下面的形式:
D(形參列表): B(實參列表), C(實參列表), A(實參列表){
//其他操作
}
那么也是先調用 A 類的構造函數,再調用 B 類構造函數,最后調用 C 類構造函數。
命名沖突:當兩個或多個基類中有同名的成員時,如果直接訪問該成員,就會產生命名沖突,編譯器不知道使用哪個基類的成員。這個時候需要在成員名字前面加上類名和域解析符::,以顯式地指明到底使用哪個類的成員,消除二義性。
虛繼承
多繼承是指從多個直接基類中產生派生類的能力,多繼承的派生類繼承了所有父類的成員。盡管概念上非常簡單,但是多個基類的相互交織可能會帶來錯綜復雜的設計問題,命名沖突就是不可回避的一個。
類 A 派生出類 B 和類 C,類 D 繼承自類 B 和類 C,這個時候類 A 中的成員變量和成員函數繼承到類 D 中變成了兩份,一份來自 A-->B-->D 這條路徑,另一份來自 A-->C-->D 這條路徑。
在一個派生類中保留間接基類的多份同名成員,雖然可以在不同的成員變量中分別存放不同的數據,但大多數情況下這是多余的
//間接基類A
class A{
protected:
int m_a;
};
//直接基類B
class B: public A{
protected:
int m_b;
};
//直接基類C
class C: public A{
protected:
int m_c;
};
//派生類D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名沖突
private:
int m_d;
};
為了消除歧義,我們可以在 m_a 的前面指明它具體來自哪個類:
void seta(int a){ B::m_a = a; }
虛繼承
虛繼承使得在派生類中只保留一份間接基類的成員。
在繼承方式前面加上?virtual?關鍵字就是虛繼承。
//間接基類A
class A{
protected:
int m_a;
};
//直接基類B
class B: virtual public A{ //虛繼承
protected:
int m_b;
};
//直接基類C
class C: virtual public A{ //虛繼承
protected:
int m_c;
};
//派生類D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正確
void setb(int b){ m_b = b; } //正確
void setc(int c){ m_c = c; } //正確
void setd(int d){ m_d = d; } //正確
private:
int m_d;
};
int main(){
D d;
return 0;
}
虛繼承的目的是讓某個類做出聲明,承諾愿意共享它的基類。其中,這個被共享的基類就稱為虛基類(Virtual Base Class),本例中的 A 就是一個虛基類。在這種機制下,不論虛基類在繼承體系中出現了多少次,在派生類中都只包含一份虛基類的成員。
我們會發現虛繼承的一個不太直觀的特征:必須在虛派生的真實需求出現前就已經完成虛派生的操作。在上圖中,當定義 D 類時才出現了對虛派生的需求,但是如果 B 類和 C 類不是從 A 類虛派生得到的,那么 D 類還是會保留 A 類的兩份成員。
換個角度講,虛派生只影響從指定了虛基類的派生類中進一步派生出來的類,它不會影響派生類本身。
在實際開發中,位于中間層次的基類將其繼承聲明為虛繼承一般不會帶來什么問題。通常情況下,使用虛繼承的類層次是由一個人或者一個項目組一次性設計完成的。對于一個獨立開發的類來說,很少需要基類中的某一個類是虛基類,況且新類的開發者也無法改變已經存在的類體系。
使用多繼承經常會出現二義性問題,必須十分小心。上面的例子是簡單的,如果繼承的層次再多一些,關系更復雜一些,程序員就很容易陷人迷魂陣,程序的編寫、調試和維護工作都會變得更加困難,因此不提倡在程序中使用多繼承
C++ 面向對象編程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。