柚子快報(bào)邀請(qǐng)碼778899分享:java 【C++】多態(tài)詳解
柚子快報(bào)邀請(qǐng)碼778899分享:java 【C++】多態(tài)詳解
?博客主頁:https://blog.csdn.net/2301_779549673 ?歡迎點(diǎn)贊 ? 收藏 ?留言 ? 如有錯(cuò)誤敬請(qǐng)指正! ?本文由 JohnKi 原創(chuàng),首發(fā)于 CSDN? ?未來很長,值得我們?nèi)Ρ几案篮玫纳?
文章目錄
????1. 多態(tài)的概念????2.多態(tài)的定義及實(shí)現(xiàn)??2.1 多態(tài)的構(gòu)成條件2.1.1 實(shí)現(xiàn)多態(tài)還有兩個(gè)必須重要條件!2.1.2 虛函數(shù)2.1.3虛函數(shù)的重寫/覆蓋2.1.4 多態(tài)場景的一個(gè)選擇題2.1.6 override 和 final關(guān)鍵字
????3.純虛函數(shù)和抽象類????4.多態(tài)的原理????4.2 多態(tài)的原理?總結(jié)
????1. 多態(tài)的概念
多態(tài)(polymorphism)的概念:通俗來說,就是多種形態(tài)。多態(tài)分為編譯時(shí)多態(tài)(靜態(tài)多態(tài))和運(yùn)行時(shí)多態(tài)(動(dòng)態(tài)多態(tài)),這里我們重點(diǎn)講運(yùn)行時(shí)多態(tài),編譯時(shí)多態(tài)(靜態(tài)多態(tài))和運(yùn)行時(shí)多態(tài)(動(dòng)態(tài)多態(tài))。編譯時(shí)多態(tài)(靜態(tài)多態(tài))主要就是我們前面講的函數(shù)重載和函數(shù)模板,他們傳不同類型的參數(shù)就可以調(diào)用不同的函數(shù),通過參數(shù)不同達(dá)到多種形態(tài),之所以叫編譯時(shí)多態(tài),是因?yàn)樗麄儗?shí)參傳給形參的參數(shù)匹配是在編譯時(shí)完成的,我們把編譯時(shí)一般歸為靜態(tài),運(yùn)行時(shí)歸為動(dòng)態(tài)。
運(yùn)行時(shí)多態(tài),具體點(diǎn)就是去完成某個(gè)行為(函數(shù)),可以傳不同的對(duì)象就會(huì)完成不同的行為,就達(dá)到多種形態(tài)。比如買票這個(gè)行為,當(dāng)普通人買票時(shí),是全價(jià)買票;學(xué)生買票時(shí),是優(yōu)惠買票(5折或75折);軍人買票時(shí)是優(yōu)先買票。再比如,同樣是動(dòng)物叫的一個(gè)行為(函數(shù)),傳貓對(duì)象過去,就是喵,傳狗對(duì)象過去,就是狗
????2.多態(tài)的定義及實(shí)現(xiàn)
??2.1 多態(tài)的構(gòu)成條件
多態(tài)是一個(gè)繼承關(guān)系的下的類對(duì)象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。比如Student繼承了Person。Person對(duì)象買票全價(jià),Student對(duì)象優(yōu)惠買票。
2.1.1 實(shí)現(xiàn)多態(tài)還有兩個(gè)必須重要條件!
必須指針或者引用調(diào)用虛函數(shù)被調(diào)用的函數(shù)必須是虛函數(shù)。
**說明:**要實(shí)現(xiàn)多態(tài)效果,第一必須是基類的指針或引用,因?yàn)橹挥谢惖闹羔樆蛞貌拍芗戎赶蚺缮悓?duì)象:第二派牛類必須對(duì)基類的慮函數(shù)重寫/覆蓋,重寫或者覆蓋了,派生類才能有不同的函數(shù),多態(tài)的不同形態(tài)效果才能達(dá)到。
2.1.2 虛函數(shù)
類成員函數(shù)前面加virtual修飾,那么這個(gè)成員函數(shù)被稱為虛函數(shù)。注意非成員函數(shù)不能加virtual修飾。
class Person
{
public:
virtual void BuyTicket() { cout << "買票-全價(jià)" << endl;}
}
2.1.3虛函數(shù)的重寫/覆蓋
虛函數(shù)的重寫/覆蓋:派生類中有一個(gè)跟基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值類型、函數(shù)名字、參數(shù)列表完全相同),稱派生類的虛函數(shù)重寫了基類的虛函數(shù)。
**注意:**在重寫基類虛函數(shù)時(shí),派生類的虛函數(shù)在不加virtual關(guān)鍵字時(shí),雖然也可以構(gòu)成重寫(因?yàn)槔^承后基類的虛函數(shù)被繼承下來了在派生類依舊保持虛函數(shù)屬性),但是該種寫法不是很規(guī)范,不建議這樣使用,不過在考試選擇題中,經(jīng)常會(huì)故意買這個(gè)坑,讓你判斷是否構(gòu)成多態(tài)。
class Person {
public:
virtual void BuyTicket() {
cout << "買票-全價(jià)" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket() {
cout << "買票-打折" << endl;
}
};
void Func(Person* ptr) {
// 這?可以看到雖然都是Person指針Ptr在調(diào)?BuyTicket
// 但是跟ptr沒關(guān)系,?是由ptr指向的對(duì)象決定的。
ptr->BuyTicket();
}
int main() {
Person ps;
Student st;
Func(&ps);
Func(&st);
return 0;
}
2.1.4 多態(tài)場景的一個(gè)選擇題
class A {
public:
virtual void func(int val = 1) {
std::cout << "A->" << val << std::endl;
}
virtual void test() {
func();
}
};
class B : public A {
public:
void func(int val = 0) {
std::cout << "B->" << val << std::endl;
}
};
int main(int argc, char* argv[]) {
B* p = new B;
p->test();
return 0;
}
首先分析代碼中的類繼承關(guān)系和虛函數(shù)重寫情況:
在這段代碼中,類B繼承自類A。類A中有虛函數(shù)func和test,其中test函數(shù)內(nèi)部調(diào)用了func函數(shù)。類B重寫了func函數(shù)。 然后看函數(shù)調(diào)用情況:
在main函數(shù)中,創(chuàng)建了一個(gè)B類的對(duì)象指針p,并調(diào)用p->test()。由于test函數(shù)在A類中定義,并且func函數(shù)是虛函數(shù),雖然在B類中重寫了func函數(shù),但是虛函數(shù)的默認(rèn)參數(shù)是在編譯時(shí)確定的。當(dāng)B類中的test函數(shù)調(diào)用func時(shí),它會(huì)使用A類中定義的默認(rèn)參數(shù)1(因?yàn)槟J(rèn)參數(shù)不是多態(tài)的一部分,編譯時(shí)綁定)。 最后得出結(jié)果:
程序的輸出結(jié)果是B->1。
2.1.6 override 和 final關(guān)鍵字
從上面可以看出,C++對(duì)函數(shù)重寫的要求比較嚴(yán)格,但是有些情況下由干疏忽,比如函數(shù)名寫錯(cuò)參數(shù)寫錯(cuò)等導(dǎo)致無法構(gòu)成重載,而這種錯(cuò)誤在編譯期間是不會(huì)報(bào)出的,只有在程序運(yùn)行時(shí)沒有得到預(yù)期結(jié)果才來debug會(huì)得不償失,因此C++11提供了override,可以幫助用戶檢測是否重寫。如果我們不想讓派生類重寫這個(gè)虛函數(shù),那么可以用final去修飾。
// error C3668: “Benz::Drive”: 包含重寫說明符“override”的?法沒有重寫任何基類?法
class Car {
public:
virtual void Dirve()
{}
};
class Benz : public Car {
public:
virtual void Drive() override {
cout << "Benz-舒適" << endl;
}
};
int main() {
return 0;
}
// error C3248: “Car::Drive”: 聲明為“final”的函數(shù)?法被“Benz::Drive”重寫
class Car {
public:
virtual void Drive() final {}
};
class Benz : public Car {
public:
virtual void Drive() {
cout << "Benz-舒適" << endl;
}
};
int main() {
return 0;
}
????3.純虛函數(shù)和抽象類
在虛函數(shù)的后面寫上 =0,則這個(gè)函數(shù)為純虛函數(shù),純虛函數(shù)不需要定義實(shí)現(xiàn)(實(shí)現(xiàn)沒啥意義因?yàn)橐慌缮愔貙懀钦Z法上可以實(shí)現(xiàn)),只要聲明即可。包含純虛函數(shù)的類叫做抽象類,抽象類不能實(shí)例化出對(duì)象,如果派生類繼承后不重寫純虛函數(shù),那么派生類也是抽象類。純虛函數(shù)某種程度上強(qiáng)制了派生類重寫虛函數(shù),因?yàn)椴恢貙憣?shí)例化不出對(duì)象。
class Car {
public:
virtual void Drive() = 0;
};
class Benz : public Car {
public:
virtual void Drive() {
cout << "Benz-舒適" << endl;
}
}
class BMW : public Car {
public:
virtual void Drive() {
cout << "BMW-操控" << endl;
}
};
int main() {
// 編譯報(bào)錯(cuò):error C2259: “Car”: ?法實(shí)例化抽象類
Car car;
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
return 0;
}
????4.多態(tài)的原理
下面編譯為32位程序的運(yùn)行結(jié)果是什么() A.編譯報(bào)錯(cuò) B.運(yùn)行報(bào)錯(cuò) C.8 D. 12
class Base {
public:
virtual void Func1() {
cout << "Func1()" << endl;
}
protected:
int _b = 1;
char _ch = 'x';
};
int main() {
Base b;
cout << sizeof(b) << endl;
return 0;
}
首先分析Base類的結(jié)構(gòu):
Base類中有一個(gè)虛函數(shù)Func1。在C++中,當(dāng)一個(gè)類包含虛函數(shù)時(shí),類對(duì)象中會(huì)包含一個(gè)虛函數(shù)指針(通常是一個(gè)指針的大小,在32位系統(tǒng)中為4字節(jié),在64位系統(tǒng)中為8字節(jié))。類Base還有一個(gè)int類型的成員變量_b(通常為4字節(jié))和一個(gè)char類型的成員變量_ch(1字節(jié))。由于內(nèi)存對(duì)齊的要求,為了提高內(nèi)存訪問效率,編譯器會(huì)在成員變量的布局上進(jìn)行調(diào)整。對(duì)于一個(gè)包含虛函數(shù)指針和int、char類型成員變量的類,在大多數(shù)編譯器下,內(nèi)存布局會(huì)先放置虛函數(shù)指針,然后按照內(nèi)存對(duì)齊規(guī)則放置int和char類型的變量。 然后計(jì)算sizeof(b):
在32位系統(tǒng)中:
虛函數(shù)指針占4字節(jié),int類型的_b占4字節(jié)(按照4字節(jié)對(duì)齊),char類型的_ch占1字節(jié)(總共占4字節(jié),因?yàn)橐獫M足4字節(jié)對(duì)齊)。所以sizeof(b)為12字節(jié)。 在64位系統(tǒng)中:
虛函數(shù)指針占8字節(jié),int類型的_b占4字節(jié)(按照8字節(jié)對(duì)齊,這里會(huì)填充4字節(jié)),char類型的_ch占1字節(jié)(總共占8字節(jié),因?yàn)橐獫M足8字節(jié)對(duì)齊)。所以sizeof(b)為16字節(jié)。
所以運(yùn)行結(jié)果取決于運(yùn)行程序的系統(tǒng)是32位還是64位。如果是32位系統(tǒng),結(jié)果為12;如果是64位系統(tǒng),結(jié)果為16。
????4.2 多態(tài)的原理
從底層的角度Func函數(shù)中ptr->BuyTicket(),是如何作為ptr指向Person對(duì)象調(diào)用Person::BuyTicket,ptr指向Student對(duì)象調(diào)用Student::BuyTicket的呢?通過下圖我們可以看到,滿足多態(tài)條件后,底層不再是編譯時(shí)通過調(diào)用對(duì)象確定函數(shù)的地址,而是運(yùn)行時(shí)到指向的對(duì)象的虛表中確定對(duì)應(yīng)的虛函數(shù)的地址,這樣就實(shí)現(xiàn)了指針或引用指向基類就調(diào)用基類的虛函數(shù),指向派生類就調(diào)用派生類對(duì)應(yīng)的虛函數(shù)。第一張圖,ptr指向的Person對(duì)象,調(diào)用的是Person的虛函數(shù);第二張圖,ptr指向的Student對(duì)象,調(diào)用的是Student的虛函數(shù)。
class Person {
public:
virtual void BuyTicket() {
cout << "買票-全價(jià)" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket() {
cout << "買票-打折" << endl;
}
};
class Soldier: public Person {
public:
virtual void BuyTicket() {
cout << "買票-優(yōu)先" << endl;
}
};
void Func(Person* ptr) {
// 這?可以看到雖然都是Person指針Ptr在調(diào)?BuyTicket
// 但是跟ptr沒關(guān)系,?是由ptr指向的對(duì)象決定的。
ptr->BuyTicket();
}
int main() {
// 其次多態(tài)不僅僅發(fā)?在派?類對(duì)象之間,多個(gè)派?類繼承基類,重寫虛函數(shù)后
// 多態(tài)也會(huì)發(fā)?在多個(gè)派?類之間。
Person ps;
Student st;
Soldier sr;
Func(&ps);
Func(&st);
Func(&sr);
return 0;
}
?總結(jié)
本篇博文對(duì) 【C++】多態(tài)詳解 做了一個(gè)較為詳細(xì)的介紹,不知道對(duì)你有沒有幫助呢
覺得博主寫得還不錯(cuò)的三連支持下吧!會(huì)繼續(xù)努力的~
柚子快報(bào)邀請(qǐng)碼778899分享:java 【C++】多態(tài)詳解
推薦文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。