一、引入繼承的目的
1. 代碼重用
類的繼承和派生機制,使程式員無需修改已有類,只需在已有類的基礎上,通過增加少量代碼或修改少量代碼的方法得到新的類,從而較好地解決了代碼重用的問題。
2. 代碼的擴充
只有在衍生類別中通過添加新的成員,加入新的功能,類的派生才有實際意義。
二、衍生類別的聲明格式(單繼承)
class 衍生類別名:繼承方式 基類名 { //衍生類別新增的資料成員和成員函數};
1)基類名是一個已經定義的類的名字,也可稱為父類;
2)衍生類別名是繼承原有類的特性而產生的新類的名稱;
3)繼承方式規定了如何訪問從基類繼承的成員,指定了衍生類別成員以及對象對於基類繼承來的成員的存取權限,它包括關鍵字private、protected、public(分別表示私人、保護、公有繼承)。
4) 如果不顯示地給出繼承方式關鍵字,系統預設為私人繼承(private)。
三、基類成員在衍生類別中的訪問屬性
1. 從基類成員屬性看
1)當基類成員在基類中的訪問屬性為private時,
在三種繼承方式的衍生類別中的訪問屬性都不可直接存取;
2)當基類成員在基類中的訪問屬性為public時,
繼承方式為public,在衍生類別中的訪問屬性為public,
繼承方式為private,在衍生類別中的訪問屬性為private,
繼承方式為protected,在衍生類別中的訪問屬性為protected;
3)當基類成員在基類中的訪問屬性為protected時,
繼承方式為public,在衍生類別中的訪問屬性為protected,
繼承方式為private,在衍生類別中的訪問屬性為private,
繼承方式為protected,在衍生類別中的訪問屬性為protected。
基類成員在基類中訪問屬性 |
基類成員在衍生類別中訪問屬性 |
public |
private |
protected |
public |
public |
private |
protected |
private |
不可直接存取 |
不可直接存取 |
不可直接存取 |
protected |
protected |
private |
protected |
2. 從繼承方式看
1)當繼承方式為private時,
基類成員屬性為public和protected,則在衍生類別中的訪問屬性為private,
基類成員屬性為private,則在衍生類別中的訪問屬性為不可直接存取;
2)當繼承方式為public時,
基類成員屬性為public和protected,則在衍生類別中的訪問屬性為不變,
基類成員屬性為private,則在衍生類別中的訪問屬性為不可直接存取;
3)當繼承方式為protected時,
基類成員屬性為public和protected,則在衍生類別中的訪問屬性為protected,
基類成員屬性為private,則在衍生類別中的訪問屬性為不可直接存取。
衍生類別的繼承方式 |
基類成員在基類中訪問屬性 |
public |
private |
protected |
public |
public |
不可直接存取 |
protected |
private |
private |
不可直接存取 |
private |
protected |
protected |
不可直接存取 |
protected |
四、衍生類別的建構函式和解構函式(單繼承)
1. 說明:
1)基類的建構函式和解構函式不能被繼承;
2)在衍生類別中,若對衍生類別中新增的成員進行初始化,就需要加入衍生類別的建構函式;
3)對所有從基類繼承下來的成員的初始化工作,由基類的建構函式完成;
4)當基類含有帶參數的建構函式時,衍生類別必須定義建構函式,以對基類的建構函式所需要的參數進行設定;
5)當基類的建構函式沒有參數,或沒有顯式定義建構函式時(即使用預設建構函式),衍生類別可以不向基類傳遞參數,甚至可不定義建構函式;
6)若衍生類別的基類也是一個衍生類別,則每個衍生類別只需負責其直接基類的構造,一次上溯;
7)衍生類別與基類的解構函式是獨立的(因為解構函式不帶參數,故基類的解構函式不會因為衍生類別沒有解構函式而得不到執行)。
2.建構函式和解構函式的執行順序:
1)當建立衍生類別對象時,首先執行基類的建構函式,隨後再執行衍生類別的建構函式;
2)當撤銷衍生類別對象時,則先執行衍生類別的解構函式,隨後再執行基類的解構函式。
例子:
#include <iostream.h>class KBase{public:KBase() //基類的建構函式{cout<<"Constructing base class/n";}~KBase() //基類的解構函式{cout<<"Destructing base class/n";}};class KDerive1:public KBase{public:KDerive1() //衍生類別1的建構函式{cout<<"Constructing derive1 class/n";}~KDerive1() //衍生類別1的解構函式{cout<<"Destructing derive1 class/n";}};class KDerive2:public KDerive1{public:KDerive2() //衍生類別2的建構函式{cout<<"Constructing derive2 class/n";}~KDerive2() //衍生類別2的解構函式{cout<<"Destructing derive2 class/n";}};class KDerive3:public KDerive2{public:KDerive3() //衍生類別3的建構函式{cout<<"Constructing derive3 class/n";}~KDerive3() //衍生類別3的解構函式{cout<<"Destructing derive3 class/n";}};int main(){KDerive3 obj;return 0;}
程式結果:
Constructing base class
Constructing derive1 class
Constructing derive2 class
Constructing derive3 class
Destructing derive3 class
Destructing derive2 class
Destructing derive1 class
Destructing base class
3. 當衍生類別中含有內嵌對象成員時,建構函式的執行順序:
1)首先調用基類的建構函式,
2)其次調用內嵌對象成員的建構函式(有多個對象成員時,調用順序由它們在類中聲明的順序確定),
3)最後執行衍生類別的建構函式體中的內容。
4)撤銷對象時,解構函式的調用順序與建構函式的調用順序正好相反。
4. 構造規則
4.1. 衍生類別建構函式的一般格式:
衍生類別名(參數總表):基類名(參數表) { //衍生類別新增成員的初始化語句 }
註:基類建構函式的參數,通常來源於衍生類別建構函式的參數總表,也可用常數值。
4.2. 當衍生類別中含有內嵌對象成員時,其建構函式的一般形式:
衍生類別名(參數總表):基類名(參數表1),內嵌對象名1(內嵌對象參數表1),……,內嵌對象名n(內嵌對象參數表n) { //衍生類別新增成員的初始化語句 }
4.3. 例子:
#include <iostream.h>class KBase{private:int x;public:KBase(int i){x = i;cout<<"Constructing base class/n";}~KBase(){cout<<"Destructing base class/n";}void Show(){cout<<" x = "<<x<<endl;}};class KDerived:public KBase{private:KBase d; //基類對象d作為衍生類別的對象成員int y;public://衍生類別嵌有對象成員時的建構函式格式KDerived(int i, int j, int k):KBase(i),d(j){y = k;cout<<"Constructing derived class/n";}~KDerived(){cout<<"Destructing derived class/n";}void Show(){KBase::Show();d.Show();cout<<" y = "<<y<<endl;}};int main(){KDerived obj(4,6,8);obj.Show();return 0;}
程式結果:
Constructing base class
Constructing base class
Constructing derived class
x = 4
x = 6
y = 8
Destructing derived class
Destructing base class
Destructing base class
五、衍生類別成員覆蓋基類的同名成員
1. 含義:
在衍生類別中定義了與基類同名的成員,稱為衍生類別成員覆蓋基類的同名成員。
2. 在衍生類別中使用基類的同名成員的方法:
2.1 基類名::成員名;
2.2 衍生類別的對象名.基類名::成員名;
3. 例子:
#include <iostream.h>#include <string.h>//類Studentclass KStudent {private:char *name;char *stu_no;float score;public:KStudent(char *name1, char *stu_no1, float score1);~KStudent();void Show();};KStudent::KStudent(char *name1, char *stu_no1, float score1){name = new char[strlen(name1) + 1];strcpy(name, name1);stu_no = new char[strlen(stu_no1) + 1];strcpy(stu_no, stu_no1);score = score1;}KStudent::~KStudent(){delete []name;delete []stu_no;}void KStudent::Show(){cout<<"/n name: "<<name;cout<<"/n stu_no: "<<stu_no;cout<<"/n score: "<<score;}//類KUstudentclass KUstudent:public KStudent{private:char *major;public:KUstudent(char *name1, char *stu_no1, float score1, char *major1);~KUstudent();void Show(); //在衍生類別中,重新定義了成員函數Show()};KUstudent::KUstudent(char *name1, char *stu_no1, float score1, char *major1):KStudent(name1, stu_no1, score1){major = new char[strlen(major1) + 1];strcpy(major, major1);}KUstudent::~KUstudent(){delete []major;}void KUstudent::Show(){KStudent::Show(); //定義衍生類別時,訪問基類的同名成員的格式cout<<"/n major: "<<major<<endl;}int main(){KUstudent stu1("Liming", "990201", 90, "computer");stu1.Show();stu1.KStudent::Show();//衍生類別對象訪問基類的同名成員的格式return 0;}
程式結果:
name: Liming
stu_no: 990201
score: 90
major: computer
name: Liming
stu_no: 990201
score: 90
六、訪問聲明
1. 格式:(在私人衍生類別的同名段中)
基類名::基類的成員函數名(或資料成員名);
2. 說明:
1)資料成員和函數成員均可使用訪問聲明;
2)訪問聲明的方法針對私人衍生類別、基類的保護成員或公有成員(不能訪問基類的私人成員);
3)訪問聲明必須寫在衍生類別定義式中的同名段中;
4)訪問聲明不能改變類成員原來在基類中的成員性質,只能把原基類的保護成員調整為衍生類別的保護成員,原基類的公有成員調整為衍生類別的公有成員;
5)訪問聲明機制可個別調整私人衍生類別從基類繼承下來的成員性質,從而使外界可通過衍生類別的介面直接存取基類的某些成員,同時不影響其他基類成員的封閉性;
6)注意函數的訪問聲明的格式中不帶傳回型別和參數(既不能寫出傳回型別和括弧);
7)對於重載函數使用訪問聲明時要謹慎(因為對於基類中的重載函數名,訪問聲明將對基類中所有同名函數起作用)。
3. 例子:
#include <iostream.h>class KA{private:int x;public:KA(int x1){x = x1;}void Print(){cout<<" x = "<<x;}};class KB:private KA{private:int y;public:KB(int x1, int y1):KA(x1){y = y1;}KA::Print; //訪問聲明,這時Print也就成為了類KB的公有成員};int main(){KB b(10,20);b.Print();return 0;}
程式結果:x = 10
七、多重繼承
1. 聲明格式:
class 衍生類別名:繼承方式1 基類名1,……,繼承方式n 基類名n { //衍生類別新增的資料成員和成員函數 };
2. 說明:
1)預設的繼承方式是private;
2)在多重繼承中,公有繼承和私人繼承對於基類成員在衍生類別中的可訪問性與單繼承的規則相同。
3. 建構函式和解構函式
3.1. 建構函式定義格式:
衍生類別名(參數總表):基類名1(參數表1),基類名2(參數表2),……,基類名n(參數表n) { //衍生類別新增成員的初始化語句 };
3.2. 建構函式的執行順序:(與單繼承建構函式的執行順序相同)
1)首先執行基類的建構函式(處於同一層次的各個基類的建構函式的執行順序,取決於聲明衍生類別時所指定的各個基類的順序,與衍生類別建構函式中所定義的成員初始化列表的各項順序沒有關係);
2)再執行對象成員的建構函式;
3)最後執行衍生類別建構函式。
4) 解構函式的執行順序剛好與建構函式的執行順序相反。
3.3. 說明:
1)多重繼承下衍生類別建構函式與單繼承下衍生類別建構函式相似,它必須同時負責該衍生類別所有基類建構函式的調用,同時,衍生類別的參數個數必須包含完成所有基類初始化所需的參數個數;
3.4. 例子:
#include <iostream.h>class KX{private:int a;public:KX(int sa){a = sa;cout<<"Constructing class KX/n";}~KX(){cout<<"Destructing class KX/n";}int GetX(){return a;}};class KY{private:int b;public:KY(int sb){b = sb;cout<<"Constructing class KY/n";}~KY(){cout<<"Destructing class KY/n";}int GetY(){return b;}};class KZ:public KX, private KY{private:int c;public:KZ(int sa, int sb):KX(sa), KY(sb){c = sb;cout<<"Constructing class KZ/n";}~KZ(){cout<<"Destructing class KZ/n";}int GetZ(){return c;}/*int GetY() //採用重載的方式使函數GetY()成為類KZ的公有成員{return KY::GetY();}*/KY::GetY; //採用訪問聲明的方式使函數GetY()成為類KZ的公有成員};int main(){KZ obj(2,4);int ma = obj.GetX();cout<<"a = "<<ma<<endl;int mb = obj.GetY();cout<<"b = "<<mb<<endl;int mc = obj.GetZ();cout<<"c = "<<mc<<endl;return 0;}
程式結果:
Constructing class KX
Constructing class KY
Constructing class KZ
a = 2
b = 4
c = 4
Destructing class KZ
Destructing class KY
Destructing class KX