日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

继承专题

發(fā)布時間:2025/3/15 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 继承专题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

【1】繼承及訪問權限

(1)理論知識

<1> 基類與派生類。基類、父類、超類均是指被繼承的類;派生類、子類是指繼承于基類(父類、超類)的類。

<2> 在C++中使用冒號表示繼承。如下代碼片段:

1 class A : public B // 表示派生類A從基類B繼承而來。 2 { 3 };

<3> 派生類包含基類的所有成員,且還包括自已特有的成員。派生類內部成員函數(shù)和派生類對象訪問基類中的成員就像訪問自已的成員一樣,可以直接使用,不需加任何操作符。但是,派生類永遠無法訪問基類中的私有成員。

<4> 在C++中派生類可以同時從多個基類繼承(Java 不允許這種多重繼承),當繼承多個基類時,使用逗號將基類隔開。

<5> 基類訪問控制符:

1 class A : public B //基類以公有方式被繼承; 2 class A : private B //基類以私有方式被繼承; 3 class A : protected B //基類以受保護方式被繼承;

如果沒有(或忘記寫)訪問控制符呢?切記:默認為私有繼承。

<6> protected 受保護的訪問權限:使用protected 保護權限表明這個成員是受保護的,但在派生類中可以訪問基類中的受保護成員。但是,派生類的對象就不能訪問基類受保護的成員了。

<7> 如果基類以public公有方式被繼承:那么基類的所有公有成員都會成為派生類的公有成員,受保護的基類成員成為派生類的受保護成員。

<8> 如果基類以private私有方式被繼承:那么基類的所有公有成員都會成為派生類的私有成員,基類的受保護成員也成為派生類的私有成員。

<9> 如果基類以protected受保護方式被繼承:那么基類的所有公有和受保護成員都會變成派生類的受保護成員。

<10> 不管基類以何種方式被繼承,基類的私有成員,始終永久保有其私有性,派生的任何代子類都不能訪問基類的私有成員。

(2)示例代碼

<1> 三種訪問控制符

1 /* 2 *三種訪問控制符 3 */ 4 class A 5 { 6 private: //私有成員 7 int a; 8 protected: //保護成員 9 int b; 10 public: //公有成員 11 int c; 12 A() 13 { 14 a = b = c = 1; 15 } 16 };

<2> 公有方式繼承

1 class B:public A 2 { 3 public: 4 int d; 5 B() 6 { 7 // a = 2; //error!派生類中不可以訪問基類的私有成員 8 b = 2; //可以在派生類中訪問基類的受保護成員。權限仍為受保護成員 9 c = 2; //可以在派生類中訪問基類的公有成員。權限仍為公有成員 10 } 11 };

<3> 受保護方式繼承

1 class C : protected A 2 { 3 public: 4 int e; 5 C() 6 { 7 // a = 3; //error!派生類中不可以訪問基類的私有成員 8 b = c = e = 3; //可以在派生類中訪問基類的公有及受保護成員(權限都為受保護成員) 9 } 10 };

<4> 私有方式繼承

1 class D : private A 2 { 3 public: 4 D() 5 { 6 b = c = 4; //基類中的公有和受保護成員均成了派生類的私有成員 7 } 8 };

<5> 驗證受保護和私有的訪問權限

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 private: 7 int a; 8 protected: 9 int b; 10 public: 11 int c; 12 A() 13 { 14 a = b = c = 1; 15 } 16 }; 17 18 class B : public A 19 { 20 public: 21 int d; 22 B() 23 { 24 // a = 2; //error!派生類中不可以訪問基類的私有成員 25 b = 2; //可以在派生類中訪問基類的受保護成員。權限仍為受保護成員 26 c = 2; //可以在派生類中訪問基類的公有成員。權限仍為公有成員 27 } 28 }; 29 30 class C : protected A 31 { 32 public: 33 int e; 34 C() 35 { 36 // a = 3; //error!派生類中不可以訪問基類的私有成員 37 b = c = e = 3; //可以在派生類中訪問基類的受保護以及公有成員。權限都為受保護成員 38 } 39 }; 40 41 class D : private A 42 { 43 public: 44 D() 45 { 46 b = c = 4; //基類中的公有和受保護成員均成了派生類的私有成員 47 } 48 }; 49 50 class C1 : public C 51 { 52 public: 53 C1() 54 { 55 b = c = e = 4; //類A中的成員b和c在類C中均為受保護成員,此時仍為C1的受保護成員 56 } 57 }; 58 59 class D1 : public D //類A中的成員b和c在類D中均為私有成員,此時仍為D1的私有成員 60 { 61 public: 62 D1() 63 { 64 // b = 5; //error!派生類中不可以訪問基類的私有成員 65 // c = 5; //error!派生類中不可以訪問基類的私有成員 66 } 67 }; 68 69 void main() 70 { 71 A m1; B m2; C m3; D m4; 72 // cout << m1.b << m2.b << m3.b; //不能用類對象訪問類的受保護成員。只有在類的成員函數(shù)中才可以訪問的 73 cout << m1.c; //可以用類的對象訪問類的公有成員 74 cout << m2.c; //可以用類的對象訪問類的公有成員 75 // cout << m3.c; //error!不能用類的對象訪問類的受保護成員 76 // cout << m4.c; //error!不能用類的對象訪問類的私有成員 77 }

【2】覆蓋和隱藏基類成員(變量或函數(shù))

<1> 基類的成員(變量或函數(shù))被覆蓋:

如果派生類覆蓋了基類中的成員(變量或函數(shù)):

當用派生類對象調用此同名成員(變量或函數(shù))時調用的是派生類中的版本;

當用基類對象調用此同名成員(變量或函數(shù))時調用的是基類中的版本。

<2> 隱藏基類成員函數(shù)的情況:

如果在派生類中定義了一個與基類同名的函數(shù),不管這個函數(shù)的參數(shù)列表是不是與基類中的函數(shù)相同,這個同名函數(shù)都會把基類中所有同名函數(shù)及所有重載版本隱藏掉,這時并不是在派生類中重載基類的同名成員函數(shù),而是所謂的隱藏。比如:類A中有函數(shù)f(int i, int j) 和 f(int ?i)兩個版本,當從 A 派生出的類 B 中定義了基類的同名f()函數(shù)版本時,類中的f(int i) 和 f(int i, int j)就被隱藏掉了,也就是說由類 B 創(chuàng)建的對象比如為objB,不能直接訪問類 A 中的f(int i) 和 f(int i, int j)版本,即就是用語句objB.f(2)時會發(fā)生錯誤。

關于<1> 和 <2>兩點,請參見示例如下代碼:

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 int a; // 注意:默認為私有成員變量 7 protected: 8 int b; 9 public: 10 int c; 11 int d; 12 A() 13 { 14 a = b = c = d = 1; 15 } 16 void f(int i) 17 { 18 cout << "class A::f(int i)" << "[param Value:" << i << "]" << endl; 19 } 20 }; 21 22 class B : public A 23 { 24 public: 25 int d; //覆蓋基類中成員變量d 26 B() 27 { 28 b = c = d = 2; //這個d是指派生類B中的成員變量d,而不是基類中的成員變量d 29 A::d = 3; //給基類中被覆蓋的成員變量d賦值。注意類中訪問被覆蓋成員的方式(用雙冒號,作用域解析運算符) 30 } 31 void f() //在派生類中重定義基類中的同名函數(shù)。雖然參數(shù)列不同,但同樣會隱藏基類中的同名函數(shù) 32 { 33 cout << "class B::f()" << endl; 34 A::f(100);//在函數(shù)中調用基類中被隱藏函數(shù)的方式,使用作用域解析運算符 35 // f(1); //錯誤,因為基類中的函數(shù)被派生類的同名函數(shù)隱藏了,在這里派生類不知道有一個帶參數(shù)的函數(shù)f(int i) 36 } 37 }; 38 39 void main() 40 { 41 B m; 42 cout << m.d << endl; //輸出的是派生類中的成員變量d的值,注意派生類中覆蓋了基類成員d 43 cout << m.A::d << endl; //輸出基類中成員變量d的值,注意這是使用對象訪問被覆蓋的基類成員的方式 44 m.f(); //調用派生類中不帶參數(shù)的函數(shù)f() 45 // m.f(2); //error!!因為基類中帶一個參數(shù)的函數(shù)f被派生類的同名函數(shù)隱藏掉了,不能這樣訪問,需用作用域解析運算符來訪問 46 m.A::f(200); //使用派生類對象訪問基類中被隱藏函數(shù)的方式 47 } 48 49 /* 執(zhí)行輸出: 50 2 51 3 52 class B::f() 53 class A::f(int i)[param Value:100] 54 class A::f(int i)[param Value:200] 55 */

<3> 怎樣使用派生類的對象訪問基類中被派生類 覆蓋或隱藏 的函數(shù)或變量:

1:使用作用域運算符雙冒號。在使用對象調用基類中的函數(shù)或變量時使用作用域運算符即語句m.A::f(2),這時就能訪問基類中的函數(shù)或變量版本。

注意:訪問基類中被派生類覆蓋了的成員變量只能用這種方法。

2:使用using:該方法只適用于被隱藏或覆蓋的基類函數(shù),在派生類的類定義中使用語句using 把基類的名字包含進來,比如using A::f;就是將基類中的函數(shù)f()的所有重載版本包含進來,重載版本被包含到子類之后,這些重載的函數(shù)版本就相當于是子類的一部分,這時就可以用派生類的對象直接調用被派生類隱藏了的基類版本,比如m.f(2)。但是,使用這種語句還是沒法調用基類在派生類中被覆蓋了的基類的函數(shù),比如m.f() 調用的是派生類中定義的函數(shù)f,要調用被覆蓋的基類中的版本要使用語句m.A::f()才行。

<4> 在派生類的函數(shù)中調用基類中的成員變量和函數(shù)的方法:就是在函數(shù)中使用的被派生類覆蓋的基類成員變量或函數(shù)前用作用域解析符加上基類的類名,即A::f()就是在派生類的函數(shù)中調用基類中被派生類覆蓋了的函數(shù)f()的方法。

關于<3> 和 <4>兩點,請參見示例如下代碼:

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 int a; 7 protected: 8 int b; 9 public: 10 int c; 11 int d; 12 void f() 13 { 14 cout << "class A::f()" << endl; 15 } 16 void f(int i) 17 { 18 cout << "class A::f(int i)" << "[param Value:" << i << "]" << endl; 19 } 20 A() 21 { 22 a = b = c = d = 1; 23 } 24 }; 25 26 class B : public A 27 { 28 public: 29 int d; //覆蓋基類中成員變量d 30 B() 31 { 32 b = c = d = 2; //這個d是指類B中的d,而不是基類中的d 33 A::d = 3; //給基類中被覆蓋的成員d賦值。注意類中訪問被覆蓋成員的方式 34 } 35 using A::f; //使用語句using把類A中的函數(shù)f包含進來,以便可以直接訪問被隱藏了的函數(shù),注意函數(shù)f沒有括號 36 void f() //在子類中重定義基類中的同名函數(shù),雖然參數(shù)列不同,但同樣會隱藏基類中的同名函數(shù) 37 { 38 cout << "class B::f()" << endl;//在子類中覆蓋基類中的同名函數(shù)。注意這里是覆蓋,同時會隱藏基類中的其他同名重載函數(shù) 39 A::f(100); //在函數(shù)中調用基類中的被隱藏同名函數(shù)的方法,使用作用域解析運算符 40 f(200); //正確,因為使用了using語句 41 A ma; 42 ma.f(); //正確,在子類中創(chuàng)建的基類對象,可以直接用對象名調用基類中被子類覆蓋或隱藏了的函數(shù),因為這時不會出現(xiàn)二義性。 43 ma.f(300); //正確,在子類中創(chuàng)建的基類對象,可以直接用對象名調用基類中被子類覆蓋或隱藏了的函數(shù),因為這時不會出現(xiàn)二義性。 44 } 45 void g() 46 { 47 cout << "this class B::g()" << endl; 48 f();//正確,但該語句訪問的是子類中的不帶參數(shù)函數(shù),雖然在類中使用了語句,但直接調用 49 //被子類覆蓋了的基類函數(shù)時不能使用這種方法 50 A::f();//正確,調用被派生類覆蓋了的基類中的函數(shù),注意,雖然使用了但要訪問被子類覆蓋了的函數(shù),只能這樣訪問。 51 } 52 }; 53 54 void main() 55 { 56 B m; 57 m.f(); //調用子類中不帶參數(shù)的函數(shù)f() 58 m.A::f(400);//使用子類對象訪問基類中被隱藏的函數(shù)的方法 59 m.f(500); //調用基類重載的函數(shù),注意這里可以不用運算符,因為在子類中使用了,只要子類沒有覆蓋基類中的方法,都可以這 60 //樣直接調用。 61 m.A::f(600);//當然,使用了后,也可以使用這種方法 62 63 A k; 64 k.f(); 65 } 66 /* 執(zhí)行輸出: 67 class B::f() 68 class A::f(int i)[param Value:100] 69 class A::f(int i)[param Value:200] 70 class A::f() 71 class A::f(int i)[param Value:300] 72 class A::f(int i)[param Value:400] 73 class A::f(int i)[param Value:500] 74 class A::f(int i)[param Value:600] 75 class A::f() 76 */

<5> 基類以私有方式被繼承時,改變基類中的公有成員為派生類中公有的方式:

[1] 使用::作用域運算符。不提倡用這種方法,在派生類的public權限后面用作用域運算符把基類的公有成員包含進來,這樣基類的成員就會成為派生類中的公有成員了。注意一點,如果是函數(shù)的話后面不能加括號:如 A::f; 如果 f 是函數(shù)名稱,不能有括號。

[2] 使用using 語句,現(xiàn)在一般用這種方法,也是在派生類的public權限后使用using 把基類成員包含進來,如using A::f

關于<5>內容,請參見如下示例代碼:

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 int a, b; 8 void f() 9 { 10 cout << "class A::f()" << endl; 11 } 12 void g() 13 { 14 cout << "class A::g()" << endl; 15 } 16 }; 17 18 class B : private A 19 { 20 public: 21 A::f; 22 A::a; //使用作用域運算符使基類中的成員成為公有 23 using A::g; //使用語句使基類中的成員函數(shù)成為派生類中的公有成員,注意函數(shù)名后不能有括號。 24 }; 25 26 void main() 27 { 28 B m; 29 // m.b = 1;//錯誤。因為類B是以私有方式繼承的,基類A中的成員變量b在派生類B中是私有的,不能通過對象訪問私有成員。 30 m.f(); 31 m.g(); 32 m.a = 1; 33 } 34 35 /* 執(zhí)行輸出: 36 class A::f() 37 class A::g() 38 */

【3】繼承時的構造函數(shù)和析構函數(shù)問題

(1)理論知識

<1> 在繼承中,基類的構造函數(shù)構建對象的基類部分,派生類的構造函數(shù)構建對象的派生類部分。

<2> 當創(chuàng)建派生類對象時,先用派生類的構造函數(shù)調用基類的構造函數(shù)構建基類,然后再執(zhí)行派生類構造函數(shù)構造出完整的派生類對象。

即先構造基類再構造派生類的順序。執(zhí)行析構函數(shù)的順序與此相反。

<3> 調用基類帶參數(shù)的構造函數(shù)的方法:

在派生類的構造函數(shù)中使用初始化列表的形式就可以調用基類帶參數(shù)的構造函數(shù)初始化基類成員。如下類B 是類A 的派生類:

1 class A 2 { 3 int m_nA; 4 public: 5 A(int m = 100) : m_nA(m) 6 {} 7 }; 8 9 class B : public A 10 { 11 int m_nB; 12 public: 13 B(int n = 200) : A(n), m_nB(n) // 類B是類A的派生類 14 {} 15 };

<4> 派生類的構造函數(shù)調用基類的構造函數(shù)的方法為:

1:如果派生類沒有顯式用初始化列表調用基類的構造函數(shù)時,這時就會用派生類的構造函數(shù)調用基類的默認構造函數(shù),構造完基類后,才會執(zhí)行派生類的構造函數(shù)函數(shù)體,以保證先執(zhí)行基類構造函數(shù)再執(zhí)行派生類構造函數(shù)的順序,如果基類沒有默認構造函數(shù)就會出錯。

2:如果派生類用顯式的初始化列表調用基類的構造函數(shù)時,這時就會檢測派生類的初始化列表。當檢測到顯式調用基類的構造函數(shù)時,就調用基類的構造函數(shù)構造基類,然后再構造派生類,以保證先執(zhí)行基類構造函數(shù)再執(zhí)行派生類構造函數(shù)的順序。如果基類沒有定義派生類構造函數(shù)初始化列表所調用的構造函數(shù)版本就會出錯。

<5> 如果在基類中沒有定義默認構造函數(shù),但定義了其他構造函數(shù)版本,這時派生類中定義了幾個構造函數(shù)的不同版本,只要派生類有一個構造函數(shù)沒有顯式調用基類中定義的構造函數(shù)版本就會發(fā)生錯誤。因為編譯器會首先檢查派生類構造函數(shù)調用基類構造函數(shù)的匹配情況,如果發(fā)現(xiàn)不匹配就會出錯。即使沒有創(chuàng)建任何類的對象都會出錯,而不管這個派生類的對象有沒有調用派生類的這個構造函數(shù)。比如:基類有一個構造函數(shù)版本A(int i)而沒有定義默認構造函數(shù),派生類B有這幾個版本的構造函數(shù)B() : A(4){},B(int i) : A(5){},再有語句B(int i, int j){} 沒有顯式調用基類定義的構造函數(shù)而是調用基類的默認構造函數(shù),如果創(chuàng)建了B m 和語句B m(1)時都會提示沒有可用的基類默認構造函數(shù)的錯誤。雖然這時類B 的對象m 沒有調用派生類B 的帶有兩個形參的構造函數(shù),但同樣會出錯。

<6> 同樣的道理,如果基類中定義了默認構造函數(shù),卻沒有其他版本的構造函數(shù),而這時派生類卻顯式調用了基類構造函數(shù)的其他版本,這時就會出錯。不管你有沒有創(chuàng)建類的對象,因為編譯器會先在創(chuàng)建對象前就檢查構造函數(shù)的匹配問題。

關于<4>、<5>、<6>三點,請參見如下示例代碼:

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 int a, b; 8 A(int i) 9 { 10 a = b = 1; 11 cout << "Construction A1" << endl; 12 } 13 A(const A & J) 14 { 15 a = b = 2; 16 cout << "Copy Construction" << endl; 17 } 18 ~A() 19 { 20 cout << "Destroy A" << endl; 21 } 22 }; 23 24 class B : public A 25 { 26 public: 27 int c, d; 28 // B() //error!!此語句將要調用基類默認的構造函數(shù),而基類不存在那樣的函數(shù) 29 // { 30 // c = d = 3; 31 // cout << "Constrution B" << endl; 32 // } 33 // B(int i, int j) //error!!此語句將要調用基類默認的構造函數(shù),而基類不存在那樣的函數(shù) 34 // { 35 // c = d = 4; 36 // } 37 B(int i) : A(2) //顯式調用基類帶一個形參的構造函數(shù)。注意語法 38 { 39 c = d = 5; 40 cout << "Constrution B1" << endl; 41 } 42 ~B() 43 { 44 cout << "Destroy B" << endl; 45 } 46 }; 47 48 int main() 49 { 50 B m(0); 51 cout << "m.a = " << m.a << " m.b = " << m.b << endl; 52 B m1(m); //調用基類的拷貝構造函數(shù),調用派生類的默認拷貝構造函數(shù) 53 cout << "m1.a = " << m1.a << " m1.b = " << m1.b << endl; 54 } 55 56 /* 57 Construction A1 58 Constrution B1 59 m.a = 1 m.b = 1 60 Copy Construction 61 m1.a = 2 m1.b = 2 62 Destroy B 63 Destroy A 64 Destroy B 65 Destroy A 66 */

<7> 派生類只能初始化它的直接基類。比如類C 是類B 的子類,而類B 又是類A 的子類,如下示例代碼:

1 class A 2 { 3 int m_nA; 4 public: 5 A(int m = 100) : m_nA(m) 6 {} 7 }; 8 9 class B : public A 10 { 11 int m_nB; 12 public: 13 B(int n = 200) : A(n), m_nB(n) // 類B是類A的派生類 14 {} 15 }; 16 17 class C : public B 18 { 19 int m_nC; 20 public: 21 C(int k = 300) : A(k), m_nC(k) // 非法成員初始化:“A”不是基或成員 22 {} 23 };

將會出錯:“非法成員初始化”。該語句試圖顯式調用類B 的基類類A 的構造函數(shù),這時會出現(xiàn)類A不是類C的基類的錯誤。

<8> 繼承中的拷貝構造函數(shù)和構造函數(shù)一樣,基類的拷貝構造函數(shù)復制基類部分,派生類的拷貝構造函數(shù)復制派生類部分。

<9> 派生類拷貝構造函數(shù)調用基類拷貝構造函數(shù)的方法為:A(const A & m) : B(m){ } 其中B 是基類,A 是派生類。

<10> 如果在派生類中定義了拷貝構造函數(shù)而沒有用初始化列表顯式調用基類的拷貝構造函數(shù),這時不管基類是否定義了拷貝構造函數(shù),若出現(xiàn)派生類對象的復制初始化情況時就將調用基類中的默認構造函數(shù)初始化基類的成員變量。注意是默認構造函數(shù)不是默認拷貝構造函數(shù),如果基類沒有默認構造函數(shù)就會出錯。也就是說,派生類的拷貝構造函數(shù)的默認隱藏形式是B(const B & j) : A(){}這里B 是A 的派生類;也就是說,如果不顯式用初始化列表形式調用基類的拷貝構造函數(shù)時,默認情況下是用初始化列表的形式調用的是基類的默認構造函數(shù)。

<11> 當在派生類中定義了拷貝構造函數(shù)且顯式調用基類的拷貝構造函數(shù),而基類卻沒有定義拷貝構造函數(shù)時,若出現(xiàn)派生類對象復制初始化情況就將調用基類中的默認拷貝構造函數(shù)初始化基類部分,調用派生類的拷貝構造函數(shù)初始化派生類部分。因為拷貝構造函數(shù)只有一種形式,即A(const A & m){},比如當出現(xiàn)調用時A(const A & m) : B(m){} 如果這時基類B 沒有定義拷貝構造函數(shù),則該語句將會調用派生類A 的默認拷貝構造函數(shù)。

<12> 如果基類定義了拷貝構造函數(shù),而派生類沒有定義時,則會調用基類的拷貝構造函數(shù)初始化基類部分,調用派生類的默認拷貝構造函數(shù)初始化派生類部分。

關于拷貝構造函數(shù),示例代碼如下:

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 int a, b; 8 A() 9 { 10 a = b = 1; 11 cout << "Construction A" << endl; 12 } 13 A(int i) 14 { 15 a = b = i; 16 cout << "Construction A1" << endl; 17 } 18 A(const A & obj) 19 { 20 a = obj.a; 21 b = obj.b; 22 cout << "Copy Construction" << endl; 23 } 24 ~A() 25 { 26 cout << "Destroy A" << endl; 27 } 28 }; 29 30 class B : public A 31 { 32 public: 33 int c, d; 34 B() //此語句意味將要調用基類默認的構造函數(shù)A() 35 { 36 c = d = 4; 37 cout << "Constrution B" << endl; 38 } 39 B(int i, int j) //此語句意味將要調用基類默認的構造函數(shù)A() 40 { 41 c = i; 42 d = j; 43 } 44 B(int i) : A(i) //顯式調用基類帶一個形參的構造函數(shù)。注意語法 45 { 46 c = d = i; 47 cout << "Constrution B1" << endl; 48 } 49 B(const B & obj) //這里沒有顯式調用基類的拷貝構造函數(shù),用默認的拷貝構造函數(shù)形式調用基類中的默認構造函數(shù) 50 { 51 c = obj.c; 52 d = obj.d; 53 cout << "Copy Construction B" << endl; 54 } 55 ~B() 56 { 57 cout << "Destroy B" << endl; 58 } 59 }; 60 61 void main() 62 { 63 B m(0); 64 cout << "m.a = " << m.a << " m.b = " << m.b << endl; 65 cout << "m.c = " << m.c << " m.d = " << m.d << endl; 66 B m1(m); //先調用基類的默認構造函數(shù)將A初始化,再調用B類的拷貝構造函數(shù)對B類進行初始化 67 cout << "m1.a = " << m1.a << " m1.b = " << m1.b << endl; 68 cout << "m1.c = " << m1.c << " m1.d = " << m1.d << endl; 69 B m2(10, 20); 70 cout << "m2.a = " << m2.a << " m2.b = " << m2.b << endl; 71 cout << "m2.c = " << m2.c << " m2.d = " << m2.d << endl; 72 } 73 74 /* 75 Construction A1 76 Constrution B1 77 m.a = 0 m.b = 0 78 m.c = 0 m.d = 0 79 Construction A 80 Copy Construction B 81 m1.a = 1 m1.b = 1 82 m1.c = 0 m1.d = 0 83 Construction A 84 m2.a = 1 m2.b = 1 85 m2.c = 10 m2.d = 20 86 Destroy B 87 Destroy A 88 Destroy B 89 Destroy A 90 Destroy B 91 Destroy A 92 */

【4】多重繼承與虛基類

(1)理論知識

<1> C++允許一個派生類從多個基類繼承,這種繼承方式稱為多重繼承。當從多個基類繼承時每個基類之間用逗號隔開,比如class A : public B, public C { }就表示派生類A從基類B 和C 繼承而來。

<2> 多重繼承的構造函數(shù)和析構函數(shù):多重繼承中初始化的次序是按繼承的次序來調用構造函數(shù)的而不是按初始化列表的次序,比如有class A : public B, public C{ }那么在定義類A 的對象A m 時將首先由類A 的構造函數(shù)調用類B 的構造函數(shù)初始化B,然后再調用類C 的構造函數(shù)初始化C,最后再初始化對象A,這與在類A 中的初始化列表次序無關。

<3> 多重繼承中的二義性問題:

1:成員名重復:

比如類A 從類B 和C 繼承而來,而類B 和C 中都包含有一個名字為f 的成員函數(shù),這時派生類A 創(chuàng)建一個對象,比如A m; 語句m.f()將調用類B 中的f 函數(shù)呢還是類C 中的f 函數(shù)呢?

2:多個基類副本:

比如類C 和B 都從類D 繼承而來,這時class A : public B, public C { } 類A 從類C 和類B 同時繼承而來,這時類A 中就有兩個類D 的副本,一個是由類B 繼承而來的,一個是由類C 繼承而來的,當類A 的對象比如A m;要訪問類D 中的成員函數(shù)f 時,語句m.f()就會出現(xiàn)二義性,這個f 函數(shù)是調用的類B 繼承來的 f 還是訪問類C 繼承來的函數(shù)f 呢。

3:在第2 種情況下還有種情況,語句class A : public B, public C { },因為類A 首先使用類B 的構造函數(shù)調用共同基類D 的構造函數(shù)構造第一個類D的副本,然后再使用類C 的構造函數(shù)調用共同基類D的構造函數(shù)構造第二個類D的副本。類A的對象m 總共有兩個共享基類D的副本,這時如果類D中有一個公共成員變量d,則語句m.B::d 和m.D::d 都是訪問的同一變量,類B 和類D 都共享同一個副本,既如果有語句m.D::d=3 則m.B::d 也將是3。這時m.C::d 的值不受影響而是原來的值。為什么會這樣呢?因為類A 的對象m 總共只有兩個類D 的副本,所以類A 的對象m 就會從A 繼承來的兩個直接基類B 和C 中,把從共同基類D 中最先構造的第一個副本作為類A 的副本,即類B 構造的D 的副本。因為class A:public B,public C{}最先使用B 的構造函數(shù)調用共同基類類D 創(chuàng)造D 的第一個副本,所以類B 和類D 共享同一個副本。

二義性問題示例代碼如下:

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 int a; 8 A(int i) 9 { 10 a = i; 11 cout << "A" << endl; 12 } 13 }; 14 15 class B : public A 16 { 17 public: 18 int b; 19 B() : A(4) 20 { 21 cout << "B" << endl; 22 } 23 }; 24 25 class C : public A 26 { 27 public: 28 int c; 29 C() : A(5) 30 { 31 cout << "C" << endl; 32 } 33 }; 34 35 class D : public B, public C 36 { 37 public: 38 int d; 39 D() : C(), B() 40 { 41 cout << "D" << endl; 42 } 43 }; 44 45 int main() 46 { 47 D m; //ABACD,注意這里的構造順序 48 // m.a = 1; //error!!a成員出現(xiàn)二義性 49 m.B::a = 1; 50 cout << "m.B::a = " << m.B::a << endl; //1 51 m.A::a = 3; 52 cout << "m.B::a = " << m.B::a << " m.A::a = " << m.A::a << endl;//33 53 m.C::a = 2; 54 cout << "m.C::a = " << m.C::a << endl; 55 } 56 57 /* 執(zhí)行結果: 58 A 59 B 60 A 61 C 62 D 63 m.B::a = 1 64 m.B::a = 3 m.A::a = 3 65 m.C::a = 2 66 */

4:解決方法:對于第1 和第2 種情況都可以使用作用域運算符“冒號”來限定要訪問的類名來解決二義性。

但對于第二種情況一般不允許出現(xiàn)兩個基類的副本,這時可以使用虛基類來解決這個問題,一旦定義了虛基類,就只會有一個基類的副本。

<4> 虛基類:

方法是使用virtual 關鍵字,比如class B : public virtual D { },class C : virtual public D { } 注意關鍵字virtual 的次序無關緊要。類B 和類C 以虛基類的方式從類D 繼承,這樣的話從類B 和類C 同時繼承的類時就會只創(chuàng)建一個類 D的副本,比如class A:public B, public C{ } 這時類A 的對象就只會有一個類D 的副本,類A 類B 類C 類D 四個類 都共享一個類D 的副本,比如類D 有一個公有成員變量d,則m.d 和m.A::d,m.B::d,m.C::d,m.D::d 都將訪問的是同一個變量。這樣類A 的對象調用類D 中的成員時就不會出現(xiàn)二義性了。

<5> 虛基類的構造函數(shù):

比如class B : public virtual D{};

class C : virtual public D{};

class A : public B, public C{};

這時當創(chuàng)建類A 的對象A m 時初始化虛基類D 將會使用類A 的構造函數(shù)直接調用虛基類的構造函數(shù)初始化虛基類部分,而不會使用類B 或者類C 的構造函數(shù)調用虛基類的構造函數(shù)初始化虛基類部分,這樣就保證了只有一個虛基類的副本。 但是當創(chuàng)建一個類B 和類C 的對象時仍然會使用類B 和類C 中的構造函數(shù)調用虛基類的構造函數(shù)初始化虛基類。

虛基類及其構造函數(shù)示例代碼:

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 int a; 8 A() 9 { 10 a = 10; 11 cout << "Default Construction" << endl; 12 } 13 A(int i) 14 { 15 a = i; 16 cout << "A" << endl; 17 } 18 }; 19 20 class B : public virtual A //表示以虛基類的方式繼承 21 { 22 public: 23 int b; 24 B(int i) : A(4) 25 { 26 cout << "B" << endl; 27 } 28 }; 29 30 class C : public virtual A //表示以虛基類的方式繼承 31 { 32 public: 33 int c; 34 C() : A(5) 35 { 36 cout << "C" << endl; 37 } 38 }; 39 40 class D : public B, public C 41 { 42 public: 43 int d; 44 D() : A(6), C(), B(2) 45 { 46 cout << "D" << endl; 47 } 48 }; 49 50 class E : public B, public C 51 { 52 public: 53 int e; 54 E() : C(), B(2) 55 { 56 cout << "E" << endl; 57 } 58 }; 59 60 //因為類A是虛基類,所以類D會直接調用虛基類的構造函數(shù)構造虛基類部分, 61 //而不會使用類B或者類C的構造函數(shù)來調用虛基類的構造函數(shù)初始化虛基類 62 //部分。要調用虛基類中的帶參數(shù)的構造函數(shù)必須在這里顯式調用,如果不 63 //顯式調用就將調用虛基類的默認構造函數(shù)。 64 65 void main() 66 { 67 D m; //ABCD, 注意沒有重復的虛基類副本A 68 cout << m.a << endl; //4 69 m.B::a = 1; 70 cout << m.a << ";" << m.A::a << ";" << m.B::a << ";" << m.C::a << endl; //四個類公用一個虛基類的副本,不存在二義性 71 E m1; 72 cout << m1.a << ";" << m1.A::a << ";" << m1.B::a << ";" << m1.C::a; //四個類公用一個虛基類的副本,不存在二義性 73 } 74 75 /* 76 A 77 B 78 C 79 D 80 6 81 1;1;1;1 82 Default Construction 83 B 84 C 85 E 86 10;10;10;10 87 */

希望有所收獲,僅此而已。

?

轉載于:https://www.cnblogs.com/Braveliu/archive/2013/01/04/2843916.html

總結

以上是生活随笔為你收集整理的继承专题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。