c++基礎(十一)
類的繼承的概念
繼承是在保持已有類的基礎之上構造新類的過程,而派生是指在已有類的基礎之上新增自己的特性而產生新類的過程。二者是對同一個問題的不同描述,繼承側重於保持原有類的特性,而派生側重於增加新的特性。被繼承的類(即原有類)稱為基類,派生出的類稱為派生類。基類又分為直接基類和間接基類。
繼承的目的:實現設計與代碼的重用
派生的目的:對原有的類進行改造以解決基類不能解決的問題。當基類中已有的函數核成員不能夠解決現有的問題時,那麽我們需要新增數據成員或者函數成員以解決這個問題。
單繼承時派生類的定義:
Class 派生類名:繼承方式 基類名
{
成員聲明;
}
多繼承時派生類的定義:
Class 派生類名:繼承方式1 基類名1,繼承方式2 基類名…
{
成員聲明;
}
默認情況下,派生類包含了全部基類中除了構造和析構函數之外的所有成員,不過c++11標準中也提供了使用using語句將基類的構造函數也繼承過來的功能。如果在派生類中聲明了與基類中同名的新成員,那麽基類中的成員就會被覆蓋。
繼承方式
不同繼承方式的影響主要體現在這兩個方面,一方面繼承方式會決定派生類的成員對基類成員的訪問權限,另一方面決定了派生類的對象對基類成員的訪問權限。繼承方式有三種:公有繼承,私有繼承,保護繼承。三種繼承方式的特性見下表:
私有繼承(private)
- 繼承的訪問控制
- 基類的public和protected成員:都以private身份出現在派生類中;
- 基類的private成員:不可直接訪問。
- 訪問權限
- 派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
- 通過派生類的對象:不能直接訪問從基類繼承的任何成員。
保護繼承(protected)
- 繼承的訪問控制
- 基類的public和protected成員:都以protected身份出現在派生類中;
- 基類的private成員:不可直接訪問。
- 訪問權限
- 派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
- 通過派生類的對象:不能直接訪問從基類繼承的任何成員。
protected 成員的特點與作用
- 對建立其所在類對象的模塊來說,它與 private 成員的性質相同。
- 對於其派生類來說,它與 public 成員的性質相同。
- 既實現了數據隱藏,又方便繼承,實現代碼重用。
- 如果派生類有多個基類,也就是多繼承時,可以用不同的方式繼承每個基類。
類型轉換
通過公有派生類的對象可以當作基類的對象使用,反之不然。派生類的對象可以隱含轉換為基類對象,派生類對象可以初始化基類的引用,派生類的指針可以隱含轉換為基類的指針。通過基類對象名、指針只能使用從基類繼承來的成員。不要重新定義繼承而來的非虛函數。
案例代碼:
#include <iostream>
using namespace std;
class Base1 { //基類Base1定義
public:
void display() const {
cout << "Base1::display()" << endl;
}
};
class Base2: public Base1 { //公有派生類Base2定義
public:
void display() const {
cout << "Base2::display()" << endl;
}
};
class Derived: public Base2 { //公有派生類Derived定義
public:
void display() const {
cout << "Derived::display()" << endl;
}
};
void fun(Base1 *ptr) { //參數為指向基類對象的指針
ptr->display(); //"對象指針->成員名"
}
int main() { //主函數
Base1 base1; //聲明Base1類對象
Base2 base2; //聲明Base2類對象
Derived derived; //聲明Derived類對象
fun(&base1); //用Base1對象的指針調用fun函數
fun(&base2); //用Base2對象的指針調用fun函數
fun(&derived); //用Derived對象的指針調用fun函數
return 0;
}
運行這個程序我們會發現:打印的結果一直為Base1::display(),這是為什麽呢?在後面我們會學到:如果在派生類中有與基類中同名的函數,那麽需要在基類中將這個函數定義為虛函數,在派生類中也定義為虛函數,因為非虛函數在編譯的時候是靜態的,不能夠實現動態綁定,而虛函數在編譯的時候先不去編譯具體的函數體,而是在運行的時候才確定執行哪一段函數代碼,這樣我們就可以實現通用的打印函數了。
派生類的構造函數
默認情況下,基類的構造函數不被繼承,派生類需要定義自己的構造函數。C++11中規定:可以用using語句繼承基類的構造函數,但是只能初始化從基類繼承的成員,如果派生類有新增的數據成員,那麽繼承來的構造函數是不能對新增的數據成員初始化的。語法格式:using B::B;如果不去繼承基類的構造函數,那麽派生類的新增成員需要通過派生類中定義的構造函數初始化,繼承自基類的成員會自動地調用基類的構造函數進行初始化,但是基類的構造函數的參數從哪裏傳遞進去呢?這時,我們需要通過派生類的構造函數給基類的構造函數傳遞參數。單繼承時構造函數的定義語法:
派生類名::派生類名(基類需要的形參,本類成員需要的形參):
基類名(參數表), 本類成員初始化列表
{
//其他初始化;
};
多繼承時構造函數:
派生類名::派生類名(形參表):
基類名1(參數),基類名2(參數),…基類名n(參數)
{
//其他初始化;
};
派生類與基類的構造函數
當基類有默認構造函數時
? 派生類構造函數可以不向基類構造函數傳遞參數。
? 構造派生類的對象時,基類的默認構造函數將被調用。
如需執行基類中帶參數的構造函數
? 派生類構造函數應為基類構造函數提供參數
多繼承且有對象成員時派生的構造函數定義語法
派生類名::派生類名(形參表):
基類名1(參數), 基類名2(參數), ..., 基類名n(參數),
本類成員(含對象成員)初始化列表
{
//其他初始化
};
構造函數的執行順序
第一步,調用基類構造函數,其順序按照他們被繼承時的順序;第二步,對初始化列表中的成員進行初始化,對象成員初始化時自動調用其所屬類的構造函數;第三步,執行派生類中的構造函數體中的內容。
派生類的復制構造函數
- 派生類沒有聲明復制構造函數
-
- 編譯器會自動生成一個隱含的復制構造函數,先調用基類的復制構造函數,再執行派生類新成員的復制。
- 派生類定義了復制構造函數
- 我們需要給基類的復制構造函數傳遞參數。復制構造函數只能接受一個參數,既用來初始化派生類定義的成員,也將被傳遞給基類的復制構造函數。由於基類的復制構造函數的形參是基類對象的引用,而派生類可以初始化基類的引用,所以在給基類的復制構造函數傳遞參數時,可以將派生類對象的引用作為參數。
派生類的析構函數
派生類如果需要析構函數,則我們需要自己定義析構函數,其聲明方法與無繼承關系的類的析構函數相同,析構函數不需要顯示調用。在執行析構函數時,系統會先執行派生類的析構函數,再調用基類析構函數。
訪問從基類中繼承的成員
當派生類中定義有與基類相同名稱的成員時,在沒有特別限定的情況下,通過派生類的對象使用的是派生類中的同名成員,如果要通過派生類對象訪問基類中被隱藏的成員,則需要通過基類名和作用域操作符(::)來限定。
二義性問題
當從不同的基類繼承了同名成員,但是在派生類中沒有定義同名成員,那麽通過派生類對象名.成員名或者引用名.成員名或者派生類指針->成員名來訪問成員就會存在二義性問題。解決措施是:使用類名限定。
虛基類
當派生類從多個基類派生,而這些基類又有共同基類,那麽這個派生類中就會存在多個相同的成員,在訪問派生類的成員時就會產生冗余,這些冗余的存在不僅僅是占用了空間,並且很可能會導致不一致性。虛基類的存在就是為了解決這個問題。
虛基類的聲明:class B1:virtual public B。聲明了虛基類後,多繼承情況下就不會發生對同一基類繼承多次而產生的二義性問題,並且為最遠的派生類提供唯一的基類成員,而不產生多次復制。需要註意的是:在第一級繼承時就需要將共同基類設計為虛基類。
虛基類及其派生類構造函數
- 建立對象時所指定的類稱為最遠派生類。
- 虛基類的成員是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化的。
- 在整個繼承結構中,直接或間接繼承虛基類的所有派生類,都必須在構造函數的成員初始化表中為虛基類的構造函數列出參數。如果未列出,則表示調用該虛基類的默認構造函數。
- 在建立對象時,只有最遠派生類的構造函數調用虛基類的構造函數,其他類對虛基類構造函數的調用被忽略。
c++基礎(十一)