C++學習-入門到精通【9】面向對象編程:繼承

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對象。

CommunityMemberEmployee、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繼承層次結構

之前我們已經介紹了訪問說明符publicprivate。基類的public成員在該類的體內以及程序中任何有該類或其派生類對象的句柄(即名字、引用或指針)的地方,都是可以訪問的。而基類的private成員只能在該類的體內被基類的成員和友元訪問。

下面我們來介紹protected說明符,它在publicprivate之間之間提供了一級折中的保護。為了使類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,那么派生類無法替代基類,因為基類的接口在派生類中可能不可訪問(工具函數等)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/81957.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/81957.shtml
英文地址,請注明出處:http://en.pswp.cn/web/81957.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

黑馬k8s(十七)

一&#xff1a;高級存儲 1.高級存儲-pv和pvc介紹 2.高級存儲-pv 3.高級存儲-pvc 最后一個改成5gi pvc3是沒有來綁定成功的 pv3沒有綁定 刪除pod、和pvc&#xff0c;觀察狀態&#xff1a; 4.高級存儲-pc和pvc的生命周期 二&#xff1a;配置存儲 1.配置存儲-ConfigMap 2.配…

cf每日刷題c++

目錄 Simple Repetition&#xff08;1000&#xff09; Fashionable Array&#xff08;800&#xff09; Kevin and Arithmetic(800) Permutation Warm-Up(800) Game of Mathletes(900) LRC and VIP(800) Simple Repetition&#xff08;1000&#xff09; https://codeforc…

歷年中國科學技術大學計算機保研上機真題

2025中國科學技術大學計算機保研上機真題 2024中國科學技術大學計算機保研上機真題 2023中國科學技術大學計算機保研上機真題 在線測評鏈接&#xff1a;https://pgcode.cn/school?classification1 拆分數字 題目描述 給定一個數字&#xff0c;拆分成若干個數字之和&#xff…

PHP學習筆記(十一)

類常量 可以把在類中始終保持不變的值定義為常量&#xff0c;類常量的默認可見性是public。 接口中也可以定義常量。 可以用一個變量來動態調用類&#xff0c;但該變量的值不能為關鍵字 需要注意的是類常量只為每個類分配一次&#xff0c;而不是為每個類的實例分配。 特殊的…

Nginx 性能優化全解析:從進程到安全的深度實踐

一、進程優化&#xff1a;釋放硬件性能潛力 Nginx 通過多工作進程處理請求&#xff0c;合理配置進程參數能充分利用 CPU 資源&#xff0c;避免資源浪費。 1.1 worker_processes 參數詳解 worker_processes用于設置 Nginx 工作進程的數量&#xff0c;它直接影響 Nginx 對 CP…

中國移動咪咕助力第五屆全國人工智能大賽“AI+數智創新”專項賽道開展

第五屆全國人工智能大賽由鵬城實驗室主辦&#xff0c;新一代人工智能產業技術創新戰略聯盟承辦&#xff0c;華為、中國移動、鵬城實驗室科教基金會等單位協辦&#xff0c;廣東省人工智能與機器人學會支持。 大賽發布“AI圖像編碼”、“AI增強視頻質量評價”、“AI數智創新”三大…

《 PyTorch 2.3革新:torch.compile自動生成CUDA優化內核全解》

CUDA作為NVIDIA推出的并行計算平臺和編程模型&#xff0c;為GPU計算提供了強大的支持&#xff0c;但手動優化CUDA代碼不僅需要深厚的專業知識&#xff0c;而且過程繁瑣、耗時費力&#xff0c;torch.compile的出現&#xff0c;猶如一道曙光&#xff0c;為解決這一困境帶來了全新…

mysql-mysql源碼本地調試

前言 先進行mysql源碼本地編譯&#xff1a;mysql源碼本地編譯 1.本地調試 這里以macbook為例 1.使用vscode打開mysql源碼 2.創建basedir目錄、數據目錄、配置文件目錄、配置文件 cd /Users/test/ mkdir mysqldir //創建數據目錄和配置目錄 cd mysqldir mkdir conf data …

帶你手寫React中的useReducer函數。(底層實現)

文章目錄 前言一、為什么需要 Reducer&#xff1f;二、Reducer 的核心概念1. Reducer 函數2. useReducer 鉤子 三&#xff0c;手寫react中的useReducer 總結 前言 在 React 開發中&#xff0c;useReducer 是管理復雜狀態邏輯的利器。它類似于 Redux 的簡化版&#xff0c;允許我…

用wireshark抓了個TCP通訊的包

昨兒個整理了下怎么用wireshark抓包&#xff0c;鏈接在這里&#xff1a;捋捋wireshark 今天打算抓個TCP通訊的包試試&#xff0c;整體來說比較有收獲&#xff0c;給大家匯報一下。 首先就是如何搞到可以用來演示TCP通訊的客戶端、服務端&#xff0c;問了下deepseek&#xff0c;…

運維 pgsql 安裝完后某次啟動不了

pgsql 安裝完后某次啟動不了 錯誤 data directory "/usr/local/postgresql/data" has invalid permissions 安裝成功后一直可以 后面同事敲了 chmod -R 777 /usr/local 導致不行 改到了 /usr/local/postgresql/data 權限 /usr/local/postgresql/data的權限有限…

查看·電腦安裝·的 .NET 版本

方法 一&#xff1a;使用命令提示符或 PowerShell 打開命令提示符或 PowerShell。 輸入以下命令&#xff1a;dotnet --version 按下回車鍵。 命令輸出將顯示已安裝的 .NET 版本。 方法二&#xff1a;使用 .NET Framework 控制面板 打開控制面板。 點擊“程序”。 點擊“程序…

Linux WiFi 模組使用及故障排查整理文檔

Linux WiFi 模組使用及故障排查整理文檔 1. STA 模式下 WiFi 延時不穩定問題解決方法&#xff1a; 2. Power Saving 機制說明3. AP 模式下 WiFi 設置4. RTL8821CS AP 模式下 Windows 客戶端異常斷開問題問題描述問題原因解決方案步驟 1&#xff1a;修改 dnsmasq 配置步驟 2&…

mac mini m4命令行管理員密碼設置

附上系統版本圖 初次使用命令行管理員&#xff0c;讓輸入密碼&#xff0c;無論是輸入登錄密碼還是賬號密碼&#xff0c;都是錯的&#xff0c;百思不得其解&#xff0c;去網上搜說就是登錄密碼啊 直到后來看到了蘋果官方的文檔 https://support.apple.com/zh-cn/102367 https…

棧內行為分析

棧內行為分析 一、源碼分析 我們以以下簡單的 C 程序為例&#xff0c;通過 GDB 動態調試分析函數調用過程中的棧內布局變化&#xff1a; #include <stdio.h> int add(){int a 10;int b 20;return (a b); }int main() {add();return 0; }編譯為 32 位程序&#xff1a…

老舊設備數據采集破局 AI圖像解析如何讓質檢LIMS系統煥發新生

在實驗室數字化進程中&#xff0c;大量服役超過 10 年的老舊設備成為數據采集的 “攔路虎”&#xff1a;指針式儀表盤需人工讀取、紙質原始記錄靠手工錄入、非標準接口設備數據無法自動獲取…… 某化工實驗室因 15 臺老舊設備數據采集耗時占比達 40%&#xff0c;檢測效率長期滯…

【征求意見】四川省大數據發展研究會關于對《數據資源建設費用測算標準》團體標準征求意見的通知

四川省大數據發展研究會 關于對《數據資源建設費用測算標準》團體標準征求意見的通知 各有關單位&#xff1a; 由四川省大數據發展研究會歸口、成都東契奇科技有限公司牽頭編制的《數據資源建設費用測算標準》團體標準已形成征求意見稿&#xff0c;現公開征求意見。請于2025年…

element上傳文件多選 實現文件排序

上傳文件多選排序 只上代碼 不多逼逼 這是el-elment 的文件上傳 <el-uploadaction"#"list-type"picture-card"ref"upload":accept"accept":on-change"onUploadChange":file-list"fileList":http-request&quo…

.NET 查找 DLL 的路徑順序

在 C# 中&#xff0c;[DllImport("SgCamWrapper.dll")] 這行代碼表明它會在運行時從當前可執行文件的搜索路徑中查找 SgCamWrapper.dll。具體搜索順序如下&#xff08;按優先級&#xff09;&#xff1a; ? .NET 查找 DLL 的路徑順序&#xff1a; 應用程序啟動目錄&a…

低代碼——表單生成器以form-generator為例

主要執行流程說明&#xff1a; 初始化階段 &#xff1a; 接收表單配置對象formConf深拷貝配置&#xff0c;初始化表單數據和驗證規則處理每個表單組件的默認值和特殊配置&#xff08;如文件上傳&#xff09; 渲染階段 &#xff1a; 通過render函數創建el-form根組件遞歸渲染表…