C++中,繼承就是在一個已存在的類的基礎上建立一個新的類。已存在的類稱為基類,又稱父類;新建立類稱為派生類,又稱為子類。
基類是對派生類的抽象,派生類是對基類的具體化。
(一)派生類的定義與構成
1)派生類的定義
class 派生類名:類派生列表{
成員列表
};
類派生列表指定了一個或多個基類,形式如下:
訪問權限標號 基類名1,訪問權限標號 基類名2,...
2)派生類的構成
派生類由兩部分組成:第一部分是從基類繼承得到的,另一部分是自己定義的新成員,這些成員仍然分為三種訪問屬性。
注意,友元關系是不能繼承的:一方面,基類的友元對派生類成員沒有特殊的訪問權限;另一方面,如果基類被授予了友元關系,則只有基類有特殊訪問權限,該基類的派生類不能訪問授予友元關系的類。
實際編程中,設計一個派生類包括三個方面工作:
a)從基類接收成員。
除了構造函數與析構函數之外,派生類會把基類的全部成員繼承過來。
b)調整基類成員的訪問。
程序員可以對接收的成員指定訪問策略。
c)在定義派生類時增加新的成員。另外還應該自己定義派生類的構造函數和析構函數,因為它們不能從基類繼承過來。
(二)派生類成員的訪問
不同的繼承方式決定了基類成員在派生類中的訪問屬性
1)公有繼承
基類的公有成員和保護成員在派生類中保持原有訪問屬性,私有成員仍為基類所有。
2)私有繼承
基類的所有成員在繼承類中為私有成員。
3)保護繼承
一般地,保護繼承與私有繼承在實際編程中極少使用,它們只在技術理論上有意義。
在公有繼承中,派生類成員可以訪問繼承的基類的public部分與protected部分,但是不能訪問private部分。只有基類成員以及基類的友元可以訪問private部分。
無論采用何種繼承方式得到的派生類,派生類成員及其友元都不能訪問基類的私有成員。
(三)賦值兼容性規則
賦值兼容規則是指在需要基類對象的任何地方,都可以使用共有派生類的對象來替代。通過公有繼承,派生類得到了基類中除了構造函數與析構函數之外的所有成員。這樣共有派生類實際就具有了基類的所有功能,凡是基類能解決的問題,公有派生類都可以解決。
class Base{}; //基類
class Derive:public Base{}; //公有派生類
Base b,*pb; //定義基類對象,指針
Derive d; //定義派生類對象
賦值兼容性規則中所指的替代包括如下情況。
(1)派生類的對象可以賦值給基類對象;
b=d; //派生類對象賦值給基類,復制基類繼承部分
(2)派生類的對象可以初始化基類的引用;
Base &rb=d; //基類引用到派生類對象
(3)派生類對象的地址可以賦值給指向基類的指針。
pb=&d; //基類指針指向派生類對象
(四)派生類的構造和析構函數
1)構造函數
派生類的構造函數定義形式如下:
派生類名站(形式參數列表):基類名(基類構造函數實參列表),派生類初始化列表
例子:
class Point{int x,y;
public:Point(int a,int b):x(a),y(b){} //基類構造函數
};
class Rect:public Point {int h,w;
public :Rect(int a,int b,int c,int d):Point(a,b),h(c),w(d){} //派生類構造函數
如果基類有默認構造函數,或者參數全部都是默認參數的構造函數,那么派生類的構造函數中可以不顯式初始化子對象。編譯器會調用基類的構造函數進行初始化。
2)組合關系的派生類的構造函數
包括多個子對象的派生類的構造函數定義為:
派生類名(形式參數列表):基類名(基類構造函數實參列表),
子對象名1(子對象1所屬類的構造函數實參列表)
...
派生類初始化列表
{
派生類初始化函數體
};
下面是一段實例代碼:
#include<iostream> using namespace std; class A{ public: A():a1(1),a2(2){} A(int a,int b):a1(a),a2(b){} //private: int a1,a2; }; class B{ public: B(int a,int b):b1(a),b2(b){} //private: int b1,b2; A A1; }; class C:public B{ public: C(int a,int b):B(3,4), A2(5,6), c1(a),c2(b){} //private: int c1,c2; A A2; }; int main() { C C1(7,8); cout<<C1.c2<<endl<<C1.c1<<endl; cout<<C1.A2.a2<<endl<<C1.A2.a1<<endl; cout<<C1.b2<<endl<<C1.b1<<endl; cout<<C1.A1.a2<<endl<<C1.A1.a1<<endl; return 0; }
利用gcc編譯器測試:
lzb@lzb:~/classic_lib/C++_learning$ g++ 417.cpp
lzb@lzb:~/classic_lib/C++_learning$ ./a.out
8
7
6
5
4
3
2
1
3)析構函數
在執行派生類的析構函數時候,系統會自動調用基類的析構函數與子對象的析構函數,對基類和子對象進行處理。
(五)多重繼承
C++支持一個派生類同時繼承多個基類。
多重繼承派生類的定義:
class 派生類名:訪問標號1 基類名1,訪問標號2 基類名2,...{
成員列表
};
多重繼承構造函數:
多重繼承派生類的構造函數形式與單一繼承時的構造函數形式基本相同,只是在派生類的構造函數初始化列表中調用調用多個基類構造函數。一般形式為:
派生類名(形參列表):基類名1(基類1構造函數實參列表)
基類名2(基類2構造函數實參列表)
...
子對象名1(子對象1所屬類的構造函數實參列表)
...
派生類初始化列表
{
派生類初始化列表
};
a)二義性問題
class A{
public:
void fun (){cout<<"a";}
};
class B{
public:
void fun(){cout<<"b";}
void gun(){cout<<"c";}
};
class C:public A,public B{
public:
void gun(){cout<<"d";}
void hun(){fun();}
};
C c,*c1;
c1=&c;
多重繼承時,多個基類可能會出現同名的成員。在派生類中對這種基類對象的直接訪問會有二義性。C++要求派生類對基類成員的訪問必須是無二義性的。這時我們需要使用成員名限定消除二義性:
基類名::成員名 //在派生類內部使用基類成員,可以在fun()前面加上::,得到A::fun()
對象名.基類名::成員名 //派生類對象使用基類成員 ,c.A::fun(); c.B::fun();
對象指針名->基類名::成員名 //派生類指針使用基類成員, c1->A::fun(); c1->B::fun();
b)名字支配規則
在類的派生層次結構中,基類的成員與派生類新增的成員都具有類作用域,二者的作用域是不同的。它們是相互包含的兩個層,基類在外邊,派生類在里面。配生類如果聲明了一個和基類成員同名的成員,這時直接使用成員名只能訪問到派生類的成員。如果派生類中聲明了與基類成員函數同名的函數,即使函數形式參數不同,直接使用成員名也只能訪問到派生類的新函數。這時需要作用域運算符和基類名來限定。
c.gun; //c.C::gun();
c.B::gun(); //使用B::gun
(六)虛基類
如果一個派生類有多個直接基類,而這些直接基類又有一個共同的基類,則在最終的派生類中會保留該間接共同基類數據成員的多份同名成員。
在一個類中保留間接共同基類的多份同名成員,雖然有時候是必要的,可以在不同的數據成員中分別存放不同的數據,也可以通過構造函數分別對他們進行初始化。但是大多數情況下,這種情況是人們不希望出現的。因為保留多份數據成員的副本,不僅占用較多的存儲空間,還增加了訪問這些成員時的困難。
C++提供虛基類機制,使得在繼承間接共同基類時只保留一份成員。
1)虛基類定義
虛基類是在派生類定義時指定繼承方式時聲明的。因為一個基類可以在生成一個派生類時作為虛基類,而在生成另一個派生類時不作為虛基類。聲明虛基類的一般形式為:
class 派生類名:virtual 訪問權限標號 虛基類名,...{
成員列表
};
經過這樣的聲明之后,當基類通過多條派生路徑被一個派生類繼承時,該派生類只繼承該基類一次,也就是基類成員只保留一次。
注意:為了保證虛基類在派生類中只繼承一次,應當在該基類的所有直接派生類中聲明為虛基類,否則仍然會出現對基類的多次繼承。
2)虛基類的初始化
如果在虛基類中定義了帶參數的構造函數,而且沒有定義