深入解析C++中派生類的構造函數
基類的構造函數不能被繼承,在聲明派生類時,對繼承過來的成員變量的初始化工作也要由派生類的構造函數來完成。所以在設計派生類的構造函數時,不僅要考慮派生類新增的成員變量,還要考慮基類的成員變量,要讓它們都被初始化。
解決這個問題的思路是:在執(zhí)行派生類的構造函數時,調用基類的構造函數。
下面的例子展示了如何在派生類的構造函數中調用基類的構造函數。
#include<iostream>
using namespace std;
//基類
class People{
protected:
char *name;
int age;
public:
People(char*, int);
};
People::People(char *name, int age): name(name), age(age){}
//派生類
class Student: public People{
private:
float score;
public:
Student(char*, int, float);
void display();
};
//調用了基類的構造函數
Student::Student(char *name, int age, float score): People(name, age){
this->score = score;
}
void Student::display(){
cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
}
int main(){
Student stu("小明", 16, 90.5);
stu.display();
return 0;
}
運行結果為:
小明的年齡是16,成績是90.5
請注意代碼第23行:
Student::Student(char *name, int age, float score): People(name, age)
這是派生類 Student 的構造函數的寫法。冒號前面是派生類構造函數的頭部,這和我們以前介紹的構造函數的形式一樣,但它的形參列表包括了初始化基類和派生類的成員變量所需的數據;冒號后面是對基類構造函數的調用,這和普通構造函數的參數初始化表非常類似。
實際上,你可以將對基類構造函數的調用和參數初始化表放在一起,如下所示:
Student::Student(char *name, int age, float score): People(name, age), score(score){}
基類構造函數和初始化表用逗號隔開。
需要注意的是:冒號后面是對基類構造函數的調用,而不是聲明,所以括號里的參數是實參,它們不但可以是派生類構造函數總參數表中的參數,還可以是局部變量、常量等。如下所示:
Student::Student(char *name, int age, float score): People("李磊", 20)
基類構造函數調用規(guī)則
事實上,通過派生類創(chuàng)建對象時必須要調用基類的構造函數,這是語法規(guī)定。也就是說,定義派生類構造函數時最好指明基類構造函數;如果不指明,就調用基類的默認構造函數(不帶參數的構造函數);如果沒有默認構造函數,那么編譯失敗。
請看下面的例子:
#include<iostream>
using namespace std;
//基類
class People{
protected:
char *name;
int age;
public:
People();
People(char*, int);
};
People::People(){
this->name = "xxx";
this->age = 0;
}
People::People(char *name, int age): name(name), age(age){}
//派生類
class Student: public People{
private:
float score;
public:
Student();
Student(char*, int, float);
void display();
};
Student::Student(){
this->score = 0.0;
}
Student::Student(char *name, int age, float score): People(name, age){
this->score = score;
}
void Student::display(){
cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
}
int main(){
Student stu1;
stu1.display();
Student stu2("小明", 16, 90.5);
stu2.display();
return 0;
}
運行結果:
xxx的年齡是0,成績是0 小明的年齡是16,成績是90.5
創(chuàng)建對象 stu1 時,執(zhí)行派生類的構造函數 Student::Student(),它并沒有指明要調用基類的哪一個構造函數,從運行結果可以很明顯地看出來,系統默認調用了不帶參數的構造函數,也就是 People::People()。
創(chuàng)建對象 stu2 時,執(zhí)行派生類的構造函數 Student::Student(char *name, int age, float score),它指明了基類的構造函數。
在第31行代碼中,如果將 People(name, age) 去掉,也會調用默認構造函數,stu2.display() 的輸出結果將變?yōu)椋?br /> xxx的年齡是0,成績是90.5
如果將基類 People 中不帶參數的構造函數刪除,那么會發(fā)生編譯錯誤,因為創(chuàng)建對象 stu1 時沒有調用基類構造函數。
總結:如果基類有默認構造函數,那么在派生類構造函數中可以不指明,系統會默認調用;如果沒有,那么必須要指明,否則系統不知道如何調用基類的構造函數。
構造函數的調用順序
為了搞清這個問題,我們不妨先來看一個例子:
#include<iostream>
using namespace std;
//基類
class People{
protected:
char *name;
int age;
public:
People();
People(char*, int);
};
People::People(): name("xxx"), age(0){
cout<<"PeoPle::People()"<<endl;
}
People::People(char *name, int age): name(name), age(age){
cout<<"PeoPle::People(char *, int)"<<endl;
}
//派生類
class Student: public People{
private:
float score;
public:
Student();
Student(char*, int, float);
};
Student::Student(): score(0.0){
cout<<"Student::Student()"<<endl;
}
Student::Student(char *name, int age, float score): People(name, age), score(score){
cout<<"Student::Student(char*, int, float)"<<endl;
}
int main(){
Student stu1;
cout<<"--------------------"<<endl;
Student stu2("小明", 16, 90.5);
return 0;
}
運行結果:
PeoPle::People() Student::Student() -------------------- PeoPle::People(char *, int) Student::Student(char*, int, float)
從運行結果可以清楚地看到,當創(chuàng)建派生類對象時,先調用基類構造函數,再調用派生類構造函數。如果繼承關系有好幾層的話,例如:
A --> B --> C
那么則創(chuàng)建C類對象時,構造函數的執(zhí)行順序為:
A類構造函數 --> B類構造函數 --> C類構造函數
構造函數的調用順序是按照繼承的層次自頂向下、從基類再到派生類的。
C++有子對象的派生類的構造函數
類的數據成員不但可以是標準型(如int、char)或系統提供的類型(如string),還可以包含類對象,如可以在聲明一個類時包含這樣的數據成員:
Student s1; //Student是已聲明的類名,s1是Student類的對象
這時,s1就是類對象中的內嵌對象,稱為子對象(subobject),即對象中的對象。
那么,在對數據成員初始化時怎樣對子對象初始化呢?請仔細分析下面程序,特別注意派生類構造函數的寫法。
[例] 包含子對象的派生類的構造函數。為了簡化程序以易于閱讀,這里設基類Student的數據成員只有兩個,即num和name。
#include <iostream>
#include <string>
using namespace std;
class Student//聲明基類
{
public: //公用部分
Student(int n, string nam ) //基類構造函數,與例11.5相同
{
num=n;
name=nam;
}
void display( ) //成員函數,輸出基類數據成員
{
cout<<"num:"<<num<<endl<<"name:"<<name<<endl;
}
protected: //保護部分
int num;
string name;
};
class Student1: public Student //聲明公用派生類Student1
{
public:
Student1(int n, string nam,int n1, string nam1,int a, string ad):Student(n,nam),monitor(n1,nam1) //派生類構造函數
{
age=a;
addr=ad;
}
void show( )
{
cout<<"This student is:"<<endl;
display(); //輸出num和name
cout<<"age: "<<age<<endl; //輸出age
cout<<"address: "<<addr<<endl<<endl; //輸出addr
}
void show_monitor( ) //成員函數,輸出子對象
{
cout<<endl<<"Class monitor is:"<<endl;
monitor.display( ); //調用基類成員函數
}
private: //派生類的私有數據
Student monitor; //定義子對象(班長)
int age;
string addr;
};
int main( )
{
Student1 stud1(10010,"Wang-li",10001,"Li-sun",19,"115 Beijing Road,Shanghai");
stud1.show( ); //輸出學生的數據
stud1.show_monitor(); //輸出子對象的數據
return 0;
}
運行時的輸出如下:
This student is: num: 10010 name: Wang-li age: 19 address:115 Beijing Road,Shanghai Class monitor is: num:10001 name:Li-sun
請注意在派生類Student1中有一個數據成員:
Student monitor; //定義子對象 monitor(班長)
“班長”的類型不是簡單類型(如int、char、float等),它是Student類的對象。我們知道, 應當在建立對象時對它的數據成員初始化。那么怎樣對子對象初始化呢?顯然不能在聲明派生類時對它初始化(如Student monitor(10001, "Li-fun");),因為類是抽象類型,只是一個模型,是不能有具體的數據的,而且每一個派生類對象的子對象一般是不相同的(例如學生A、B、C的班長是A,而學生D、E、F的班長是F)。因此子對象的初始化是在建立派生類時通過調用派生類構造函數來實現的。
派生類構造函數的任務應該包括3個部分:
- 對基類數據成員初始化;
- 對子對象數據成員初始化;
- 對派生類數據成員初始化。
程序中派生類構造函數首部如下:
Student1(int n, string nam,int n1, string nam1,int a, string ad):
Student(n,nam),monitor(n1,nam1)
在上面的構造函數中有6個形參,前兩個作為基類構造函數的參數,第3、第4個作為子對象構造函數的參數,第5、第6個是用作派生類數據成員初始化的。
歸納起來,定義派生類構造函數的一般形式為: 派生類構造函數名(總參數表列): 基類構造函數名(參數表列), 子對象名(參數表列)
{
派生類中新增數成員據成員初始化語句
}
執(zhí)行派生類構造函數的順序是:
調用基類構造函數,對基類數據成員初始化;
調用子對象構造函數,對子對象數據成員初始化;
再執(zhí)行派生類構造函數本身,對派生類數據成員初始化。
派生類構造函數的總參數表列中的參數,應當包括基類構造函數和子對象的參數表列中的參數?;悩嬙旌瘮岛妥訉ο蟮拇涡蚩梢允侨我獾?,如上面的派生類構造函數首部可以寫成
Student1(int n, string nam,int n1, string nam1,int a, string ad): monitor(n1,nam1),Student(n,nam)
編譯系統是根據相同的參數名(而不是根據參數的順序)來確立它們的傳遞關系的。但是習慣上一般先寫基類構造函數。
如果有多個子對象,派生類構造函數的寫法依此類推,應列出每一個子對象名及其參數表列。

