C++學習-入門到精通【9】面向對象編程:繼承
目錄
- C++學習-入門到精通【9】面向對象編程:繼承
- 一、基類與派生類
- CommunityMember類的繼承層次結構
- 如何定義一個派生類呢
- 二、基類和派生類間的關系
- 1.創建并使用類CommissionEmployee
- 2.不使用繼承創建類BasePlusCommissionEmployee
- 3.創建CommissionEmployee-BasePlusCommissionEmployee繼承層次結構
- 4.使用protected數據的CommissionEmployee-BasePlusCommissionEmployee繼承層次結構
- 5.使用private數據的CommissionEmployee-BasePlusCommissionEmployee繼承層次結構
- 三、派生類中的構造函數和析構函數
- C++11:繼承基類的構造函數
- 四、public、protected和private繼承
一、基類與派生類
我們先從下表可以很清楚的看出基類和派生類之間的關系
基類 | 派生類 |
---|---|
學生 | 本科生、研究生 |
形狀 | 圓、三角形、矩形、球體、立方體 |
貸款 | 汽車貸款、住房貸款、抵押貸款 |
雇員 | 教職員工、后勤人員 |
賬戶 | 支票賬戶、儲蓄賬戶 |
可以看到上圖中的基類更加通用,派生類更加具體。
繼承關系構成了類的層次結構。基類和它的派生類間存在這種層次關系。雖然類可以獨立存在,但是一旦投入到繼承關系中,它們和其他的類就有了關聯。在繼承關系中,類作為基類、或是派生類、或以二者兼有的身份出現。
CommunityMember類的繼承層次結構
下面設計一個簡單的繼承層次結構,這個層次有5層。一個大學群體有數以千計的CommunityMember對象。
這些CommunityMember
對象由Employee
(雇員)、Student
(學生)和Alumnus
(校友)對象組成。Employee對象可能Faculty
(教職員工)或者Staff
(后勤人員),一個Faculty對象可能是Administrator
(行政管理人員)或者Teacher
(教師)對象。有些Administrator對象同時也可能是Teacher對象。所以這里我們使用多重繼承來設計類AdministratorTeacher
。多重繼承會在后續章節詳細介紹,但是并不建議使用多重繼承。
下面是先前設計的CommunityMember類的繼承層次結構的UML圖。
上圖中每個箭頭表示一個is-a
關系(繼承)(同時還有has-a
關系表示組成)。當我們跟蹤這些類層次的箭頭時,可以知道Employee
對象是CommunityMember
對象。
CommunityMember
是Employee、Student和Alumnus
的直接基類。是圖中其他類的間接基類(在類層次中被繼承了兩層或以上)。
如何定義一個派生類呢
那么我們應該如何將一個類定義為另一個類的派生(從另一個類繼承)呢?以以下形式進行定義,我們可以指定類Employee是由類CommunityMember派生而來的,類Employee定義的開始部分有:
class Employee : public CommunityMember
這是最常用的繼承形式——public繼承的一個例子。本文后續部分會繼續討論private繼承和protected繼承。
在各種形式的繼承中,基類的private成員都不能被它的派生類直接訪問,但是這些private基類成員仍被繼承(派生類也包含它們)。在public繼承中,基類的所有其他成員在成為派生類的成員時仍保持其原始的成員訪問權限(比如,基類的public成員在派生類中仍是public成員,protected成員仍是protected成員)。那么派生類如何訪問繼承來的private成員呢——使用從基類繼承來的成員函數來訪問。注意:友元函數無法繼承。
二、基類和派生類間的關系
這一節將通過一個雇員的繼承層次結構來討論基類和派生類之間的關系。這個層次結構包含了一個公司的工資發放系統所涉及的多種類型的雇員。傭金雇員(CommisisionEmployee)將被表示為基類的對象,他們的薪水完全由銷售提成;帶底薪傭金雇員(base-salaried commission employee)將被表示為派生類的對象,他們的薪水上底薪和銷售提成組成。
下面我們將用5個例子來循序漸進地討論傭金雇員和帶底薪傭金雇員之間的關系。
1.創建并使用類CommissionEmployee
CommissionEmployee.h
#include <string>class CommissionEmployee
{
public:// 參數分別為firstName、lastName、socialSecurityNumber、grossSales和commnissionRateCommissionEmployee(const std::string&, const std::string&,const std::string&, double = 0.0, double = 0.0);void setFirstName(const std::string&);std::string getFirstName() const;void setLastName(const std::string&);std::string getLastName() const;void setSocialSecurityNumber(const std::string&);std::string getSocialSecurityNumber() const;void setGrossSales(double);double getGrossSales() const;void setCommissionRate(double);double getCommissionRate() const;double earnings() const; // 計算工資void print() const; // 輸出CommissionEmployee的成員
private:std::string firstName;std::string lastName; std::string socialSecurityNumber; // 社保號碼double grossSales; // 銷售總額double commissionRate; // 提成比例
};
CommissionEmployee.cpp
#include <iostream>
#include <string>
#include <stdexcept>
#include "CommissionEmployee.h"using namespace std;CommissionEmployee::CommissionEmployee(const string& first, const string& last,const string& ssn, double sales, double rate): firstName(first), lastName(last),socialSecurityNumber(ssn), grossSales(sales), commissionRate(rate)
{// 也可以不使用初始化列表進行初始化,在構造函數體中調用set成員函數來進行初始化// 這樣可以對這些值的有效性進行檢查
}void CommissionEmployee::setFirstName(const string& first)
{// 還可以增加對名的有效性的限制firstName = first;
}string CommissionEmployee::getFirstName() const
{return firstName;
}void CommissionEmployee::setLastName(const string& last)
{// 還可以增加對姓的有效性的限制lastName = last;
}string CommissionEmployee::getLastName() const
{return lastName;
}void CommissionEmployee::setSocialSecurityNumber(const string& ssn)
{// 還可以增加對姓的有效性的限制socialSecurityNumber = ssn;
}string CommissionEmployee::getSocialSecurityNumber() const
{return socialSecurityNumber;
}void CommissionEmployee::setGrossSales(double sales)
{if (sales >= 0.0){grossSales = sales;}else{throw invalid_argument("Gross Sales must be greater than 0.0.");}
}double CommissionEmployee::getGrossSales() const
{return grossSales;
}void CommissionEmployee::setCommissionRate(double rate)
{if (rate > 0.0 && rate < 1.0){commissionRate = rate;}else{throw invalid_argument("Commission rate must be greater than 0.0 and smaller than 1.0.");}
}double CommissionEmployee::getCommissionRate() const
{return commissionRate;
}double CommissionEmployee::earnings() const
{return commissionRate * grossSales;
}void CommissionEmployee::print() const
{cout << "commission employee: " << firstName << ' ' << lastName<< "\nsocial security number: " << socialSecurityNumber<< "\ngrossSales: " << grossSales<< "\ncommission rate: " << commissionRate;
}
test.cpp
#include <iostream>
#include <iomanip>
#include "CommissionEmployee.h"
using namespace std;int main()
{// 實例化一個CommissionEmployee對象CommissionEmployee employee("Peter", "Griffin", "111-22-3333", 10000, .06);// 設置浮點數格式,固定保留小數點后兩位cout << fixed << setprecision(2);// 調用get成員函數獲取employee的信息cout << "Employee information obtained by get functions: \n"<< "\nFirst name is " << employee.getFirstName()<< "\nLast name is " << employee.getLastName()<< "\nSocial security number is " << employee.getSocialSecurityNumber()<< "\nGross sales is " << employee.getGrossSales()<< "\nCommission rate is " << employee.getCommissionRate() << endl;// 設置employee的總銷售額和提成比例employee.setGrossSales(10000);employee.setCommissionRate(.1);cout << "\nUpdated employee information output by print function: \n\n";employee.print();// 打印employee的收入cout << "\n\nEmployee`s earings is $" << employee.earnings() << endl;
}
運行結果:
2.不使用繼承創建類BasePlusCommissionEmployee
下面創建一個全新的類,比上面的類多了一個數據成員:baseSalary(底薪)。
BasePlusCommissionEmployee.h
#include <string>class BasePlusCommissionEmployee
{
public:// 參數分別為firstName、lastName、socialSecurityNumber、grossSales、commnissionRate和baseSalaryBasePlusCommissionEmployee(const std::string&, const std::string&,const std::string&, double = 0.0, double = 0.0, double = 0.0);void setFirstName(const std::string&);std::string getFirstName() const;void setLastName(const std::string&);std::string getLastName() const;void setSocialSecurityNumber(const std::string&);std::string getSocialSecurityNumber() const;void setGrossSales(double);double getGrossSales() const;void setCommissionRate(double);double getCommissionRate() const;void setBaseSalary(double);double getBaseSalary() const;double earnings() const; // 計算工資void print() const; // 輸出CommissionEmployee的成員
private:std::string firstName;std::string lastName;std::string socialSecurityNumber; // 社保號碼double grossSales; // 銷售總額double commissionRate; // 提成比例double baseSalary; // 底薪
};
BasePlusCommissionEmployee.cpp
#include <iostream>
#include <string>
#include <stdexcept>
#include "BasePlusCommissionEmployee.h"using namespace std;BasePlusCommissionEmployee::BasePlusCommissionEmployee(const string& first, const string& last,const string& ssn, double sales, double rate, double base): firstName(first), lastName(last),socialSecurityNumber(ssn), grossSales(sales), commissionRate(rate),baseSalary(base)
{// 也可以不使用初始化列表進行初始化,在構造函數體中調用set成員函數來進行初始化// 這樣可以對這些值的有效性進行檢查
}void BasePlusCommissionEmployee::setFirstName(const string& first)
{// 還可以增加對名的有效性的限制firstName = first;
}string BasePlusCommissionEmployee::getFirstName() const
{return firstName;
}void BasePlusCommissionEmployee::setLastName(const string& last)
{// 還可以增加對姓的有效性的限制lastName = last;
}string BasePlusCommissionEmployee::getLastName() const
{return lastName;
}void BasePlusCommissionEmployee::setSocialSecurityNumber(const string& ssn)
{// 還可以增加對姓的有效性的限制socialSecurityNumber = ssn;
}string BasePlusCommissionEmployee::getSocialSecurityNumber() const
{return socialSecurityNumber;
}void BasePlusCommissionEmployee::setGrossSales(double sales)
{if (sales >= 0.0){grossSales = sales;}else{throw invalid_argument("Gross Sales must be greater than 0.0.");}
}double BasePlusCommissionEmployee::getGrossSales() const
{return grossSales;
}void BasePlusCommissionEmployee::setCommissionRate(double rate)
{if (rate > 0.0 && rate < 1.0){commissionRate = rate;}else{throw invalid_argument("Commission rate must be greater than 0.0 and smaller than 1.0.");}
}double BasePlusCommissionEmployee::getCommissionRate() const
{return commissionRate;
}void BasePlusCommissionEmployee::setBaseSalary(double base)
{if (base > 0.0){baseSalary = base;}else{throw invalid_argument("base must be greater than 0.0.");}
}double BasePlusCommissionEmployee::getBaseSalary() const
{return baseSalary;
}double BasePlusCommissionEmployee::earnings() const
{return commissionRate * grossSales + baseSalary;
}void BasePlusCommissionEmployee::print() const
{cout << "commission employee: " << firstName << ' ' << lastName<< "\nsocial security number: " << socialSecurityNumber<< "\ngrossSales: " << grossSales<< "\ncommission rate: " << commissionRate<< "\nbase salary: " << baseSalary;
}
對比類BasePlusCommissionEmployee和類CommissionEmployee,我們能夠貶責這個新類的定義中只有大部分都是重復代碼,只有涉及到新加入的數據成員的地方才有一些變化。
使用繼承時,類層次結構中所有類共同的數據成員和成員函數應該在基類中聲明。當需要對這些共同特征進行修改時,只需在基類中進行修改即可,派生類會繼承這些修改。如果不使用繼承機制,而是采用上面的方法,當這些共同部分出錯時,我們就需要對所有包含有問題代碼副本的源文件進行修改。
3.創建CommissionEmployee-BasePlusCommissionEmployee繼承層次結構
現在我們采用繼承的方式來創建BasePlusCommissionEmployee類。它由CommissionEmployee派生而來。一個BasePlusCommissionEmployee類的對象同時也是一個CommissionEmployee類的對象。但是BasePlusCommissionEmployee中除了具有所有CommissionEmployee類對象具有的成員之外,還有一個特有的數據成員baseSalary。
使用public繼承,繼承了基類中除構造函數、析構函數和重載的賦值運算符函數(如果有的話)之外的所有成員,每個類都會提供特定于自己的構造函數和析構函數。
BasePlusCommissionEmployee.h
#include "CommissionEmployee.h"class BasePlusCommissionEmployee : public CommissionEmployee
{
public:BasePlusCommissionEmployee(const std::string&, const std::string&,const std::string&, double = 0.0, double = 0.0, double = 0.0);void setBaseSalary(double);double getBaseSalary() const;// 收入發生變化,所以earnings成員函數需要重寫double earnings() const;// 同樣的打印函數一樣需要重寫void print() const;
private:double baseSalary;
};
BasePlusCommissionEmployee.cpp
#include <iostream>
#include <stdexcept>
#include "BasePlusCommissionEmployee.h"
using namespace std;BasePlusCommissionEmployee::BasePlusCommissionEmployee(const string & first, const string& last, const string& ssn,double sales, double rate, double salary): CommissionEmployee(first, last, ssn, sales, rate)
{setBaseSalary(salary);
}void BasePlusCommissionEmployee::setBaseSalary(double salary)
{if (salary >= 0.0){baseSalary = salary;}else{throw invalid_argument("Base salary must be greater than 0.0.");}
}double BasePlusCommissionEmployee::getBaseSalary() const
{return baseSalary;
}double BasePlusCommissionEmployee::earnings() const
{return baseSalary + commissionRate * grossSales;
}void BasePlusCommissionEmployee::print() const
{cout << "commission employee: " << firstName << ' ' << lastName<< "\nsocial security number: " << socialSecurityNumber<< "\ngrossSales: " << grossSales<< "\ncommission rate: " << commissionRate<< "\nbase salary: " << baseSalary;
}
編譯結果:
上面的構造函數中引入了基類初始化器語法,使用成員初始化器將參數傳遞給基類的構造函數。C++要求派生類調用其基類的構造函數來初始化繼承到派生類的基類的數據成員。為此在該類的構造函數函數中在它的成員初始化器中將顯式的調用了基類的構造函數。如果類BasePlusCommissionEmployee的構造函數沒有顯式地調用基類的構造函數,C++將會嘗試隱式調用基類的默認構造函數,但是該類并沒有提供默認的構造函數,且顯式的提供了構造函數,所以編譯器也不會提供一個無參的默認構造函數,那么就會導致錯誤。
派生類的構造函數只能通過成員初始化器來初始化基類中的數據成員
常見錯誤:在派生類構造函數調用其基類的構造函數時,如果傳遞給基類構造函數的參數,它的個數和類型與基類構造函數不符,將導致編譯錯誤。
注意:大家肯定發現了在派生類中我們又重新定義了earnings和print函數,因為基類中的這兩個函數并沒有聲明為虛函數,所以這里是進行了函數隱藏,派生類中新定義的函數隱藏了基類中的相同函數原型的函數,派生類對象直接調用該函數會調用派生類中定義的函數,如果想要調用基類中的同名函數需要顯式的指定作用域,例如:Commission::earnings()
除了上面的初始化問題外,可以看到上面的類在編譯是出現了錯誤,無法訪問基類的私有成員。那么我們在派生類中要如何訪問基類的私有數據成員呢?公有的成員函數(基類中的私有成員函數同樣的也不能調用)。還有沒有其他方法呢?有的,兄弟有的。使用protected成員訪問訪問說明符。
4.使用protected數據的CommissionEmployee-BasePlusCommissionEmployee繼承層次結構
之前我們已經介紹了訪問說明符public
和private
。基類的public成員在該類的體內以及程序中任何有該類或其派生類對象的句柄(即名字、引用或指針)的地方,都是可以訪問的。而基類的private成員只能在該類的體內被基類的成員和友元訪問。
下面我們來介紹protected
說明符,它在public
和private
之間之間提供了一級折中的保護。為了使類BasePlusCommissionEmployee能夠直接訪問類CommissionEmployee的數據成員,可以在基類中將它們聲明為protected。基類中的protected成員即可以在基類的體內被基類的成員和友元訪問,又可以被由基類派生的任何類的成員或友元訪問。
使用protected數據定義基類CommissionEmployee
將CommissionEmployee.h進行如下的修改
#include <string>class CommissionEmployee
{
public:// 參數分別為firstName、lastName、socialSecurityNumber、grossSales和commnissionRateCommissionEmployee(const std::string&, const std::string&,const std::string&, double = 0.0, double = 0.0);void setFirstName(const std::string&);std::string getFirstName() const;void setLastName(const std::string&);std::string getLastName() const;void setSocialSecurityNumber(const std::string&);std::string getSocialSecurityNumber() const;void setGrossSales(double);double getGrossSales() const;void setCommissionRate(double);double getCommissionRate() const;double earnings() const; // 計算工資void print() const; // 輸出CommissionEmployee的成員
protected:std::string firstName;std::string lastName; std::string socialSecurityNumber; // 社保號碼double grossSales; // 銷售總額double commissionRate; // 提成比例
};
測試修改后的BasePlusCommissionEmployee類
test.cpp
#include <iostream>
#include <iomanip>
#include "BasePlusCommissionEmployee.h"
using namespace std;int main()
{// 實例化一個BasePlusCommissionEmployee對象BasePlusCommissionEmployee employee("Chris", "Griffin", "333-22-1111", 8000, .06, 300);// 設置浮點數格式,固定保留小數點后兩位cout << fixed << setprecision(2);// 調用get成員函數獲取employee的信息cout << "Employee information obtained by get functions: \n"<< "\nFirst name is " << employee.getFirstName()<< "\nLast name is " << employee.getLastName()<< "\nSocial security number is " << employee.getSocialSecurityNumber()<< "\nGross sales is " << employee.getGrossSales()<< "\nCommission rate is " << employee.getCommissionRate() << "\nBase salary is " << employee.getBaseSalary() << endl;// 設置employee的總銷售額和提成比例employee.setGrossSales(1000);employee.setCommissionRate(.08);cout << "\nUpdated employee information output by print function: \n\n";employee.print();// 打印employee的收入cout << "\n\nEmployee`s earings is $" << employee.earnings() << endl;
}
運行結果:
使用protected數據的一些注意事項
因為派生類可以直接訪問基類中聲明為protected
的數據成員,所以派生類可以免去調用函數來設置或獲取這些數據成員的額外開銷。所以繼承protected數據成員會使程序的性能稍稍提升。
但是,使用protected數據將會產生以下兩個問題:
- 派生類對象不必使用成員函數設置protected數據成員的值,因此,派生類很容易將無效的值賦給基類的protected數據,導致對象處于不一致的狀態。例如,為基類的數據成員grossSales賦一個負值。
- 派生類成員函數很可能太依賴基類的實現,派生類應該只依賴于基類提供的服務(非private的成員函數),而不是基類的實現。在派生類中使用基類的protected數據時,如果修改了基類的實現,那么很有可能還需要修改所有直接使用了protected數據成員的派生類,這極大的降低了代碼的可維護性。
5.使用private數據的CommissionEmployee-BasePlusCommissionEmployee繼承層次結構
下面對之前使用的代碼進行優化:
將基類CommissionEmployee中的數據成員改回private的數據成員。
CommissionEmployee.cpp,修改了構造函數、earnings和print函數的實現
#include <iostream>
#include <string>
#include <stdexcept>
#include "CommissionEmployee.h"using namespace std;CommissionEmployee::CommissionEmployee(const string& first, const string& last,const string& ssn, double sales, double rate): firstName(first), lastName(last),socialSecurityNumber(ssn)
{// 調用成員函數來設置銷售總額和提成比例,在設置之前進行有效性檢查setGrossSales(sales);setCommissionRate(rate);
}void CommissionEmployee::setFirstName(const string& first)
{// 還可以增加對名的有效性的限制firstName = first;
}string CommissionEmployee::getFirstName() const
{return firstName;
}void CommissionEmployee::setLastName(const string& last)
{// 還可以增加對姓的有效性的限制lastName = last;
}string CommissionEmployee::getLastName() const
{return lastName;
}void CommissionEmployee::setSocialSecurityNumber(const string& ssn)
{// 還可以增加對姓的有效性的限制socialSecurityNumber = ssn;
}string CommissionEmployee::getSocialSecurityNumber() const
{return socialSecurityNumber;
}void CommissionEmployee::setGrossSales(double sales)
{if (sales >= 0.0){grossSales = sales;}else{throw invalid_argument("Gross Sales must be greater than 0.0.");}
}double CommissionEmployee::getGrossSales() const
{return grossSales;
}void CommissionEmployee::setCommissionRate(double rate)
{if (rate > 0.0 && rate < 1.0){commissionRate = rate;}else{throw invalid_argument("Commission rate must be greater than 0.0 and smaller than 1.0.");}
}double CommissionEmployee::getCommissionRate() const
{return commissionRate;
}double CommissionEmployee::earnings() const
{// 調用get成員函數來獲取數據成員// 當修改這些數據成員的名字時,不需要修改該函數的實現return getCommissionRate() * getGrossSales();
}void CommissionEmployee::print() const
{// 同樣的調用成員函數來訪問數據成員cout << "commission employee: " << getFirstName() << ' ' << getLastName()<< "\nsocial security number: " << getSocialSecurityNumber()<< "\ngrossSales: " << getGrossSales()<< "\ncommission rate: " << getCommissionRate();
}
BasePlusCommissionEmployee.cpp
#include <iostream>
#include <stdexcept>
#include "BasePlusCommissionEmployee.h"
using namespace std;BasePlusCommissionEmployee::BasePlusCommissionEmployee(const string & first, const string& last, const string& ssn,double sales, double rate, double salary): CommissionEmployee(first, last, ssn, sales, rate)
{setBaseSalary(salary);
}void BasePlusCommissionEmployee::setBaseSalary(double salary)
{if (salary >= 0.0){baseSalary = salary;}else{throw invalid_argument("Base salary must be greater than 0.0.");}
}double BasePlusCommissionEmployee::getBaseSalary() const
{return baseSalary;
}double BasePlusCommissionEmployee::earnings() const
{// 使用作用域分辨運算符指定基類的作用域來調用在派生類中被隱藏的基類的earnings函數return getBaseSalary() + CommissionEmployee::earnings();
}void BasePlusCommissionEmployee::print() const
{cout << "base-salaried ";CommissionEmployee::print();cout << "\nbase salary: " << getBaseSalary();
}
提示
利用成員函數訪問數據成員的值可能比直接訪問這些數據稍慢些。但是,如今優化的編譯器經過精心設置,可以隱式地執行許多優化工作(例如,把設置和獲取成員函數的調用進行內聯)。所以,程序員應該致力于編寫出符合軟件工程原則的代碼,而將優化問題留給編譯器去做。一條好的準則是:不要懷疑編譯器
。
可以使用上面相同的測試代碼來進行測試,看看是否輸出相同的結果。
三、派生類中的構造函數和析構函數
從上面的例子中我們可以看出,當我們要實例化一個派生類的對象時,會調用一連串的構造函數。其中派生類的構造函數在執行它自己的任務之前,先顯式地(通過成員初始化器)或隱式地(調用基類的默認構造函數)調用其直接基類的構造函數。同樣如果該基類也是從其他類派生而來的,則該基類構造函數需要它的基類的構造函數。在這個構造函數調用鏈中,調用的最后一個構造函數是在繼承層次結構中最頂層的構造函數。
例如,在我們前面研究的CommissionEmployee-BasePlusCommissionEmployee繼承層次結構中,當程序要創建類BasePlusComissionEmployee的對象時,將調用類CommissionEmployee的構造函數。因為類CommissionEmployee是這個繼承層次結構中的基類,所以它的構造函數先執行,所初始化的CommissionEmployee的private數據成員同時也是類BasePlusCommissionEmployee對象的一部分。當類CommissionEmployee的構造函數執行完畢時,它將程序控制權還給類BasePlusCommissionEmployee的構造函數,后者再初始化該類對象的數據成員baseSalary。
派生類的對象在創建時,最先執行的是繼承層次結構頂層的類的構造函數。
當銷毀派生類的對象時,程序將調用對象的析構函數。這又將展開一連串的析構函數的調用,其中派生類的析構函數,派生類的直接基類、派生類的間接基類及類的成員的析構函數,會按照它們的構造函數執行次序相反順序依次執行。當調用派生類對象的析構函數時,該析構函數執行其任務,然后調用繼承層次結構中上一層基類的析構函數。重復進行這一過程,直到繼承結構頂層的最后一個基類的析構函數被調用。之后,該對象就從內存中刪除了。
構造函數和析構函數的執行順序
假設我們創建一個派生類對象,這個派生類及其基類中都包含(通過組成)其他類的對象。當這個派生類的對象被創建時,首先執行的是基類成員對象的構造函數,然后執行基類構造函數的函數體,接著執行派生類成員對象的構造函數,最后執行派生類構造函數的函數體。派生類對象析構函數函數的調用與相應的構造函數的調用順序正好相反。
C++11:繼承基類的構造函數
C++11中,派生類可以繼承基類的構造函數。只要在派生類中定義中包含下面的聲明:
using BaseClass::BaseClass;
上述聲明中的BaseClass表示基類的名稱。除了下列的少數例外情況外,對于基類的每個構造函數,編譯器都生成一個派生類構造函數,它調用相應的基類構造函數。生成的構造函數對派生類新增的數據成員只執行默認的初始化。在繼承構造函數時:
- 默認情況下,每個繼承而來的構造函數和它相應基類構造函數具有相同的訪問級別(public、protected和private);
- 缺省構造函數、拷貝構造函數和移動構造函數不被繼承;
- 如果在基類構造函數的原型中放置
= delete
而在基類中刪除這個構造函數,那么在派生類中相應的構造函數也被刪除; - 如果派生類沒有顯式地定義構造函數,那么編譯器在派生類生成一個默認構造函數,即使它從基類繼承了其他構造函數;
- 如果顯式地定義在派生類的構造函數和基類的構造函數具有相同的形參列表,那么該基類的構造函數不被繼承;
- 基類構造函數的默認實參是不被繼承的,編譯器會在派生類中生成重載的構造函數。例如,如果基類聲明了如下的構造函數:
BaseClass(int = 0, double = 0.0);
那么編譯器生成如下的兩個沒有默認實參的派生類構造函數:
DerivedClass(int);
DerivedClass(int, double);
這兩個構造函數都調用這個指定默認實參的BaseClass構造函數。(不指定參數時,調用派生類DerivedClass的默認構造函數,如果沒有顯式定義,編譯器自動生成,調用基類的默認構造函數進行初始化,所以執行起來效果相同。)
四、public、protected和private繼承
從基類派生出一個類時,繼承基類的方式有三種,即public繼承、protected繼承和private繼承。下圖總結了每種繼承方式下,在派生類中對基類成員的可訪問性。
當采用public繼承派生一個類時,基類的public成員成為派生類的public成員,基類的protected成員成為派生類中的protected成員,基類的private成員對派生類隱藏;
當采用protected繼承派生一個類時,基類的public和protected成員都變成派生類的protected成員,基類的private成員對派生類隱藏;
當采用private繼承派生一個類時,基類的public和protected成員都變成派生類中的private成員,基類的private成員對派生類隱藏;
派生類永遠不能直接訪問基類的private成員,繼承會將保護級別低于它的成員升級到保護級別相同(protected繼承會將public的成員升級成protected,private繼承是將成員升級成private,它仍在派生類中可以被訪問,所以可以被友元函數訪問。而private成員不論什么繼承方式,都是被隱藏,無法通過友元函數訪問。)
private和protected繼承不滿足 “is-a” 的關系
“is-a”
關系的核心在派生類對象可以被當作基類對象使用,如果繼承方式不是public,那么派生類無法替代基類,因為基類的接口在派生類中可能不可訪問(工具函數等)。