C++筆記 --- 類的繼承


 

目錄

繼承
單繼承
多繼承

 (本章節中例子都是用 VS2005 編譯調試的)


 

繼承

方式:

  • private     基類的公有成員與保護成員成為派生類的私有成員
  • public       基類的成員訪問權限不變
  • protected     基類的公有成員與保護成員將成為派生類的保護成員

三種繼承方式的對比:

特征       公有繼承         保護繼承           私有繼承      
公有成員繼成   派生類公有成員      派生類保護成員        派生類私有成員
私有成員繼成   只能通過基類接口訪問   只能通過基類接口訪問     只能通過基類接口訪問
保護成員繼成   派生類保護成員      派生類保護成員        派生類私有成員
能否隱式轉換   是            是(但只能在此派生類中)    否

在沒有虛函數的情況下,類指針調用函數是注意:

  • 如果以一個基類指針指向派生類對象,那么經由該指針只能調用基類所定義的函數
  • 如果你以一個派生類的指針指向一個基類對象,你必須先做明顯的強制轉換,但是這樣做很危險
  • 如果基類和派生類都定義了相同名的成員函數,那么通過對象指針調用成員函數時候,調用的函數是由指針的原始類型而定,而不是看指針指向的對象的類型而定

虛函數表:

定義:

為了達到動態綁定(后期綁定)的目的,C++編譯器通過某個表格,在執行期"間接"調用實際上欲綁定的函數(注意"間接"字眼).這樣的表格稱為虛函數表(常被稱為vtable).每一個"內含虛函數的類",編譯器都會為它做一個虛函數表,表中的每一個元素都指向一個虛函數的地址.此外,編譯器當然會為這個類加上一項成員變量,是一個指向這個虛函數表的指針(常被稱為vptr),且每個由此派生出來的對象,都會有這個一個vptr.

介紹:

當我們通過這個對象調用虛函數時,事實上是通過vptr找到虛函數,再找出真實的地址,虛函數表用這種間接的的方式,虛函數表的內容是依據類中的虛函數聲明次序,意義填入函數表(以及所有其他可以繼承的成員),當我們在派生類中改寫虛函數時,虛函數表就受了影響:表中元素所指的函數地址將不再是基類的函數地址,而是派生類的地址

參考鏈接:  [什么是虛函數]   [虛函數表解析]

例子:

三個繼承權限的區別:

View Code
 1 class ex0
 2 {
 3 private:
 4     void showPrivate(){cout<<"this is private function!";}
 5 public:
 6     void showPublic(){cout<<"this is public function!";}
 7 protected:
 8     void showProtected(){cout<<"this is protected function!";}
 9 };
10 class ex1:public ex0
11 {
12 public:
13     void func()
14     {
15         showPrivate();
16         //錯誤因為此函數訪問權限只有基類 ex0 自己有
17         showPublic();
18         showProtected();
19     }
20 };
21 class ex2:protected ex0
22 {
23 public:
24     void func()
25     {
26         showPrivate();
27         //錯誤因為此函數訪問權限只有基類 ex0 自己有
28         showPublic();
29         //正確, 但是此函數由於 ex2 的保護繼承這個函數的訪問權限已經變成了 protected,
30         //也就是說對於外部類來說已經不具備訪問這個函數的權限了
31         showProtected();
32     }
33 };
34 class ex3:private ex0
35 {
36 public:
37     void func()
38     {
39         showPrivate();
40         //錯誤因為此函數訪問權限只有基類 ex0 自己有
41         showPublic();
42         //正確, 但是此函數由於 ex3 的私有繼承這個函數的訪問權限已經變成了 private,
43         //也就是說對於外部類和派生類來說已經不具備訪問這個函數的權限了
44         showProtected();
45         //正確, 但是此函數由於 ex3 的私有繼承這個函數的訪問權限已經變成了 private,
46         //也就是說對於外部類和派生類來說已經不具備訪問這個函數的權限了
47     }
48 };

有沒有虛函數的情況下,類指針調用函數的區別:

[此處例子鏈接] 

[返回目錄]


 

單繼承

聲明格式:

class 派生類名:繼承方式(若不具體指出默認為private) 基類名
{
    …
};

特性:

繼承了基類所有屬性與行為,包括私有成員,但不允許派生類直接訪問基類私有成員

構造函數:

格式:   派生類構造函數名(形參表):基類構造函數名(形參表){…}

要點:

● 創建派生類對象時,程序首先創建基類對象,即基類對象應在進入派生類構造函數前被創建完成
 (即先調用基類構造函數,后調用派生類構造函數)
● 派生類構造函數應通過成員初始化表將基類信息傳遞給基類構造函數
● 派生類構造函數應初始化派生類新增的數據成員

析構函數:

特性:    派生類對象過期時,程序將首先調用派生類析構函數,然后調用基類的

虛析構函數:

作用:  和虛函數一樣類似,在用基類指針釋放派生類對象時候,為了能調用正確的析構函數.

注意:  當一個類有虛函數功能,它經常作為一個基類使用,並且它的派生類經常使用new來分配,那么它最好也使用虛析構函數,因為這樣才能保證在釋放實例對象的時候調用正確的析構函數

注意:

  • 構造函數的調用次序為 基類 -> 派生類
  • 析構函數的調用次序為 派生類 -> 基類

例子:

構造函數和析構函數的調用順序:

View Code
 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 
 5 class A{
 6 public:
 7     A(){cout<<"A::A()    ";}
 8     ~A(){cout<<"A::~A()"<<endl;}
 9 };
10 class B:virtual public A{
11 public:
12     B(){cout<<"B::B()    "<<endl;}
13     ~B(){cout<<"B::~B()    ";}
14 };
15 void func()
16 {
17     cout<<"this is class D:"<<endl;
18     cout<<"the order of constructor:    ";
19     B b;
20     cout<<"the order of destructor:    ";
21 }
22 
23 void main()
24 {
25     func();
26     system("pause");
27 }
28 /********************************
29 輸出結果:
30 this is class D:
31 the order of constructor:       A::A()    B::B()
32 the order of destructor:        B::~B()    A::~A()
33 請按任意鍵繼續. . .
34 ********************************/

虛函數使用:

[此處為鏈接]

無虛析構函數:

View Code
 1 class A{
 2 public:
 3     A(){cout<<"A::A()    ";}
 4     ~A(){cout<<"A::~A()"<<endl;}
 5 };
 6 class B:public A{
 7 public:
 8     B(){cout<<"B::B()    "<<endl;}
 9     ~B(){cout<<"B::~B()    ";}
10 };
11 void func()
12 {
13     A* pb = new B();
14     delete pb;
15 }
16 /******************************************
17 調用 func 函數的輸出結果:
18 A::A()    B::B()
19 A::~A()
20 ******************************************/

有虛析構函數:

View Code
 1 class A{
 2 public:
 3     A(){cout<<"A::A()    ";}
 4     virtual ~A(){cout<<"A::~A()"<<endl;}
 5 };
 6 class B:public A{
 7 public:
 8     B(){cout<<"B::B()    "<<endl;}
 9     ~B(){cout<<"B::~B()    ";}
10 };
11 void func()
12 {
13     A* pb = new B();
14     delete pb;
15 }
16 /*****************************************
17 調用 func 函數的輸出結果:
18 A::A()    B::B()
19 B::~B()    A::~A()
20 *****************************************/

基類構造函數的調用:

View Code
 1 class A{
 2 public:
 3     A(){cout<<"this is A::A()"<<endl;}
 4     A(int b){cout<<"this is A::A(int b):    the value of b is: "<<b<<endl;}
 5     A(double b){cout<<"this is A::A(double b)    the value of b is: "<<b<<endl;}
 6 };
 7 class B:public A{
 8 public:
 9     B():A(){}  //調用基類 A 默認構造函數
10     B(int b):A(b){} //調用基類 A 的 A::A(int b) 構造函數
11     B(double b):A(b){} //調用基類 A 的 A::A(double b) 構造函數
12 };
13 void main()
14 {
15     B b0,b1(5),b2(10.5);//產生三個實例,分別調用不同的基類構造函數
16     system("pause");
17 }
18 /***********************************
19 調用 func 函數的輸出結果:
20 this is A::A()
21 this is A::A(int b):    the value of b is: 5
22 this is A::A(double b)    the value of b is: 10.5
23 ***********************************/

 [返回目錄]


 

多繼承

聲明格式:

class 派生類名:繼承方式 基類名,繼承方式 基類名… {….};

構造函數:

  • 形式:

派生類名::派生類名(形參表):基類名(形參表),基類名(形參表)…{…}

  • 特點:

處理同一層的基類構造函數的執行順序取決於定義派生類對各基類的排列順序,與定義派生類的構造函數時基類的排列順序無關

  • 二定義性:

原因:

  • 由於多層次的交叉派生類關系,造成一個派生類對象包含了基類成員的多個副體
  • 多個基類中某個成員名相同

方法:

  • 用虛基類來解決由於多層次的交叉派生類關系(原理:采用虛基類定義方式定義派生類,在創建派生類時,類層次結構中某個虛基類的成員只保留一個,即虛基類成員的一個副本被所有派生類共享)
  • 用基類中定義成員的訪問修改方法來解決多個基類中某個成員名相同

虛基類:

聲明格式:   class 派生類名:virtual 繼承方式 基類名,… { … };

注意:

  • virtual與繼承方式之間的次序無關且將基類作為虛基類,計算機為此完成一些額外計算量慎用
  • 一個類可以在一個類族中用作虛基類,也可以用作非虛基類.
  • 在派生類的對象中,同名的虛基類只產生一個虛基類子對象,而某個非虛基類產生各自的對象.
  • 虛基類子對象是由最派生類的構造函數通過調用虛基類的構造函數進行初始化的.
  • 最派生類是指在繼承結構中建立對象時所指定的類.
  • 在派生類的構造函數的成員初始化列表中,必須列出對虛基類構造函數的調用,如果沒有列出,則表示使用該虛基類的缺省構造函數.
  • 在虛基類直接或間接派生的派生類中的構造函數的成員初始化列表中,都要列出對虛基類構造函數的調用.但只有用於建立對象的最派生類的構造函數調用虛基類的構造函數,而該派生類的所有基類中列出的對虛基類構造函數的調用在執行中被忽略,從而保證對虛基類子對象只初始化一次.
  • 在一個成員初始化列表中,同時出現對虛基類和非虛基類構造函數的調用時,基類的構造函數先於非虛基類的構造函數執行

有虛基類和無虛基類的區別:

  • 有虛基類:

  • 無虛基類:

例子:

沒有虛基類繼承:

View Code
 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 
 5 class A{
 6 protected:
 7     int a;
 8 public:
 9     A():a(0){}; //對成員變量 a 進行初始化
10 };
11 class B:public A{
12 public:
13     void B_changeValue(int b) //在派生類 B 中修改基類 A 的成員變量 a 並輸出其前后的值
14     {
15         cout<<"in the class B"<<endl;
16         cout<<"the original value of a is:   "<<a<<endl;
17         a = b;
18         cout<<"the value after change is:   "<<a<<endl<<endl;
19     }
20 };
21 class C:public A{
22 public:
23     void C_changeValue(int c) //在派生類 C 中修改基類 A 的成員變量 a 並輸出其前后的值
24     {
25         cout<<"in the class C"<<endl;
26         cout<<"the original value of a is:   "<<a<<endl;
27         a = c;
28         cout<<"the value after change is:   "<<a<<endl<<endl;
29     }
30 };
31 class D:public B,public C{
32 
33 };
34 
35 void main()
36 {
37     D d;
38     d.B_changeValue(10);
39     d.C_changeValue(20);
40     d.B_changeValue(11);
41     d.C_changeValue(21);
42     system("pause");
43 }

 輸出結果:

由此可以看出,其實派生類 D 從基類 B 與基類 C 中繼承了兩份 A 的數據變量備份

有虛基類的繼承:

View Code
 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 
 5 class A{
 6 protected:
 7     int a;
 8 public:
 9     A():a(0){}; //初始化成員變量 a
10 };
11 class B:virtual public A{
12 public:
13     void B_changeValue(int b) // 在派生 B 中修改基類 A 的成員變量 a 並輸出其修改前后的值
14     {
15         cout<<"in the class B"<<endl;
16         cout<<"the original value of a is:   "<<a<<endl;
17         a = b;
18         cout<<"the value after change is:   "<<a<<endl<<endl;
19     }
20 };
21 class C:virtual public A{
22 public:
23     void C_changeValue(int c) // 在派生 C 中修改基類 A 的成員變量 a 並輸出其修改前后的值
24     {
25         cout<<"in the class C"<<endl;
26         cout<<"the original value of a is:   "<<a<<endl;
27         a = c;
28         cout<<"the value after change is:   "<<a<<endl<<endl;
29     }
30 };
31 class D:public B,public C{
32 
33 };
34 
35 void main()
36 {
37     D d;
38     d.B_changeValue(10);
39     d.C_changeValue(20);
40     d.B_changeValue(11);
41     d.C_changeValue(21);
42     system("pause");
43 }

  輸出結果:

由此可以看出,這次派生類 D 從基類 B 與基類 C 中繼承了只有一份 A 的數據變量備份

構造函數與析構函數的順序:

View Code
 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 
 5 class A{
 6 public:
 7     A(){cout<<"A::A()    ";}
 8     ~A(){cout<<"A::~A()"<<endl;}
 9 };
10 class B:virtual public A{
11 public:
12     B(){cout<<"B::B()    ";}
13     ~B(){cout<<"B::~B()    ";}
14 };
15 class C:virtual public A{
16 public:
17     C(){cout<<"C::C()    ";}
18     ~C(){cout<<"C::~C()    ";}
19 };
20 class D:public B,public C{
21 public:
22     D():C(),B(){cout<<"D::D()"<<endl;}
23     ~D(){cout<<"D::~D()    ";}
24 };
25 class E:public C,public B
26 {
27 public:
28     E():C(),B(){cout<<"E::E()"<<endl;}
29     ~E(){cout<<"E::~E()    ";}
30 };
31 void funcD()
32 {
33     cout<<"this is class D:"<<endl;
34     cout<<"the order of constructor:    ";
35     D d;
36     cout<<"the order of destructor:    ";
37 }
38 void funcE()
39 {
40     cout<<"this is class E:"<<endl;
41     cout<<"the order of constructor:    ";
42     E e;
43     cout<<"the order of destructor:    ";
44 }
45 void main()
46 {
47     funcD();
48     cout<<endl;
49     funcE();
50     cout<<endl;
51     system("pause");
52 }

 輸出結構:

由此可以看出,構造函數的順序與繼承順序有關,而不是基類構造函數的調用有關

基類構造函數調用:

View Code
 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 
 5 class A{
 6 public:
 7     A(){cout<<"this is A::A()"<<endl;}
 8     A(int b){cout<<"this is A::A(int b):    the value of b is: "<<b<<endl;}
 9     A(double b){cout<<"this is A::A(double b)    the value of b is: "<<b<<endl;}
10 };
11 class B
12 {
13 public:
14     B(){cout<<"this is B::B()"<<endl;}
15     B(int b){cout<<"this is B::B(int b):    the value of b is: "<<b<<endl;}
16     B(double b){cout<<"this is B::B(double b)    the value of b is: "<<b<<endl;}
17 };
18 class C:public A,public B{
19 public:
20     C():A(),B(){cout<<endl;}
21     C(int b,double c):A(b),B(c){cout<<endl;}
22     C(double b,int c):A(b),B(c){cout<<endl;}
23 };
24 void main()
25 {
26     C c0,c1(5,15.123),c2(10.5,1235);
27     system("pause");
28 }

輸出結果:

[返回目錄] 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM