欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報(bào)邀請(qǐng)碼778899分享:【C++】多態(tài)

柚子快報(bào)邀請(qǐng)碼778899分享:【C++】多態(tài)

http://yzkb.51969.com/

多態(tài)

一.多態(tài)的概念二.多態(tài)的定義及實(shí)現(xiàn)1.多態(tài)的構(gòu)成條件2.實(shí)現(xiàn)多態(tài)兩個(gè)必要條件3.虛函數(shù)4.虛函數(shù)的重寫(覆蓋)5.多態(tài)筆試題6.協(xié)變(了解)7.析構(gòu)函數(shù)的重寫8.override和final關(guān)鍵字9.重點(diǎn):重載/重寫/隱藏的對(duì)比

三.純虛函數(shù)和抽象類四.多態(tài)的原理1.虛函數(shù)表指針2.多態(tài)的原理1.多態(tài)是如何實(shí)現(xiàn)的2.靜態(tài)(編譯時(shí))綁定與動(dòng)態(tài)(運(yùn)行時(shí))綁定3.虛函數(shù)表

一.多態(tài)的概念

通俗來說,多態(tài)就是多種形態(tài)。多態(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折)。再比如,同樣是動(dòng)物叫的?個(gè)行為,傳貓對(duì)象過去,就是"(>ω<)喵",傳狗對(duì)象過去,就是"汪汪"。

二.多態(tài)的定義及實(shí)現(xiàn)

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.實(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á)到。

3.虛函數(shù)

類成員函數(shù)前面加virtual修飾,那么這個(gè)成員函數(shù)被稱為虛函數(shù)。注意非成員函數(shù)不能加virtual修飾。

class Person

{

public:

virtual void BuyTicket() { cout << "買票-全價(jià)" << endl;}

};

4.虛函數(shù)的重寫(覆蓋)

虛函數(shù)的重寫(覆蓋):派生類中有一個(gè)跟基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值類型、函數(shù)名字、參數(shù)列表(參數(shù)的類型、位置、個(gè)數(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:

//void BuyTicket() 子類中要重寫的成員函數(shù)可以不加virtual,但不建議

//函數(shù)名、函數(shù)參數(shù)、函數(shù)返回值相同構(gòu)成虛函數(shù)的重寫

virtual void BuyTicket()

{

cout << "買票-打折" << endl;

}

};

void Func(Person* ptr)

{

//基類的指針接收,滿足多態(tài)

//雖然都是Person指針在調(diào)用BuyTicket

//但是跟ptr的類型沒關(guān)系,而是有ptr指向的對(duì)象決定的

ptr->BuyTicket();

}

void Func(Person& ptr)

{

//基類的引用接收,滿足多態(tài)

//雖然都是Person指針在調(diào)用BuyTicket

//但是跟ptr的類型沒關(guān)系,而是有ptr指向的對(duì)象決定的

ptr.BuyTicket();

}

void Func(Person ptr)

{

//不是基類的指針/引用,不滿足多態(tài)(看ptr的類型)

//由于用Person類接收,無論傳入Student/Person的對(duì)象調(diào)用的都是Person中的BuyTicket函數(shù)

ptr.BuyTicket();

}

int main()

{

Person p;

Student s;

Func(&p); //買票-全價(jià)

Func(&s); //買票-打折

return 0;

}

class Animal

{

public:

virtual void talk() const

{}

};

class Dog : public Animal

{

public:

virtual void talk() const

{

std::cout << "汪汪" << std::endl;

}

};

class Cat : public Animal

{

public:

virtual void talk() const

{

std::cout << "(>^ω^<)喵" << std::endl;

}

};

void letsHear(const Animal& animal)

{

animal.talk();

}

int main()

{

Cat cat;

Dog dog;

letsHear(cat); //(>^ω^<)喵

letsHear(dog); //汪汪

return 0;

}

5.多態(tài)筆試題

class A

{

public:

virtual void func(int val = 1)

{

cout << "A->" << val << endl;

}

virtual void test()

{

func();

}

};

class B : public A

{

public:

void func(int val = 0)

{

cout << "B->" << val << endl;

}

};

int main()

{

B* p = new B;

p->test();

p->func();

return 0;

}

6.協(xié)變(了解)

虛函數(shù)重寫的一些其他問題:協(xié)變(也構(gòu)成多態(tài))。

派生類重寫基類虛函數(shù)時(shí),與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對(duì)象的指針或者引用,派生類虛函數(shù)返回派生類對(duì)象的指針或者引用時(shí),稱為協(xié)變。協(xié)變的實(shí)際意義并不大,所以們了解一下即可。

class A {};

class B : public A {};

class Person

{

public:

virtual A* BuyTicket()

//或者virtual Person* BuyTicket()

{

cout << "買票-全價(jià)" << endl;

return nullptr;

}

};

class Student : public Person

{

public:

virtual B* BuyTicket()

//或者virtual Student* BuyTicket()

{

cout << "買票-打折" << endl;

return nullptr;

}

};

void Func(Person* ptr)

{

ptr->BuyTicket();

}

int main()

{

Person ps;

Student st;

Func(&ps);

Func(&st);

return 0;

}

7.析構(gòu)函數(shù)的重寫

基類的析構(gòu)函數(shù)為虛函數(shù),此時(shí)派生類析構(gòu)函數(shù)只要定義,無論是否加virtual關(guān)鍵字,都與基類的析構(gòu)函數(shù)構(gòu)成重寫,雖然基類與派生類析構(gòu)函數(shù)名字不同看起來不符合重寫的規(guī)則,實(shí)際上編譯器對(duì)析構(gòu)函數(shù)的名稱做了特殊處理,編譯后析構(gòu)函數(shù)的名稱統(tǒng)一處理成destructor,所以基類的析構(gòu)函數(shù)加了vialtual修飾,派生類的析構(gòu)函數(shù)就構(gòu)成重寫。

下面的代碼我們可以看到,如果~ A(),不加virtual,那么delete p2時(shí)只調(diào)用的A的析構(gòu)函數(shù),沒有調(diào)用B的析構(gòu)函數(shù),就會(huì)導(dǎo)致內(nèi)存泄漏問題,因?yàn)閪 B()中在釋放資源。

注意:這個(gè)問題面試中經(jīng)??疾欤欢ㄒY(jié)合類似下面的樣例才能講清楚,為什么基類中的析構(gòu)函數(shù)建議設(shè)計(jì)為虛函數(shù)。

class A

{

public:

~A()

{

cout << "~A()" << endl;

delete _p1;

}

protected:

int* _p1 = new int[10] {0};

int _num = 100;

};

class B : public A

{

public:

//析構(gòu)函數(shù)本質(zhì)都是destructor(),此時(shí)子類與父類構(gòu)成多態(tài)

virtual ~B()

{

cout << "~B()" << endl;

delete _p2;

//子類的析構(gòu)函數(shù)結(jié)束后,自動(dòng)調(diào)用父類的析構(gòu)函數(shù),釋放繼承的_p1

}

protected:

int* _p2 = new int[10] {0};

//繼承了父類的_num = 100, _p1指向不同的堆區(qū)空間

};

int main()

{

A* p1 = new A;

A* p2 = new B;

//p1->destructor() + operator delete(free)

delete p1;

delete p2;

//若父類的析構(gòu)函數(shù)不是虛函數(shù),則不構(gòu)成多態(tài)

//A* p2 = new B;

//delete p2; 只調(diào)用A的析構(gòu)函數(shù),釋放子類中繼承的父類的資源,而未清理子類中的資源,導(dǎo)致內(nèi)存泄漏

return 0;

}

8.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,可以幫助用戶檢測(cè)是否重寫。

class Car

{

public:

virtual void Dirve()

{}

};

class Benz :public Car

{

public:

virtual void Drive() override //與父類的函數(shù)名不同,沒有構(gòu)成重寫——>編譯報(bào)錯(cuò)

{

cout << "Benz-舒適" << endl;

}

};

如果我們不想讓派生類重寫這個(gè)虛函數(shù),那么可以用final去修飾。

class Car

{

public:

virtual void Drive() final //若虛函數(shù)不想被重寫,加上final

{}

};

class Benz :public Car

{

public:

virtual void Drive() //無法重寫父類中的虛函數(shù)Drive()——>編譯報(bào)錯(cuò)

{

cout << "Benz-舒適" << endl;

}

};

在繼承中若想實(shí)現(xiàn)一個(gè)類不能被繼承,可以在定義類的后面加上final,例如:class A final{};

class A final{};

class B :public A {}; //A類加上了final,導(dǎo)致B類無法繼承A類,編譯報(bào)錯(cuò)

9.重點(diǎn):重載/重寫/隱藏的對(duì)比

注意:這個(gè)概念對(duì)比面試經(jīng)???。

三.純虛函數(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; //虛函數(shù)后面加=0 ——> 純虛函數(shù)

//包含純虛函數(shù)的類叫做抽象類

};

class Benz :public Car

{

public:

//若子類未重寫抽象類中的純虛函數(shù),那么會(huì)繼承父類的純虛函數(shù),子類也變成了抽象類,無法實(shí)例化對(duì)象

virtual void Drive()

{

cout << "Benz-舒適" << endl;

}

};

class BMW :public Car

{

public:

virtual void Drive()

{

cout << "BMW-操控" << endl;

}

};

int main()

{

//Car car; 編譯報(bào)錯(cuò)——>抽象類無法實(shí)例化對(duì)象

Car* pBenz = new Benz;

pBenz->Drive(); //Benz-舒適

Car* pBMW = new BMW;

pBMW->Drive(); //BMW-操控

return 0;

}

四.多態(tài)的原理

1.虛函數(shù)表指針

下面編譯為32位程序的運(yùn)行結(jié)果是什么?

class Base

{

public:

virtual void Func()

{

cout << "Func1()" << endl;

}

protected:

int _b = 1;

char _ch = 'x';

};

int main()

{

Base b;

cout << sizeof(b) << endl; //12

return 0;

}

上面題目運(yùn)行結(jié)果是12字節(jié),除了_b和_ch成員,還多一個(gè)__vfptr放在對(duì)象的前面(注意有些平臺(tái)可能會(huì)放到對(duì)象的最后面,這個(gè)跟平臺(tái)有關(guān))。對(duì)象中的這個(gè)指針我們叫做虛函數(shù)表指針(虛表指針)。一個(gè)含有虛函數(shù)的類中都至少都有一個(gè)虛函數(shù)表指針,因?yàn)橐粋€(gè)類所有虛函數(shù)的地址要被放到這個(gè)類對(duì)象的虛函數(shù)表中,虛函數(shù)表也簡(jiǎn)稱虛表。

class Base

{

public:

virtual void Func1()

{

cout << "Func1()" << endl;

}

virtual void Func2()

{

cout << "Func2()" << endl;

}

void Func3()

{

cout << "Func3()" << endl;

}

protected:

int _b = 1;

char _ch = 'x';

};

2.多態(tài)的原理

1.多態(tài)是如何實(shí)現(xiàn)的

從底層的角度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; }

protected:

string _name;

};

class Student : public Person

{

public:

virtual void BuyTicket() { cout << "買票-打折" << endl; }

protected:

int _id;

};

class Soldier : public Person

{

public:

virtual void BuyTicket() { cout << "買票-優(yōu)先" << endl; }

protected:

string _codename;

};

void Func(Person* ptr)

{

//這里可以看到雖然都是Person指針Ptr在調(diào)用BuyTicket

//但是構(gòu)成多態(tài),跟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;

}

2.靜態(tài)(編譯時(shí))綁定與動(dòng)態(tài)(運(yùn)行時(shí))綁定

對(duì)不滿足多態(tài)條件(指針或者引用+調(diào)用虛函數(shù))的函數(shù)調(diào)用是在編譯時(shí)綁定,也就是編譯時(shí)確定調(diào)用函數(shù)的地址,叫做靜態(tài)綁定。 滿足多態(tài)條件的函數(shù)調(diào)用是在運(yùn)行時(shí)綁定,也就是在運(yùn)行時(shí)到指向?qū)ο蟮奶摵瘮?shù)表中找到調(diào)用函數(shù)的地址,也就做動(dòng)態(tài)綁定。 從運(yùn)行的效率來看,靜態(tài)綁定效率要高一些。

//ptr是指針+BuyTicket是虛函數(shù)滿足多態(tài)條件。

//這里就是動(dòng)態(tài)綁定,編譯在運(yùn)?時(shí)到ptr指向?qū)ο蟮奶摵瘮?shù)表中確定調(diào)用函數(shù)地址

ptr->BuyTicket();

00EF2001 mov eax,dword ptr [ptr]

00EF2004 mov edx,dword ptr [eax]

00EF2006 mov esi,esp

00EF2008 mov ecx,dword ptr [ptr]

00EF200B mov eax,dword ptr [edx]

00EF200D call eax

//BuyTicket不是虛函數(shù),不滿足多態(tài)條件。(與ptr的指向無關(guān),只有ptr的類型有關(guān))

//這里就是靜態(tài)綁定,在編譯的時(shí)候,編譯器直接確定調(diào)用函數(shù)地址

ptr->BuyTicket();

00EA2C91 mov ecx,dword ptr [ptr]

00EA2C94 call Student::Student (0EA153Ch)

3.虛函數(shù)表

基類對(duì)象的虛函數(shù)表中存放基類所有虛函數(shù)的地址,同類型的對(duì)象共用同一份虛函數(shù)表,不同類型的對(duì)象虛函數(shù)表各自的獨(dú)立。

class Base

{

public:

virtual void func1() { cout << "Base::func1" << endl; }

virtual void func2() { cout << "Base::func2" << endl; }

void func5() { cout << "Base::func5" << endl; }

protected:

int a = 1;

};

int main()

{

Base b1;

Base b2;

return 0;

}

派生類由兩部分構(gòu)成,繼承下來的基類和自己的成員,一般情況下,繼承下來的基類中有虛函數(shù)表指針,自己就不會(huì)再生成虛函數(shù)表指針。但是要注意的這里繼承下來的基類部分虛函數(shù)表指針和基類對(duì)象的虛函數(shù)表指針不是同一個(gè),就像基類對(duì)象的成員和派生類對(duì)象中的基類對(duì)象成員也是獨(dú)立的。 派生類中重寫的基類的虛函數(shù),派生類的虛函數(shù)表中對(duì)應(yīng)的虛函數(shù)就會(huì)被覆蓋成派生類重寫的虛函數(shù)地址。 派生類的虛函數(shù)表中包含,基類的虛函數(shù)地址,派生類重寫的虛函數(shù)地址,派生類自己的虛函數(shù)地址三個(gè)部分(先是將父類的虛函數(shù)表拷貝過來,若子類中重寫了父類的虛函數(shù),則將重寫后的虛函數(shù)的地址覆蓋子類中虛函數(shù)地址,最后就是自己的虛函數(shù)的地址)

class Base

{

public:

virtual void func1() { cout << "Base::func1" << endl; }

virtual void func2() { cout << "Base::func2" << endl; }

void func5() { cout << "Base::func5" << endl; }

protected:

int a = 1;

};

class Derive : public Base

{

public:

//重寫基類的func1

virtual void func1() { cout << "Derive::func1" << endl; }

virtual void func3() { cout << "Derive::func1" << endl; }

void func4() { cout << "Derive::func4" << endl; }

protected:

int b = 2;

};

int main()

{

Base b;

Derive d;

return 0;

}

這里Derive中沒有看到func3函數(shù),這個(gè)vs監(jiān)視窗口看不到,可以通過內(nèi)存窗口查看:

虛函數(shù)表本質(zhì)是一個(gè)存虛函數(shù)指針的指針數(shù)組,一般情況這個(gè)數(shù)組最后面放了一個(gè)0x00000000標(biāo)記(這個(gè)C++并沒有進(jìn)行規(guī)定,各個(gè)編譯器自行定義的,vs系列編譯器會(huì)再后面放個(gè)0x00000000標(biāo)記,g++系列編譯不會(huì)放) 虛函數(shù)存在哪的?虛函數(shù)和普通函數(shù)一樣的,編譯好后是一段指令,都是存在代碼段的(和普通函數(shù)一樣都在代碼段),只是虛函數(shù)的地址又存到了虛表中。 虛函數(shù)表存在哪的?這個(gè)問題嚴(yán)格說并沒有標(biāo)準(zhǔn)答案C++標(biāo)準(zhǔn)并沒有規(guī)定,我們寫下面的代碼可以對(duì)比驗(yàn)證?下。vs下是存在代碼段(常量區(qū))

class Base

{

public:

virtual void func1() { cout << "Base::func1" << endl; }

virtual void func2() { cout << "Base::func2" << endl; }

void func5() { cout << "Base::func5" << endl; }

protected:

int a = 1;

};

class Derive : public Base

{

public:

//重寫基類的func1

virtual void func1() { cout << "Derive::func1" << endl; }

virtual void func3() { cout << "Derive::func1" << endl; }

void func4() { cout << "Derive::func4" << endl; }

protected:

int b = 2;

};

int main()

{

int i = 0;

static int j = 1;

int* p1 = new int;

const char* p2 = "xxxxxxxx";

printf("棧:%p\n", &i);

printf("靜態(tài)區(qū)(數(shù)據(jù)段):%p\n", &j);

printf("堆:%p\n", p1);

printf("常量區(qū)(代碼段):%p\n", p2);

Base b;

Derive d;

Base* p3 = &b;

Derive* p4 = &d;

printf("Person虛表地址:%p\n", *(int*)p3);

printf("Student虛表地址:%p\n", *(int*)p4);

printf("虛函數(shù)地址:%p\n", &Base::func1);

printf("普通函數(shù)地址:%p\n", &Base::func5);

return 0;

}

運(yùn)行結(jié)果:

棧:00F5F988 靜態(tài)區(qū)(數(shù)據(jù)段):0055D000 堆:011DA558 常量區(qū)(代碼段):0055AB94

Person虛表地址:0055AB34 Student虛表地址:0055AB74 虛函數(shù)地址:00551474 普通函數(shù)地址:005514B0

柚子快報(bào)邀請(qǐng)碼778899分享:【C++】多態(tài)

http://yzkb.51969.com/

參考閱讀

評(píng)論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19542559.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問

文章目錄