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; }此時(shí)father1, father2都含有class_name成員, 而子類不含, 所以子類實(shí)例化的對(duì)象中如果要使用class_name, 必須要加上作用域, 否則以上代碼編譯后就會(huì)如下:
修改后代碼如下:
#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; }結(jié)果為:
或者讓子類用同名成員覆蓋該成員, 代碼如下:
#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; }結(jié)果如下:
1.2 菱形繼承的問題
也叫鉆石繼承, 即兩個(gè)派生類同時(shí)繼承于同一個(gè)基類, 又有一個(gè)派生類同時(shí)繼承于以上兩個(gè)派生類.
如下代碼所示:
#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: };這時(shí)候father1, father2類都繼承了grandfaher類的member_value屬性, 那么public_inherited_son在多繼承father1和father2類的時(shí)候就會(huì)不清楚用誰的成員, 用以下測(cè)試代碼:
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; }編譯發(fā)現(xiàn)果然有二義性:
這種情況當(dāng)然可以通過作用域來區(qū)分, 但是如果實(shí)際情況是這份數(shù)據(jù)只需要一份的話, 菱形繼承之后會(huì)造成資源浪費(fèi), 那么就可以用virtual繼承來解決這樣的問題.
2 虛繼承
2.1 普通繼承的內(nèi)存模型
當(dāng)不加虛繼承關(guān)鍵字的時(shí)候, 繼承中內(nèi)存的情況如下圖:
2.2 虛繼承實(shí)現(xiàn)
復(fù)用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; }這樣編譯就沒問題了, 運(yùn)行結(jié)果為:
原因是, 當(dāng)使用虛繼承之后, 該數(shù)據(jù)成員便只有一份, 此時(shí)該內(nèi)存模型為:
虛繼承后, 子類繼承的是一個(gè)指針, 這個(gè)指針指向的是一個(gè)虛繼承表, 表內(nèi)記錄了繼承的數(shù)據(jù)的位置與本身的偏移量(即用的是基類的數(shù)據(jù), 這樣保證了數(shù)據(jù)的唯一性), 這樣做就解決了菱形問題.
3 多態(tài)
3.1 靜態(tài)多態(tài)和動(dòng)態(tài)多態(tài)
函數(shù)重載, 運(yùn)算符重載等等, 地址早綁定, 以下是一個(gè)靜態(tài)繼承的例子:
#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; }運(yùn)行時(shí)發(fā)現(xiàn):
這就是因?yàn)殪o態(tài)多態(tài)早綁定的性質(zhì), 因?yàn)樵诰幾g階段, 其father中的printClassName函數(shù)就已經(jīng)確定了函數(shù)地址了, 就是father實(shí)例化對(duì)象的printClassName, 哪怕是通過子類進(jìn)行類型轉(zhuǎn)換的, 其作為參數(shù)傳進(jìn)PrintClassName中時(shí), 也會(huì)退化為傳入其父類的空間.
如果我們按照以下方法做:
#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; }運(yùn)行發(fā)現(xiàn):
即在father類的printClassName函數(shù)前面加virtual, 這樣的話就會(huì)實(shí)現(xiàn)與靜態(tài)多態(tài)早綁定相對(duì)應(yīng)的晚綁定, 即在運(yùn)行時(shí)才給函數(shù)賦值. 如需動(dòng)態(tài)多態(tài), 子類需要重寫父類的虛函數(shù)(區(qū)別于重載, 重寫為函數(shù)返回值類型, 函數(shù)名稱, 參數(shù)列表需完全相同). 這樣做就能達(dá)到父類的指針或引用指向子類對(duì)象時(shí), 調(diào)用其虛函數(shù)會(huì)調(diào)用子類對(duì)象對(duì)應(yīng)的重寫函數(shù).
3.2 動(dòng)態(tài)多態(tài)的實(shí)現(xiàn)原理
當(dāng)在父類的函數(shù)前加上virtual關(guān)鍵字, 其就會(huì)作為一個(gè)函數(shù)指針存儲(chǔ)在類中, 指向虛函數(shù)表, 當(dāng)運(yùn)行時(shí), 從虛函數(shù)表中獲取出當(dāng)前的函數(shù)地址并運(yùn)行(如果發(fā)生重寫, 會(huì)換成繼承后的地址).
3.3 多態(tài)各類情況總結(jié)
用以下測(cè)試類:
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 普通繼承
如下代碼測(cè)試:
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(); }結(jié)果為:
總結(jié)如下:
- 普通繼承的子類實(shí)例化的對(duì)象無論是調(diào)用普通成員函數(shù)或者是虛成員函數(shù), 都會(huì)調(diào)用子類自己的.
- 用父類等號(hào)取出其子類實(shí)例化對(duì)象中的父類調(diào)用普通成員函數(shù)或者是虛成員函數(shù), 都會(huì)調(diào)用父類自己的.
- 用父類引用取出其子類實(shí)例化對(duì)象中的父類調(diào)用普通成員函數(shù), 會(huì)調(diào)用父類自己的, 如果是虛函數(shù), 會(huì)調(diào)用子類的.
3.3.2 虛繼承
用如下代碼測(cè)試:
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(); }結(jié)果為:
總結(jié)如下:
- 虛繼承的子類實(shí)例化的對(duì)象無論是調(diào)用普通成員函數(shù)或者是虛成員函數(shù), 都會(huì)調(diào)用子類自己的.
- 用父類等號(hào)取出其子類實(shí)例化對(duì)象中的父類調(diào)用普通成員函數(shù)或者是虛成員函數(shù), 都會(huì)調(diào)用父類自己的.
- 用父類引用取出其子類實(shí)例化對(duì)象中的父類調(diào)用普通成員函數(shù), 會(huì)調(diào)用父類自己的, 如果是虛函數(shù), 會(huì)調(diào)用子類的.
3.3.3 虛析構(gòu)
如下例子:
#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; }這樣運(yùn)行結(jié)果為:
發(fā)現(xiàn)并沒有調(diào)用子類的析構(gòu)函數(shù), 也就是說當(dāng)用父類去引用或用父類指針指向子類對(duì)象時(shí), 父類本身析構(gòu)過程并不會(huì)調(diào)用子類析構(gòu)函數(shù), 這樣容易導(dǎo)致內(nèi)存泄漏(以上son的構(gòu)造過程中m_pvalue指向了堆區(qū)申請(qǐng)的空間而沒有釋放掉).
我們把父類的析構(gòu)函數(shù)設(shè)置為虛析構(gòu)函數(shù), 如下代碼:
class father { public:father() {cout << "calling father's constructor" << endl;}virtual ~father() {cout << "calling father's destructor" << endl;}virtual void printClassName() = 0; };這樣就可以避免此問題, 且虛析構(gòu)函數(shù)相較于其他虛函數(shù), 其本身父類的函數(shù)也會(huì)走, 如下結(jié)果:
如果父類的析構(gòu)函數(shù)聲明為純虛析構(gòu), 如下代碼:
class father { public:father() {cout << "calling father's constructor" << endl;}virtual ~father() = 0;virtual void printClassName() = 0; };那么編譯會(huì)出現(xiàn)如下錯(cuò)誤:
因?yàn)楦割惖募兲摵瘮?shù)是必須要走的, 所以其可以在類的實(shí)現(xiàn)文件中實(shí)現(xiàn)一下, 加入如下代碼:
father::~father() {cout << "calling father's destructor" << endl; }這樣編譯就沒問題了, 運(yùn)行結(jié)果也和上面一樣.
3.3.4 構(gòu)造函數(shù)不可以是虛函數(shù)
同析構(gòu)函數(shù)不同的是, 構(gòu)造函數(shù)不可以設(shè)置為虛函數(shù), 因?yàn)闃?gòu)造函數(shù)調(diào)用時(shí)虛函數(shù)表還沒有初始化. 而且虛函數(shù)的作用在于通過父類的指針或者引用來調(diào)用它的時(shí)候能夠變成調(diào)用子類的那個(gè)成員函數(shù)。 而構(gòu)造函數(shù)是在創(chuàng)建對(duì)象時(shí)自動(dòng)調(diào)用的,不可能通過父類的指針或者引用去調(diào)用,因此也就規(guī)定構(gòu)造函數(shù)不能是虛函數(shù), 沒有這個(gè)必要.
3.3.5 靜態(tài)函數(shù)不可以是虛函數(shù)
虛函數(shù)依靠vptr和vtable來處理。vptr是一個(gè)指針,在類的構(gòu)造函數(shù)中創(chuàng)建生成,并且只能用this指針來訪問它,因?yàn)樗穷惖囊粋€(gè)成員,并且vptr指向保存虛函數(shù)地址的vtable. 對(duì)于靜態(tài)成員函數(shù),它沒有this指針,所以無法訪問vptr. 這就是為何static函數(shù)不能為virtual.
4 重載和多態(tài)
主要是重載, 覆蓋, 隱藏的區(qū)別:
重載: 同一個(gè)類中(兩個(gè)函數(shù)都在父類, 或者都在子類, 這樣用父類指針或者用子類調(diào)用不會(huì)有什么爭(zhēng)議)
覆蓋: 父類虛函數(shù)被子類同名, 同參數(shù)函數(shù)覆蓋, 這就是簡(jiǎn)單的多態(tài),需要注意,覆蓋需要子類的方法的返回值小于等于父類的返回值,訪問權(quán)限大于父類的訪問權(quán)限。
隱藏: 父類與子類存在同名, 參數(shù)不同函數(shù), 無論父類函數(shù)是否為虛函數(shù), 都會(huì)被隱藏.
? ? ? ? ?父類與子類存在同名, 參數(shù)相同參數(shù), 父類函數(shù)不是虛函數(shù), 也會(huì)被隱藏.
? ? ? ? ?隱藏函數(shù)可以通過父類作用域調(diào)用.
總結(jié)
以上是生活随笔為你收集整理的C++中类和对象的一些注意事项 --- 多态的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ueditor如何设置上传图片的高度宽度
- 下一篇: go去掉最后一个字符_Go语言去除字符串