C++多重繼承及多態(tài)性原理實(shí)例詳解
一、多重繼承的二義性問(wèn)題
舉例:
#include <iostream> using namespace std; class BaseA { public: void fun() { cout << "A.fun" << endl; } }; class BaseB { public: void fun() { cout << "B.fun" << endl; } void tun() { cout << "B.tun" << endl; } }; class Derived :public BaseA, public BaseB { public: void tun() { cout << "D.tun" << endl; } void hun() { fun(); } //此處調(diào)用出現(xiàn)二義性,編譯無(wú)法通過(guò) }; int main() { Derived d, * p = &d; d.hun(); return 0; }
類名限定
void hun() { BaseA::fun(); } //改寫上述代碼的第14行,用BaseA類名限定調(diào)用的函數(shù)
d.BaseB::fun(); //派生類對(duì)象調(diào)用基類同名函數(shù)時(shí),使用類名限定
p->BaseB::fun(); //派生類指針調(diào)用基類同名函數(shù)時(shí),使用類名限定
名字支配規(guī)則
如果存在兩個(gè)或多個(gè)包含關(guān)系的作用域,外層聲明了一個(gè)名字,而內(nèi)層沒(méi)有再次聲明相同的名字,則外層名字在內(nèi)層可見(jiàn);如果在內(nèi)層聲明了相同的名字,則外層名字在內(nèi)層不可見(jiàn)——隱藏(屏蔽)規(guī)則。
在類的派生層次結(jié)構(gòu)中,基類的成員和派生類新增的成員都具有類作用域,二者的作用域不同:基類在外層,派生類在內(nèi)層。如果派生類聲明了一個(gè)和基類成員同名的新成員,則派生類的新成員就會(huì)屏蔽基類的同名成員,直接使用成員名只能訪問(wèn)到派生類新增的成員。(如需使用從基類繼承的成員,應(yīng)當(dāng)使用基類名限定)
#include <iostream> using namespace std; class Base { public: void fun() { cout << "A.fun" << endl; } Base(int x = 1, int y = 2) :x(x), y(y) {} int x, y; }; class Derived :public Base{ public: void tun() { cout << "D.tun" << endl; } void fun() { cout << "D.fun" << endl; } Derived(int x = 0) :x(x) {} int x; }; int main() { Derived d, * p = &d; d.fun(); //輸出的結(jié)果為 D.fun cout << p->x << " " << p->y << " " << p->Base::x << endl; //輸出為 0 2 1 d.Base::fun(); //輸出為 A.fun }
二、虛基類
虛基類的使用目的:在繼承間接基類時(shí)只保留一份成員。
聲明虛基類需要在派生類定義時(shí),指定繼承方式的時(shí)候聲明,只需要在訪問(wèn)標(biāo)號(hào)(public、protected、private繼承方式)前加上virtual關(guān)鍵字。注意:為了保證虛基類在派生類中只繼承依次,應(yīng)當(dāng)在該基類的所有直接派生類中聲明為虛基類,否則仍會(huì)出現(xiàn)多次繼承。
派生類不僅要負(fù)責(zé)對(duì)直接基類進(jìn)行初始化,還要負(fù)責(zé)對(duì)虛基類初始化;若多重繼承中沒(méi)有虛基類,則派生類只需要對(duì)間接基類進(jìn)行初始化,而對(duì)基類的初始化由各個(gè)間接基類完成(會(huì)因此產(chǎn)生多個(gè)基類的副本保存在各個(gè)間接基類中)。
#include <iostream> using namespace std; class Base { public: Base(int n) { nv = n; cout << "Member of Base" << endl; } void fun() { cout << "fun of Base" << endl; } private: int nv; }; class A:virtual public Base { //聲明Base為虛基類,作為間接基類都需要使用virtual關(guān)鍵字 public: A(int a) :Base(a) { cout << "Member of A" << endl; } private: int na; }; class B :virtual public Base { //聲明Base為虛基類,作為間接基類都需要使用virtual關(guān)鍵字 public: B(int b) :Base(b) { cout << "Member of B" << endl; } private: int nb; }; class Derived :public A, public B { public: Derived(int n) :Base(n), A(n), B(n) { cout << "Member of Derived" << endl; } //派生類的構(gòu)造函數(shù)初始化列表,先調(diào)用基類Base的構(gòu)造函數(shù),再依次調(diào)用間接基類A、B的構(gòu)造函數(shù) //由于虛基類Base中沒(méi)有默認(rèn)構(gòu)造函數(shù)(允許無(wú)參構(gòu)造),所以從Base類繼承的所有派生類的構(gòu)造函數(shù)初始化表中都需要顯式調(diào)用基類(包括間接基類)的構(gòu)造函數(shù),完成初始化 private: int nd; }; int main() { Derived de(3); de.fun();//不會(huì)產(chǎn)生二義性 return 0; }
關(guān)于虛基類的說(shuō)明:
一個(gè)類在一個(gè)類族中既可以被用作虛基類,也可以被用作非虛基類;
如果虛基類中沒(méi)有默認(rèn)構(gòu)造函數(shù)(或參數(shù)全部為默認(rèn)參數(shù)的),則在派生類中必須顯式聲明構(gòu)造函數(shù),并在初始化列表中列出對(duì)虛基類構(gòu)造函數(shù)的調(diào)用;
在一個(gè)成員初始化列表中同時(shí)出現(xiàn)對(duì)虛基類和非虛基類構(gòu)造函數(shù)的調(diào)用時(shí),虛基類的構(gòu)造函數(shù)先于非虛基類的構(gòu)造函數(shù)執(zhí)行。
三、虛函數(shù)
虛函數(shù)概念:被virtual關(guān)鍵字修飾的成員函數(shù),即為虛函數(shù),其作用就是實(shí)現(xiàn)多態(tài)性。
虛函數(shù)使用說(shuō)明:
虛函數(shù)只能是類中的成員函數(shù),且不能是靜態(tài)的;
virtual關(guān)鍵字只能在類體中使用,即便虛函數(shù)的實(shí)現(xiàn)在類體外定義,也不能帶上virtual關(guān)鍵字;
當(dāng)在派生類中定義了一個(gè)與基類虛函數(shù)同名的成員函數(shù)時(shí),只要該函數(shù)的參數(shù)個(gè)數(shù)、類型、順序以及返回類型與基類中的完全一致,則派生類的這個(gè)成員函數(shù)無(wú)論是否使用virtual關(guān)鍵字,它都將自動(dòng)成為虛函數(shù);
利用虛函數(shù),可以在基類和派生類中使用相同的函數(shù)名定義函數(shù)的不同實(shí)現(xiàn),達(dá)到「一個(gè)接口,多種方式」的目的。當(dāng)基類指針或引用對(duì)虛函數(shù)進(jìn)行訪問(wèn)時(shí),系統(tǒng)將根據(jù)運(yùn)行時(shí)指針(或引用)所指向(或引用)的實(shí)際對(duì)象來(lái)確定調(diào)用的虛函數(shù)版本;
使用虛函數(shù)并不一定產(chǎn)生多態(tài)性,也不一定使用動(dòng)態(tài)聯(lián)編。如:在調(diào)用中對(duì)虛函數(shù)使用類名限定,可以強(qiáng)制C++對(duì)該函數(shù)使用靜態(tài)聯(lián)編。
在派生類中,當(dāng)一個(gè)指向基類成員函數(shù)的指針指向一個(gè)虛函數(shù),并且通過(guò)指向?qū)ο蟮幕愔羔槪ɑ蛞茫┰L問(wèn)這個(gè)虛函數(shù)時(shí),仍將發(fā)生多態(tài)性。
#include <iostream> using namespace std; class Base { public: virtual void print() { cout << "Base-print" << endl; } }; class Derived :public Base { public: void print() { cout << "Derived-print" << endl; } }; void display(Base* p, void(Base::* pf)()) { (p->*pf)(); } int main() { Derived d; Base b; display(&d, &Base::print); //輸出Derived-print display(&b, &Base::print); //輸出Base-print return 0; }
使用虛函數(shù),系統(tǒng)要增加一定的空間開(kāi)銷存儲(chǔ)虛函數(shù)表,但是系統(tǒng)在進(jìn)行動(dòng)態(tài)聯(lián)編時(shí)的時(shí)間開(kāi)銷時(shí)很少的,因此,虛函數(shù)實(shí)現(xiàn)的多態(tài)性是高效的。
虛函數(shù)實(shí)現(xiàn)多態(tài)的條件(同時(shí)滿足)
類之間的繼承關(guān)系滿足賦值兼容規(guī)則;
改寫了同名的虛函數(shù),但函數(shù)形參、返回類型要保持一致;
根據(jù)賦值兼容規(guī)則使用指針(或引用);
- 使用基類指針(或引用)訪問(wèn)虛函數(shù);
- 把指針(或引用)作為函數(shù)參數(shù),這個(gè)函數(shù)不一定是類的成員函數(shù),可以是普通函數(shù),并且可以重載。
虛析構(gòu)函數(shù)
派生類的對(duì)象從內(nèi)存中撤銷時(shí)一般先調(diào)用派生類的析構(gòu)函數(shù),然后再調(diào)用基類的析構(gòu)函數(shù)。但如果用new運(yùn)算符建立了派生類對(duì)象,且定義了一個(gè)基類的指針指向這個(gè)對(duì)象,那么當(dāng)用delete運(yùn)算符撤銷對(duì)象時(shí),系統(tǒng)會(huì)只執(zhí)行基類的析構(gòu)函數(shù),而不執(zhí)行派生類的析構(gòu)函數(shù),因而也無(wú)法對(duì)派生類對(duì)象進(jìn)行真正的撤銷清理工作。
如果希望delete關(guān)鍵字作用于基類指針時(shí),也執(zhí)行派生類的析構(gòu)函數(shù),則需要將基類的析構(gòu)函數(shù)聲明為虛函數(shù)。
如果將基類的析構(gòu)函數(shù)聲明為虛函數(shù),則由該基類所派生的所有派生類的析構(gòu)函數(shù)也都自動(dòng)成為虛函數(shù),即使派生類的析構(gòu)函數(shù)與基類的析構(gòu)函數(shù)名字不相同。
C++支持虛析構(gòu)函數(shù),但是不支持虛構(gòu)造函數(shù),即構(gòu)造函數(shù)不能聲明為虛函數(shù)!
純虛函數(shù)
許多情況下,不能在基類中為虛函數(shù)給出一個(gè)有意義的定義,這時(shí)可以將它說(shuō)明為純虛函數(shù),將具體定義留給派生類去做。純虛函數(shù)的定義形式為:virtual 返回類型 函數(shù)名(形式參數(shù)列表) = 0;
包含有純虛函數(shù)的類稱為抽象類,一個(gè)抽象類只能作為基類來(lái)派生新類,因此又稱為抽象基類,抽象類不能定義對(duì)象(實(shí)體)。
四、多態(tài)性
多態(tài)的含義:指同一操作作用于不同的對(duì)象時(shí)產(chǎn)生不同的結(jié)果。
- 重載多態(tài)——函數(shù)重載、運(yùn)算符重載
- 強(qiáng)制多態(tài)——也稱類型轉(zhuǎn)換
- C++的基本數(shù)據(jù)類型之間轉(zhuǎn)換規(guī)則:char→short→int→unsigned→long→unsigned→float→double→long double
- 可以在表達(dá)式中使用3中強(qiáng)制類型轉(zhuǎn)換表達(dá)式:static_cast<T>(E)或T(E)或(T)E 其中E代表運(yùn)算表達(dá)式(獲得一個(gè)值),T代表一個(gè)類型標(biāo)識(shí)符。強(qiáng)制多態(tài)使得類型檢查復(fù)雜化,尤其在允許重載的情況下,會(huì)導(dǎo)致無(wú)法消解的二義性。
- 類型參數(shù)化多態(tài)——模板(函數(shù)模板、類模板)
- 包含多態(tài)——使用虛函數(shù)
至少含有一個(gè)虛函數(shù)的類稱為多態(tài)類,虛函數(shù)使得程序能夠以動(dòng)態(tài)聯(lián)編的方式達(dá)到執(zhí)行結(jié)果的多態(tài)化。這種多態(tài)使用的背景是:派生類繼承基類的所有操作,或者說(shuō),基類的操作能被用于操作派生類的對(duì)象,當(dāng)基類的操作不能適應(yīng)派生類時(shí),派生類就需要重載基類的操作;其表現(xiàn)為C++允許用基類的指針接收派生類的地址或使用基類的引用綁定派生類的對(duì)象。
靜態(tài)聯(lián)編和動(dòng)態(tài)聯(lián)編
聯(lián)編:將模塊或者函數(shù)合并在一起生成可執(zhí)行代碼的處理過(guò)程,同時(shí)對(duì)每個(gè)模塊或者函數(shù)分配內(nèi)存地址,并且對(duì)外部訪問(wèn)也分配正確的內(nèi)存地址。
靜態(tài)聯(lián)編:在編譯階段就將函數(shù)實(shí)現(xiàn)和函數(shù)調(diào)用綁定。靜態(tài)聯(lián)編在編譯階段就必須了解所有函數(shù)的或模塊執(zhí)行所需要的信息,它對(duì)函數(shù)的選擇是基于指向?qū)ο蟮闹羔槪ɑ蛘咭茫┑念愋?。C語(yǔ)言中,所有的聯(lián)編都是靜態(tài)聯(lián)編,C++中一般情況下的聯(lián)編也是靜態(tài)聯(lián)編。
動(dòng)態(tài)聯(lián)編:在程序運(yùn)行的時(shí)候才進(jìn)行函數(shù)實(shí)現(xiàn)和函數(shù)調(diào)用的綁定稱之為動(dòng)態(tài)聯(lián)編(dynamic binding)
#include <iostream> #define PI 3.14159265 using namespace std; class Point { public: Point(double x = 0, double y = 0) :x(x), y(y) {} double area_static() { return 0; } //不是虛函數(shù),只會(huì)在編譯期綁定,形成靜態(tài)聯(lián)編 virtual double area_dynamic() { return 0; } //用虛函數(shù)聲明,則編譯時(shí)只做賦值兼容的合法性檢查,而不做綁定 private: double x, y; }; class Circle :public Point { public: Circle(double r = 1.0) :r(r) {} //由于基類中的構(gòu)造函數(shù)非必須顯式傳參,所以系統(tǒng)會(huì)自動(dòng)調(diào)用基類帶默認(rèn)參數(shù)的構(gòu)造函數(shù) Circle(double x, double y, double r=1.0) :Point(x, y), r(r) {} //重載一個(gè)可傳坐標(biāo)點(diǎn)、半徑值參數(shù)的構(gòu)造函數(shù) double area_static() { return PI * r * r; } //靜態(tài)聯(lián)編 double area_dynamic() { return PI * r * r; } //動(dòng)態(tài)聯(lián)編(仍為虛函數(shù)),為使可讀性更好,可在不缺省virutal關(guān)鍵字 private: double r; }; int main() { Point o(2.5, 2.5); Circle c(2.5, 2.5, 1); Point* po = &o, * pc = &c, & y_c = c; //下面五個(gè)全部為靜態(tài)聯(lián)編,無(wú)論指針指向的是基類還是派生類,由于指針類型為基類類型,且調(diào)用的不是虛函數(shù),則統(tǒng)一綁定為基類中的函數(shù) cout << "Point area =" << o.area_static() << endl; //值為0 cout << "Circle area=" << c.area_static() << endl; //值為3.14159 cout << "the o area from po:" << po->area_static() << endl; //值為0 cout << "the c area from pc:" << pc->area_static() << endl; //值為0 cout << "the c area from cite y_c:" << y_c.area_static() << endl; //值為0 //下面三個(gè)為動(dòng)態(tài)聯(lián)編,有指針(或引用)、虛函數(shù),則所調(diào)用的虛函數(shù)會(huì)在運(yùn)行時(shí)通過(guò)vptr指針找到虛函數(shù)表 ,根據(jù)指針指向的實(shí)際對(duì)象(而非指針類型)來(lái)判定調(diào)用誰(shuí)的函數(shù) cout << "the o area from po:" << po->area_dynamic() << endl; //值為0 cout << "the c area from pc:" << pc->area_dynamic() << endl; //值為3.14159 cout << "the c area from cite y_c:" << y_c.area_dynamic() << endl; //值為3.14159 //強(qiáng)制使用靜態(tài)聯(lián)編 cout << "the c area calculated by Point::area_():" << pc->Point::area_dynamic() << endl; //值為0 return 0; }
動(dòng)態(tài)聯(lián)編與虛函數(shù)
- 當(dāng)調(diào)用虛函數(shù)時(shí),先通過(guò)vptr指針(編譯虛函數(shù)時(shí),編譯器會(huì)為類自動(dòng)生成一個(gè)指向虛函數(shù)表的vptr指針)找到虛函數(shù)表,然后再找出虛函數(shù)的真正地址,再調(diào)用它
- 派生類能繼承基類的虛函數(shù)表,而且只要是和基類同名(參數(shù)也相同)的成員函數(shù),無(wú)論是否使用virtual聲明,它們都自動(dòng)成為虛函數(shù)。如果派生類沒(méi)有改寫繼承基類的虛函數(shù),則函數(shù)指針調(diào)用基類的虛函數(shù);如果派生類改寫了基類的虛函數(shù),編譯器將重新為派生類的虛函數(shù)建立地址,函數(shù)指針會(huì)調(diào)用改寫后的虛函數(shù)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C語(yǔ)言編程銀行ATM存取款系統(tǒng)實(shí)現(xiàn)源碼
這篇文章主要為大家介紹了C語(yǔ)言編程銀行ATM存取款系統(tǒng)實(shí)現(xiàn)的源碼示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11C語(yǔ)言詳解如何實(shí)現(xiàn)帶頭雙向循環(huán)鏈表
帶頭雙向循環(huán)鏈表:結(jié)構(gòu)最復(fù)雜,一般用在單獨(dú)存儲(chǔ)數(shù)據(jù)。實(shí)際中使用的鏈表數(shù)據(jù)結(jié)構(gòu),都是帶頭雙向循環(huán)鏈表。另外這個(gè)結(jié)構(gòu)雖然結(jié)構(gòu)復(fù)雜,但是使用代碼實(shí)現(xiàn)以后會(huì)發(fā)現(xiàn)結(jié)構(gòu)會(huì)帶來(lái)很多優(yōu)勢(shì),實(shí)現(xiàn)反而簡(jiǎn)單2022-04-04C++編程中用put輸出單個(gè)字符和cin輸入流的用法
這篇文章主要介紹了C++編程中用put輸出單個(gè)字符和cin輸入流的用法,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09C++指針數(shù)組、數(shù)組指針、數(shù)組名及二維數(shù)組技巧匯總
這篇文章主要介紹了C++指針數(shù)組、數(shù)組指針、數(shù)組名及二維數(shù)組技巧匯總,對(duì)于深入理解C++數(shù)組與指針來(lái)說(shuō)非常重要,需要的朋友可以參考下2014-08-08Qt6實(shí)現(xiàn)調(diào)用攝像頭并顯示畫面
這篇文章主要為大家詳細(xì)介紹了Qt6如何實(shí)現(xiàn)調(diào)用攝像頭并顯示畫面的效果,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2023-02-02QT+OpenGL實(shí)現(xiàn)簡(jiǎn)單圖形的繪制
這篇文章主要為大家詳細(xì)介紹了如何利用QT和OpenGL實(shí)現(xiàn)簡(jiǎn)單圖形的繪制,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2022-12-12C++ 類的賦值運(yùn)算符''''=''''重載的方法實(shí)現(xiàn)
這篇文章主要介紹了C++ 類的賦值運(yùn)算符'='重載的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02Clion2020.2.x最新激活碼破解版附安裝教程(Mac Linux Windows)
Clion2020增加了很多新特性,修復(fù)了大量bug,大大提高了開(kāi)發(fā)效率。這篇文章主要介紹了Clion2020.2.x最新激活碼破解版附安裝教程(Mac Linux Windows),需要的朋友可以參考下2020-11-11