日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

C++中类和对象的一些注意事项 --- 多态

發布時間:2024/7/23 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++中类和对象的一些注意事项 --- 多态 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 一些繼承中的問題

1.1 多繼承中父類含有重名成員問題

如下:

#include <iostream> #include <string> using namespace std;class father1 { public:father1() {class_name = "father1";}string class_name; };class father2 { public:father2() {class_name = "father2";}string class_name; };class public_inherited_son : public father1, public father2 { public: };int main() {public_inherited_son public_inherited_son_instance1;cout << public_inherited_son_instance1.class_name << endl;return 0; }

此時father1, father2都含有class_name成員, 而子類不含, 所以子類實例化的對象中如果要使用class_name, 必須要加上作用域, 否則以上代碼編譯后就會如下:

修改后代碼如下:

#include <iostream> #include <string> using namespace std;class father1 { public:father1() {class_name = "father1";}string class_name; };class father2 { public:father2() {class_name = "father2";}string class_name; };class public_inherited_son : public father1, public father2 { public: };int main() {public_inherited_son public_inherited_son_instance1;cout << public_inherited_son_instance1.father1::class_name << endl;cout << public_inherited_son_instance1.father2::class_name << endl;return 0; }

結果為:

或者讓子類用同名成員覆蓋該成員, 代碼如下:

#include <iostream> #include <string> using namespace std;class father1 { public:father1() {class_name = "father1";}string class_name; };class father2 { public:father2() {class_name = "father2";}string class_name; };class public_inherited_son : public father1, public father2 { public:public_inherited_son() {class_name = "public_inherited_son";}string class_name; };int main() {public_inherited_son public_inherited_son_instance1;cout << public_inherited_son_instance1.class_name << endl;return 0; }

結果如下:

1.2 菱形繼承的問題

也叫鉆石繼承, 即兩個派生類同時繼承于同一個基類, 又有一個派生類同時繼承于以上兩個派生類.

如下代碼所示:

#include <iostream> #include <string> using namespace std;class grandfather { public:int member_value; };class father1 : public grandfather { public: };class father2 : public grandfather{ public: };class public_inherited_son : public father1, public father2 { public: };

這時候father1, father2類都繼承了grandfaher類的member_value屬性, 那么public_inherited_son在多繼承father1和father2類的時候就會不清楚用誰的成員, 用以下測試代碼:

int main() {public_inherited_son public_inherited_son_instance1;public_inherited_son_instance1.member_value = 10;cout << public_inherited_son_instance1.member_value << endl;return 0; }

編譯發現果然有二義性:

這種情況當然可以通過作用域來區分, 但是如果實際情況是這份數據只需要一份的話, 菱形繼承之后會造成資源浪費, 那么就可以用virtual繼承來解決這樣的問題.

2 虛繼承

2.1 普通繼承的內存模型

當不加虛繼承關鍵字的時候, 繼承中內存的情況如下圖:

2.2 虛繼承實現

復用1.2中的例子, 如果修改繼承方式如下:

#include <iostream> #include <string> using namespace std;class grandfather { public:int member_value; };class father1 : virtual public grandfather { public: };class father2 : virtual public grandfather{ public: };class public_inherited_son : public father1, public father2 { public: };int main() {public_inherited_son public_inherited_son_instance1;public_inherited_son_instance1.member_value = 10;cout << "public_inherited_son_instance1.member_value = 10" << endl;cout << "current public_inherited_son_instance1.member_value is" << public_inherited_son_instance1.member_value << endl;cout << "current public_inherited_son_instance1.father1::member_value is" << public_inherited_son_instance1.father1::member_value << endl;cout << "current public_inherited_son_instance1.father2::member_value is" << public_inherited_son_instance1.father2::member_value << endl << endl;public_inherited_son_instance1.father1::member_value = 20;cout << "change public_inherited_son_instance1.father1::member_value to 20" << endl;cout << "current public_inherited_son_instance1.member_value is" << public_inherited_son_instance1.member_value << endl;cout << "current public_inherited_son_instance1.father1::member_value is" << public_inherited_son_instance1.father1::member_value << endl;cout << "current public_inherited_son_instance1.father2::member_value is" << public_inherited_son_instance1.father2::member_value << endl << endl;public_inherited_son_instance1.father2::member_value = 30;cout << "change public_inherited_son_instance1.father1::member_value to 30" << endl;cout << "current public_inherited_son_instance1.member_value is" << public_inherited_son_instance1.member_value << endl;cout << "current public_inherited_son_instance1.father1::member_value is" << public_inherited_son_instance1.father1::member_value << endl;cout << "current public_inherited_son_instance1.father2::member_value is" << public_inherited_son_instance1.father2::member_value << endl << endl;return 0; }

這樣編譯就沒問題了, 運行結果為:

原因是, 當使用虛繼承之后, 該數據成員便只有一份, 此時該內存模型為:

虛繼承后, 子類繼承的是一個指針, 這個指針指向的是一個虛繼承表, 表內記錄了繼承的數據的位置與本身的偏移量(即用的是基類的數據, 這樣保證了數據的唯一性), 這樣做就解決了菱形問題.

3 多態

3.1 靜態多態和動態多態

函數重載, 運算符重載等等, 地址早綁定, 以下是一個靜態繼承的例子:

#include <iostream> #include <string> using namespace std;class father { public:void printClassName() {cout << "this is class father" << endl;} };class son1 : public father { public:void printClassName() {cout << "this is class son1" << endl;} };void PrintClassName(father &fatherInstance) {fatherInstance.printClassName(); }void test1() {son1 son1Instance1;PrintClassName(son1Instance1); }int main() {test1();return 0; }

運行時發現:


這就是因為靜態多態早綁定的性質, 因為在編譯階段, 其father中的printClassName函數就已經確定了函數地址了, 就是father實例化對象的printClassName, 哪怕是通過子類進行類型轉換的, 其作為參數傳進PrintClassName中時, 也會退化為傳入其父類的空間.

如果我們按照以下方法做:

#include <iostream> #include <string> using namespace std;class father { public:virtual void printClassName() {cout << "this is class father" << endl;} };class son1 : public father { public:void printClassName() {cout << "this is class son1" << endl;} };void PrintClassName(father &fatherInstance) {fatherInstance.printClassName(); }void test1() {son1 son1Instance1;PrintClassName(son1Instance1); }int main() {test1();return 0; }

運行發現:

即在father類的printClassName函數前面加virtual, 這樣的話就會實現與靜態多態早綁定相對應的晚綁定, 即在運行時才給函數賦值. 如需動態多態, 子類需要重寫父類的虛函數(區別于重載, 重寫為函數返回值類型, 函數名稱, 參數列表需完全相同). 這樣做就能達到父類的指針或引用指向子類對象時, 調用其虛函數會調用子類對象對應的重寫函數.

3.2 動態多態的實現原理

當在父類的函數前加上virtual關鍵字, 其就會作為一個函數指針存儲在類中, 指向虛函數表, 當運行時, 從虛函數表中獲取出當前的函數地址并運行(如果發生重寫, 會換成繼承后的地址).

3.3 多態各類情況總結

用以下測試類:

class father { public:void printFuncName() {cout << "this is father's function" << endl << endl;}virtual void virtualPrintFuncName() {cout << "this is father's virtual function" << endl << endl;} };class virtualInheritedSon : virtual public father { public:void printFuncName() {cout << "this is virtualInheritedSon's function" << endl << endl;}void virtualPrintFuncName() {cout << "this is virtualInheritedSon's virtual function" << endl << endl;} };class normalInheritedSon : public father { public:void printFuncName() {cout << "this is normalInheritedSon's function" << endl << endl;}void virtualPrintFuncName() {cout << "this is normalInheritedSon's virtual function" << endl << endl;} };

3.3.1 普通繼承

如下代碼測試:

void normalInheritedTest() {normalInheritedSon normalInheritedSonInstance;cout << "now normalInheritedSonInstance printFuncName" << endl << endl;normalInheritedSonInstance.printFuncName();cout << "now normalInheritedSonInstance virtualPrintFuncName" << endl << endl;normalInheritedSonInstance.virtualPrintFuncName();cout << "now fatherInstance = normalInheritedSonInstance" << endl << endl;father fatherInstance = normalInheritedSonInstance;cout << "now fatherInstance printFuncName" << endl << endl;fatherInstance.printFuncName();cout << "now fatherInstance virtualPrintFuncName" << endl << endl;fatherInstance.virtualPrintFuncName();cout << "now rFatherInstance = normalInheritedSonInstance" << endl << endl;father &rFatherInstance = normalInheritedSonInstance;cout << "now rFatherInstance printFuncName" << endl << endl;rFatherInstance.printFuncName();cout << "now rFatherInstance virtualPrintFuncName" << endl << endl;rFatherInstance.virtualPrintFuncName(); }

結果為:

總結如下:

  • 普通繼承的子類實例化的對象無論是調用普通成員函數或者是虛成員函數, 都會調用子類自己的.
  • 用父類等號取出其子類實例化對象中的父類調用普通成員函數或者是虛成員函數, 都會調用父類自己的.
  • 用父類引用取出其子類實例化對象中的父類調用普通成員函數, 會調用父類自己的, 如果是虛函數, 會調用子類的.

3.3.2 虛繼承

用如下代碼測試:

void normalInheritedTest() {normalInheritedSon normalInheritedSonInstance;cout << "now normalInheritedSonInstance printFuncName" << endl << endl;normalInheritedSonInstance.printFuncName();cout << "now normalInheritedSonInstance virtualPrintFuncName" << endl << endl;normalInheritedSonInstance.virtualPrintFuncName();cout << "now fatherInstance = normalInheritedSonInstance" << endl << endl;father fatherInstance = normalInheritedSonInstance;cout << "now fatherInstance printFuncName" << endl << endl;fatherInstance.printFuncName();cout << "now fatherInstance virtualPrintFuncName" << endl << endl;fatherInstance.virtualPrintFuncName();cout << "now rFatherInstance = normalInheritedSonInstance" << endl << endl;father &rFatherInstance = normalInheritedSonInstance;cout << "now rFatherInstance printFuncName" << endl << endl;rFatherInstance.printFuncName();cout << "now rFatherInstance virtualPrintFuncName" << endl << endl;rFatherInstance.virtualPrintFuncName(); }

結果為:

總結如下:

  • 虛繼承的子類實例化的對象無論是調用普通成員函數或者是虛成員函數, 都會調用子類自己的.
  • 用父類等號取出其子類實例化對象中的父類調用普通成員函數或者是虛成員函數, 都會調用父類自己的.
  • 用父類引用取出其子類實例化對象中的父類調用普通成員函數, 會調用父類自己的, 如果是虛函數, 會調用子類的.

3.3.3 虛析構

如下例子:

#include <iostream> using namespace std;class father { public:father() {cout << "calling father's constructor" << endl;}~father() {cout << "calling father's destructor" << endl;}virtual void printClassName() = 0; };class son : public father { public:son(int value) {cout << "calling son's constructor" << endl;m_pvalue = new int(value);}~son() {cout << "calling son's destructor" << endl;if (m_pvalue != NULL) {delete m_pvalue;m_pvalue = NULL;}}void printClassName() {cout << "this is class son" << endl;cout << "my value is " << *m_pvalue << endl;}int *m_pvalue; };void test1() {father* pfatherInstance = new son(10);pfatherInstance->printClassName();delete pfatherInstance; }int main() {test1();return 0; }

這樣運行結果為:

發現并沒有調用子類的析構函數, 也就是說當用父類去引用或用父類指針指向子類對象時, 父類本身析構過程并不會調用子類析構函數, 這樣容易導致內存泄漏(以上son的構造過程中m_pvalue指向了堆區申請的空間而沒有釋放掉).

我們把父類的析構函數設置為虛析構函數, 如下代碼:

class father { public:father() {cout << "calling father's constructor" << endl;}virtual ~father() {cout << "calling father's destructor" << endl;}virtual void printClassName() = 0; };

這樣就可以避免此問題, 且虛析構函數相較于其他虛函數, 其本身父類的函數也會走, 如下結果:

如果父類的析構函數聲明為純虛析構, 如下代碼:

class father { public:father() {cout << "calling father's constructor" << endl;}virtual ~father() = 0;virtual void printClassName() = 0; };

那么編譯會出現如下錯誤:

因為父類的純虛函數是必須要走的, 所以其可以在類的實現文件中實現一下, 加入如下代碼:

father::~father() {cout << "calling father's destructor" << endl; }

這樣編譯就沒問題了, 運行結果也和上面一樣.

3.3.4 構造函數不可以是虛函數

同析構函數不同的是, 構造函數不可以設置為虛函數, 因為構造函數調用時虛函數表還沒有初始化. 而且虛函數的作用在于通過父類的指針或者引用來調用它的時候能夠變成調用子類的那個成員函數。 而構造函數是在創建對象時自動調用的,不可能通過父類的指針或者引用去調用,因此也就規定構造函數不能是虛函數, 沒有這個必要.

3.3.5 靜態函數不可以是虛函數

虛函數依靠vptr和vtable來處理。vptr是一個指針,在類的構造函數中創建生成,并且只能用this指針來訪問它,因為它是類的一個成員,并且vptr指向保存虛函數地址的vtable. 對于靜態成員函數,它沒有this指針,所以無法訪問vptr. 這就是為何static函數不能為virtual.

4 重載和多態

主要是重載, 覆蓋, 隱藏的區別:

重載: 同一個類中(兩個函數都在父類, 或者都在子類, 這樣用父類指針或者用子類調用不會有什么爭議)

覆蓋: 父類虛函數被子類同名, 同參數函數覆蓋, 這就是簡單的多態,需要注意,覆蓋需要子類的方法的返回值小于等于父類的返回值,訪問權限大于父類的訪問權限。

隱藏: 父類與子類存在同名, 參數不同函數, 無論父類函數是否為虛函數, 都會被隱藏.

? ? ? ? ?父類與子類存在同名, 參數相同參數, 父類函數不是虛函數, 也會被隱藏.

? ? ? ? ?隱藏函數可以通過父類作用域調用.

總結

以上是生活随笔為你收集整理的C++中类和对象的一些注意事项 --- 多态的全部內容,希望文章能夠幫你解決所遇到的問題。

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