子類的構造函數


基類的成員函數可以被繼承,可以通過派生類的對象訪問,但這僅僅指的是普通的成員函數,類的構造函數不能被繼承。構造函數不能被繼承是有道理的,因爲即使繼承了,它的名字和派生類的名字也不一樣,不能成爲派生類的構造函數,當然更不能成爲普通的成員函數。

在設計派生類時,對繼承過來的成員變量的初始化工作也要由派生類的構造函數完成,但是大部分基類都有 private 屬性的成員變量,它們在派生類中無法訪問,更不能使用派生類的構造函數來初始化。

這種矛盾在C++繼承中是普遍存在的,解決這個問題的思路是:在派生類的構造函數中調用基類的構造函數。

下面的例子展示瞭如何在派生類的構造函數中調用基類的構造函數: 

#include<iostream>
using namespace std;

//基類People
class People{
protected:
    char *m_name;
    int m_age;
public:
    People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}

//派生類Student
class Student: public People{
private:
    float m_score;
public:
    Student(char *name, int age, float score);
    void display();
};
//People(name, age)就是調用基類的構造函數
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<"。"<<endl;
}

int main(){
    Student stu("小明", 16, 90.5);
    stu.display();

    return 0;
}

運行結果爲:
小明的年齡是16,成績是90.5。

請注意第 23 行代碼:


  1. Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }

People(name, age)就是調用基類的構造函數,並將 name 和 age 作爲實參傳遞給它,m_score(score)是派生類的參數初始化表,它們之間以逗號,隔開。

也可以將基類構造函數的調用放在參數初始化表後面:

  1. Student::Student(char *name, int age, float score): m_score(score), People(name, age){ }
Student::Student(char *name, int age, float score): m_score(score), People(name, age){ }

但是不管它們的順序如何,派生類構造函數總是先調用基類構造函數再執行其他代碼(包括參數初始化表以及函數體中的代碼),總體上看和下面的形式類似:


  1. Student::Student(char *name, int age, float score){
  2. People(name, age);
  3. m_score = score;
  4. }
Student::Student(char *name, int age, float score){
    People(name, age);
    m_score = score;
}

當然這段代碼只是爲了方便大家理解,實際上這樣寫是錯誤的,因爲基類構造函數不會被繼承,不能當做普通的成員函數來調用。換句話說,只能將基類構造函數的調用放在函數頭部,不能放在函數體中。

另外,函數頭部是對基類構造函數的調用,而不是聲明,所以括號裏的參數是實參,它們不但可以是派生類構造函數參數列表中的參數,還可以是局部變量、常量等,例如:


  1. 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 的構造函數。 

基類構造函數調用規則

事實上,通過派生類創建對象時必須要調用基類的構造函數,這是語法規定。換句話說,定義派生類構造函數時最好指明基類構造函數;如果不指明,就調用基類的默認構造函數(不帶參數的構造函數);如果沒有默認構造函數,那麼編譯失敗。請看下面的例子:


  1. #include <iostream>
  2. using namespace std;
  3.  
  4. //基類People
  5. class People{
  6. public:
  7. People(); //基類默認構造函數
  8. People(char *name, int age);
  9. protected:
  10. char *m_name;
  11. int m_age;
  12. };
  13. People::People(): m_name("xxx"), m_age(0){ }
  14. People::People(char *name, int age): m_name(name), m_age(age){}
  15.  
  16. //派生類Student
  17. class Student: public People{
  18. public:
  19. Student();
  20. Student(char*, int, float);
  21. public:
  22. void display();
  23. private:
  24. float m_score;
  25. };
  26. Student::Student(): m_score(0.0){ } //派生類默認構造函數
  27. Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
  28. void Student::display(){
  29. cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<"。"<<endl;
  30. }
  31.  
  32. int main(){
  33. Student stu1;
  34. stu1.display();
  35.  
  36. Student stu2("小明", 16, 90.5);
  37. stu2.display();
  38.  
  39. return 0;
  40. }
#include <iostream>
using namespace std;

//基類People
class People{
public:
    People();  //基類默認構造函數
    People(char *name, int age);
protected:
    char *m_name;
    int m_age;
};
People::People(): m_name("xxx"), m_age(0){ }
People::People(char *name, int age): m_name(name), m_age(age){}

//派生類Student
class Student: public People{
public:
    Student();
    Student(char*, int, float);
public:
    void display();
private:
    float m_score;
};
Student::Student(): m_score(0.0){ }  //派生類默認構造函數
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<"。"<<endl;
}

int main(){
    Student stu1;
    stu1.display();

    Student stu2("小明", 16, 90.5);
    stu2.display();

    return 0;
}

運行結果:
xxx的年齡是0,成績是0。
小明的年齡是16,成績是90.5。

創建對象 stu1 時,執行派生類的構造函數Student::Student(),它並沒有指明要調用基類的哪一個構造函數,從運行結果可以很明顯地看出來,系統默認調用了不帶參數的構造函數,也就是People::People()

創建對象 stu2 時,執行派生類的構造函數Student::Student(char *name, int age, float score),它指明瞭基類的構造函數。

在第 27 行代碼中,如果將People(name, age)去掉,也會調用默認構造函數,第 37 行的輸出結果將變爲:
xxx的年齡是0,成績是90.5。

如果將基類 People 中不帶參數的構造函數刪除,那麼會發生編譯錯誤,因爲創建對象 stu1 時需要調用 People 類的默認構造函數, 而 People 類中已經顯式定義了構造函數,編譯器不會再生成默認的構造函數。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章