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

首頁綜合 正文
目錄

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

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

http://yzkb.51969.com/

多態(tài)的概念

了解多態(tài)之前,我們?cè)诂F(xiàn)實(shí)生活一定經(jīng)歷過這倆個(gè)例子:

????????在購(gòu)買火車票的時(shí)候,會(huì)根據(jù)你的類型來確定你的票價(jià):

成人:全價(jià)學(xué)生:半價(jià)軍人:免費(fèi)

? ? ? ? 而某外賣系統(tǒng)的紅包系統(tǒng),也會(huì)根據(jù)一定類型來確定紅包大?。?/p>

新人:大額紅包不經(jīng)常使用該外賣系統(tǒng)的人:中等額度經(jīng)常使用外面系統(tǒng)的人:小額紅包

從這些例子中,可以大致對(duì)多態(tài)有一定的理解:

多態(tài)的概念:通俗來講,就是多種形態(tài),具體點(diǎn)就是去完成某個(gè)行為,當(dāng)不同的對(duì)象去完成時(shí)會(huì)產(chǎn)生出不同的狀態(tài)。

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

虛函數(shù)

class Person

{

public:

virtual void fare()

{

cout << "全價(jià)" << endl;

}

};

虛函數(shù):被virtual修飾的類成員函數(shù)被稱為虛函數(shù)。

虛函數(shù)的重寫

class Person

{

public:

virtual void fare()

{

cout << "全價(jià)" << endl;

}

};

class Student : public Person

{

public:

virtual void fare()

{

cout << "半價(jià)" << endl;

}

};

虛函數(shù)的重寫(覆蓋):派生類中有一個(gè)跟基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值相同,函數(shù)名字,參數(shù)列表完全相同),稱為子類的虛函數(shù)重寫了基類的虛函數(shù)。

class Person

{

public:

virtual void fare()

{

cout << "全價(jià)" << endl;

}

};

class Student : public Person

{

public:

void fare()

{

cout << "半價(jià)" << endl;

}

};

【注意】觀察上述代碼,在重寫基類虛函數(shù)的時(shí)候,派生類的虛函數(shù)在不加virtual關(guān)鍵字時(shí),雖然也可也構(gòu)成重寫(因?yàn)槔^承后基類的虛函數(shù)被繼承下來,在派生類依舊保持虛函數(shù)屬性),但是這種寫法不是很規(guī)范,不建議這樣使用。

通過觀察代碼,前倆段代碼是通過對(duì)象類型來調(diào)用函數(shù),而后面?zhèn)z條代碼是通過看指向的對(duì)象,如果指向的對(duì)象是基類,則調(diào)用基類;如果指向的對(duì)象是派生類,則調(diào)用派生類。

虛函數(shù)重寫的倆個(gè)例外:

1.協(xié)變(基類與與派生類虛函數(shù)返回值類型不同)

派生類重寫基類虛函數(shù)時(shí),與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對(duì)象的指針或者引用,派生類虛函數(shù)返回派生類對(duì)象的指針或者引用,稱為協(xié)變。

class A{};

class B : public A{};

class Person

{

public:

virtual A* fare()

{

cout << "全價(jià)" << endl;

}

};

class Student : public Person

{

public:

virtual B* fare()

{

cout << "半價(jià)" << endl;

}

};

2.析構(gòu)函數(shù)的重寫(基類與派生類析構(gòu)函數(shù)的名字不同)

如果基類的析構(gòu)函數(shù)為虛函數(shù),此時(shí)派生類析構(gòu)函數(shù)只要定義,無論是否加virtual關(guān)鍵字,都與基類的析構(gòu)函數(shù)構(gòu)成重寫,雖然基類與派生類析構(gòu)函數(shù)名字不同。雖然函數(shù)名不相同,看起來違背了重寫的規(guī)則,但是這是不對(duì)的,這里可以理解為編譯器對(duì)析構(gòu)函數(shù)的名稱做了特殊處理,編譯后析構(gòu)函數(shù)的名稱被統(tǒng)一處理稱desructor。

觀察下面這段代碼:

class Person

{

public:

~Person()

{

cout << "~Person" << endl;

}

};

class Student : public Person

{

public:

~Student()

{

cout << "~Student" << endl;

}

};

int main()

{

Person* p = new Person;

delete p;

p = new Student;

delete p;

return 0;

}

這段代碼使用了為經(jīng)過處理的析構(gòu)函數(shù),使用Person這個(gè)指針進(jìn)行析構(gòu)第一次p的時(shí)候,會(huì)調(diào)用Person這個(gè)析構(gòu)函數(shù),而使用p析構(gòu)第二次p的時(shí)候,還是只會(huì)調(diào)用Person這個(gè)析構(gòu)函數(shù)。

?

class Person

{

public:

virtual ~Person()

{

cout << "~Person" << endl;

}

};

class Student : public Person

{

public:

virtual ~Student()

{

cout << "~Student" << endl;

}

};

int main()

{

Person* p = new Person;

delete p;

p = new Student;

delete p;

return 0;

}

????????將析構(gòu)函數(shù)前面加上virtual,進(jìn)行虛函數(shù)的重寫,可以將類析構(gòu)函數(shù)都被處理為destructor這個(gè)統(tǒng)一的名字。

????????這里也是由于期望p->destructor()是一個(gè)多態(tài)調(diào)用,而不是普通調(diào)用。通過多態(tài)調(diào)用,可以將第二次的p在student析構(gòu)一次,在Student析構(gòu)一次,在Person析構(gòu)一次。

? ? ? ? 這也是為什么將析構(gòu)函數(shù)統(tǒng)一處理稱destructor這個(gè)名字的原因,為了保證函數(shù)名的統(tǒng)一。

class Person

{

public:

virtual ~Person()

{

cout << "~Person" << endl;

}

};

class Student : public Person

{

public:

virtual ~Student()

{

cout << "~Student" << endl;

}

};

int main()

{

Person* p1 = new Person;

delete p1;

Person* p2 = new Student;

delete p2;

return 0;

}

【注意】只有派生類student的析構(gòu)函數(shù)重寫了person的析構(gòu)函數(shù),當(dāng)p1析構(gòu)時(shí)會(huì)調(diào)用person的析構(gòu)函數(shù),而p2析構(gòu)的時(shí)候會(huì)調(diào)用person與student的析構(gòu)函數(shù)。

【注意】派生類的析構(gòu)可以不寫。

多態(tài)的構(gòu)成條件

多態(tài)時(shí)在不同繼承關(guān)系的類對(duì)象,去調(diào)用同一個(gè)函數(shù),產(chǎn)生了不同的行為。

在繼承中構(gòu)成多態(tài)的倆個(gè)條件:

1.必須通過基類的指針或者引用調(diào)用函數(shù)。

2.被調(diào)用的函數(shù)必須是虛函數(shù),且派生類必須對(duì)基類的虛函數(shù)進(jìn)行重寫。

重寫虛函數(shù)的條件以及一些例外:

條件1:三同“函數(shù)名相同、參數(shù)相同、返回值相同”

條件2:必須是虛函數(shù)(virtual)

例外1:派生類的重寫虛函數(shù)可以不寫virtual,但是建議都加上。

例外2:協(xié)變的返回值可以不同,但是要求返回值必須是父子類關(guān)系的引用與指針。

例外3:析構(gòu)函數(shù)可以名字不同。

【注意】:對(duì)于多態(tài)而言,不同的對(duì)象傳遞過去,會(huì)調(diào)用不同的函數(shù),多態(tài)調(diào)用看的是指向的對(duì)象,而普通類型調(diào)用看的是當(dāng)前類型。

C++11特性:override與fianl

從前面的學(xué)習(xí)中了解到,C++對(duì)函數(shù)重寫的要求比較嚴(yán)格,但是可以由于各種原因?qū)е潞瘮?shù)名字母次序?qū)懛炊鵁o法構(gòu)成重載,而這種錯(cuò)誤在編譯期間是不會(huì)報(bào)錯(cuò)的,只有在程序運(yùn)行時(shí)沒有達(dá)到預(yù)期的結(jié)果需要進(jìn)行調(diào)試會(huì)得不償失。

C++11中提供了override和final倆個(gè)關(guān)鍵字,以便幫助用戶檢測(cè)是否重寫。

fianl:檢測(cè)虛函數(shù),表示該虛函數(shù)不能再被重寫。

class A

{

public:

virtual void Fun1() final

{

cout << "A" << endl;

}

};

class B : public class A

{

public:

virtual void Fun1()

{

cout << "B" << endl;

}

};

override:檢查派生類虛函數(shù)是否重寫了基類某個(gè)虛函數(shù),如果沒有重寫編譯錯(cuò)誤。

class A

{

public:

virtual void Fun1()

{

cout << "A" << endl;

}

};

class B : public A

{

public:

virtual void Fun2() override

{

cout << "B" << endl;

}

};

如何設(shè)計(jì)一個(gè)不想被繼承的類?

倆種方法:

1.C++98的方法:將基類構(gòu)造函數(shù)私有化(private)

class A

{

public:

private:

A()

{}

};

class B : public A

{

public:

B()

{}

};

此時(shí)會(huì)之間報(bào)錯(cuò):無法訪問 private 成員(在“A”類中聲明),私有構(gòu)造函數(shù)在子類不可見,而派生類的構(gòu)造函數(shù)需要調(diào)用基類的構(gòu)造函數(shù)。

? ? ? ? 伴隨著報(bào)錯(cuò)還會(huì)出現(xiàn)一系列的問題:該類如何進(jìn)行初始化操作?可以使用一個(gè)函數(shù)來調(diào)用構(gòu)造函數(shù)。

class A

{

public:

A Creatobj()

{

return A();

}

private:

A()

{}

};

?此時(shí)需要考慮的是,如果沒有初始化對(duì)象,那么該如何調(diào)用在函數(shù)來初始化對(duì)象呢?這是一個(gè)先有蛋還是先有雞的關(guān)系。

可以使用靜態(tài)函數(shù)來初始化創(chuàng)建對(duì)象:

class A

{

public:

static A Creatobj()

{

return A();

}

private:

A()

{}

};

int main()

{

A::Creatobj();

return 0;

}

可以通過這樣的方式來進(jìn)行對(duì)對(duì)象的初始化。

2.C++11的方法:基類后面加一個(gè)fianl可以變成最終類。

在C++11中規(guī)定,如果一個(gè)類后面加關(guān)鍵字fianl會(huì)導(dǎo)致這個(gè)類變成最終類,而使其無法被繼承。

class A final

{

public:

};

class B : public A

{

public:

};

重載、覆蓋(重寫)、隱藏(重定義)的對(duì)比

多態(tài)的原理

虛函數(shù)表

觀察下面這段代碼:

class A

{

public:

virtual void Fun1()

{

cout << "A::Fun1()" << endl;

}

protected:

int _a;

};

int main()

{

A a;

cout << sizeof(a) << endl;

return 0;

}

在x86環(huán)境下,sizeof(a)的值為8.

????????這個(gè)答案可能有點(diǎn)不合常理,這是因?yàn)槌薩a成員是int類型占了4個(gè)字節(jié),還多了一個(gè)_vfptr的指針占4個(gè)字節(jié),該指針被放在對(duì)象的前面(這是在vs環(huán)境下,有些平臺(tái)可能會(huì)放在對(duì)象的后面,這個(gè)跟平臺(tái)有關(guān)系),對(duì)象中的這個(gè)指針被稱為虛函數(shù)表指針(v代表virtual,f代表function)。

? ? ? ? 一個(gè)含有虛函數(shù)的類中都至少都會(huì)有一個(gè)虛函數(shù)表指針,這是因?yàn)樘摵瘮?shù)的地址要被放在虛函數(shù)表中,虛函數(shù)表也簡(jiǎn)稱虛表。

class A

{

public:

virtual void Fun1()

{

cout << "A::Fun1()" << endl;

}

virtual void Fun2()

{

cout << "A::Fun2()" << endl;

}

virtual void Fun3()

{

cout << "A::Fun3()" << endl;

}

protected:

int _a;

};

如果我們存放多個(gè)虛函數(shù),在這個(gè)虛函數(shù)指針?biāo)赶虻谋碇芯蜁?huì)出現(xiàn)三個(gè)地址。

class A

{

public:

virtual void Fun1()

{

cout << "A::Fun1()" << endl;

}

virtual void Fun2()

{

cout << "A::Fun2()" << endl;

}

void Fun3()

{

cout << "A::Fun3()" << endl;

}

protected:

int _a;

};

class B : public A

{

public:

virtual void Fun1()

{

cout << "B::Fun2()" << endl;

}

void Fun4()

{

cout << "B::Fun4()" << endl;

}

protected:

int _b;

};

int main()

{

A a;

B b;

return 0;

}

下面將通過測(cè)試來講解這段代碼:

1.派生類對(duì)象b中也有一個(gè)虛表函數(shù),b對(duì)象由倆部分組成,一部風(fēng)是基類繼承下來的成員,虛表指針也就是存在這部分的,另一部分是自己的成員。

2.基類a對(duì)象和派生類b對(duì)象虛表是不一樣的,這里可以發(fā)現(xiàn)Fun1函數(shù)完成了重寫,所以b的虛表中存的是重寫的B::Fun1,所以虛函數(shù)的重寫也叫做覆蓋,覆蓋就是指虛表中虛函數(shù)的覆蓋。重寫是語法的叫法,覆蓋是原理層的叫法。

3.另外Fun2繼承下來后是虛函數(shù),所以被放進(jìn)了虛表,F(xiàn)un3也被繼承了下來,但是由于沒有被關(guān)鍵字virtual修飾,不是虛函數(shù),所以不會(huì)放進(jìn)虛表。

4.虛函數(shù)表本質(zhì)是一存虛函數(shù)指針的指針數(shù)組,一般情況下這個(gè)數(shù)組最后面都會(huì)放一個(gè)nullptr。

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;

}

測(cè)試這段代碼:其輸出結(jié)果是B->1.

? ? ? ? 這是因?yàn)樘摵瘮?shù)的重寫的是虛函數(shù)的實(shí)現(xiàn),聲明式不會(huì)被改變的,所以在派生類可以不寫virtual,只需要存在三同即好,編譯器會(huì)在檢查之前,將基類的聲明域派生類的函數(shù)實(shí)現(xiàn)結(jié)合。

5.總結(jié)派生類的虛表生成:

先將基類中的虛表內(nèi)容拷貝一份到派生類虛表中如果派生類重寫了基類中的某個(gè)虛函數(shù),用派生類自己的虛函數(shù)覆蓋虛表中基類的虛函數(shù)派生類自己新增的虛函數(shù)按其在派生類中的聲明次序增加到派生類虛表的后面。

6.虛函數(shù)的重寫是接口繼承。

7.虛表是存在常量區(qū)的。

class A

{

public:

virtual void Fun1()

{

cout << "A::Fun1()" << endl;

}

protected:

int _a;

};

class B : public A

{

public:

virtual void Fun1()

{

cout << "B::Fun2()" << endl;

}

protected:

int _b;

};

int main()

{

int a = 0;

printf("棧區(qū):%p\n", &a);

static int b = 0;

printf("靜態(tài)區(qū):%p\n", &b);

int* p = new int;

printf("堆區(qū):%p\n", p);

const char* str = "hello";

printf("常量區(qū):%p\n", str);

A aa;

printf("虛表aa:%p\n", *((int*)&aa));

B bb;

printf("虛表bb:%p\n", *((int*)&bb));

return 0;

}

通過這段代碼的測(cè)試:

基本可以鎖定虛表存儲(chǔ)在常量區(qū)。

8.虛表里存的是虛函數(shù)指針,不是虛函數(shù),虛函數(shù)和普通函數(shù)一樣,都是存在代碼段里的。對(duì)象中存的也不是虛表,而是虛表指針。

多態(tài)的原理

之前的學(xué)習(xí)中我們知道,多態(tài)的條件有倆個(gè):

1.必須通過基類的指針或者引用調(diào)用函數(shù)。

2.被調(diào)用的函數(shù)必須是虛函數(shù),且派生類必須對(duì)基類的虛函數(shù)進(jìn)行重寫。

class A

{

public:

virtual void Fun1()

{

cout << "A::Fun1()" << endl;

}

protected:

int _a;

};

class B : public A

{

public:

virtual void Fun1()

{

cout << "B::Fun2()" << endl;

}

protected:

int _b;

};

int main()

{

A a;

A& pa = a;

pa.Fun1();

B b;

A& pb = b;

pb.Fun1();

return 0;

}

通過測(cè)試,我們可以知道不同對(duì)象去進(jìn)行調(diào)用函數(shù)Fun1的時(shí)候會(huì)展現(xiàn)不同的形態(tài)。

這里有倆個(gè)問題:

1.為什么需要使用基類的指針才可以調(diào)用函數(shù),而不能是派生類指針或者引用。

? ? ? ? 是因?yàn)榕缮愔羔樆蛞弥荒苷{(diào)用派生類的指針和引用,基類的指針或者引用不僅可以調(diào)用基類的指針或引用,也可以調(diào)用派生類的指針或引用。

2.為什么不能是父類的對(duì)象?

? ? ? ? 對(duì)象的切片與指針或者引用的切片不同,當(dāng)派生類賦值給基類對(duì)象進(jìn)行切片,對(duì)象會(huì)進(jìn)行拷貝,但是不會(huì)拷貝虛表,假設(shè)如果拷貝了虛表,然后調(diào)用了派生類的成員函數(shù),但是當(dāng)使用基類指針去指向該基類對(duì)象的時(shí)候,就也只能調(diào)用派生類的成員函數(shù),而不是基類的成員函數(shù)。

class A

{

public:

virtual void Fun1()

{

cout << "A::Fun1()" << endl;

}

protected:

int _a;

};

class B : public A

{

public:

virtual void Fun1()

{

cout << "B::Fun1()" << endl;

}

protected:

int _b;

};

int main()

{

B b;

A& pb = b;

pb.Fun1();

b.Fun1();

return 0;

}

我們通過反匯編的角度,分析使用基類引用與對(duì)象調(diào)用函數(shù)的不同:

這是基類引用時(shí)的匯編代碼。

pb中存的時(shí)基類的指針,將pb移動(dòng)到eax中。

[eax]就是取eax值指向的內(nèi)容,這里相當(dāng)于把b對(duì)象頭4個(gè)字節(jié)(虛表指針)移動(dòng)到了edx

[edx]就是取edx值指向的內(nèi)容,這里相當(dāng)于把虛表中的頭4個(gè)字節(jié)存的虛函數(shù)指針移動(dòng)到eax

call eax中存虛函數(shù)的指針。這里可以看出滿足多態(tài)的調(diào)用,不是在編譯時(shí)確定的,是運(yùn)行起來以后對(duì)象里面取到的。

這里不滿足多態(tài)的條件,所以這里是普通函數(shù)的調(diào)用轉(zhuǎn)換成地址時(shí),是在編譯時(shí)已經(jīng)從符號(hào)表確認(rèn)了函數(shù)的地址,直接call地址。

? ? ? ? 通過上面的了解,看出滿足多態(tài)以后的函數(shù)調(diào)用,不是在編譯時(shí)確定的,是運(yùn)行起來以后到對(duì)象里面取棧的,不滿足多態(tài)的函數(shù)調(diào)用時(shí)編譯時(shí)確認(rèn)好的。

動(dòng)態(tài)綁定與靜態(tài)綁定

在上述例子中總結(jié):

1.靜態(tài)綁定:靜態(tài)綁定又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態(tài)多態(tài),比如函數(shù)重載。

2.動(dòng)態(tài)綁定:動(dòng)態(tài)綁定又稱為后期綁定(晚綁定),是在程序運(yùn)行期間,根據(jù)具體拿到的類型確定程序的具體行為,調(diào)用具體的函數(shù),也稱為動(dòng)態(tài)多態(tài)。

單繼承和多繼承關(guān)系的虛函數(shù)表

單繼承中的虛函數(shù)表

class A

{

public:

virtual void Fun1()

{

cout << "A::Fun1()" << endl;

}

virtual void Fun2()

{

cout << "A::Fun2()" << endl;

}

protected:

int _a;

};

class B : public A

{

public:

virtual void Fun1()

{

cout << "B::Fun1()" << endl;

}

virtual void Fun3()

{

cout << "B::Fun3()" << endl;

}

virtual void Fun4()

{

cout << "B::Fun4()" << endl;

}

protected:

int _b;

};

使用這段代碼進(jìn)行測(cè)試的時(shí)候,在編譯的監(jiān)視窗口看不見fun3和fun4。

這里是編譯器的監(jiān)視窗口故意隱藏了這倆個(gè)函數(shù),也可以認(rèn)為這是一個(gè)bug。

// 打印函數(shù)指針數(shù)組

// void PrintVFT(FUNC_PTR table[])

void PrintVFT(FUNC_PTR* table)

{

for (size_t i = 0; table[i] != nullptr; i++)

{

printf("[%d]:%p->", i, table[i]);

FUNC_PTR f = table[i];

f();

}

printf("\n");

}

int main()

{

A a;

B b;

int vft1 = *((int*)&a);

PrintVFT((FUNC_PTR*)vft1);

int vft2 = *((int*)&b);

PrintVFT((FUNC_PTR*)vft2);

return 0;

}

這里提供一種思路:

取出a,b對(duì)象的頭4個(gè)字節(jié),就是虛表的指針,前面講解了虛函數(shù)的本質(zhì)是一個(gè)存虛函數(shù)指針的指針數(shù)組,這個(gè)數(shù)組最后面放了一個(gè)nullptr。

1.先取a的地址,強(qiáng)轉(zhuǎn)成一個(gè)int*的指針。

2.再解引用取值,就取到了a對(duì)象的頭4個(gè)字節(jié)的指針,這個(gè)值就是指向虛表的指針。

3.再?gòu)?qiáng)轉(zhuǎn)成FUNC_PTR*,因?yàn)樘摫砭褪且粋€(gè)存FUNC_PTR類型(虛函數(shù)指針類型)的數(shù)組。

4.虛表指針傳遞給PrintVFT進(jìn)行打印虛表。

5.需要說明的是這個(gè)打印虛表的代碼經(jīng)常會(huì)崩潰,因?yàn)榫幾g器有時(shí)對(duì)虛表的處理不干凈,虛表最后沒有放nullptr的話,會(huì)導(dǎo)致越界,這是編譯器的問題。只需要清理解決方案即可。

多繼承中的虛函數(shù)表

class Base1 {

public:

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

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

private:

int b1;

};

class Base2 {

public:

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

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

private:

int b2;

};

class Derive : public Base1, public Base2 {

public:

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

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

private:

int d1;

};

typedef void(*VFPTR) ();

void PrintVTable(VFPTR vTable[])

{

cout << " 虛表地址>" << vTable << endl;

for (int i = 0; vTable[i] != nullptr; ++i)

{

printf(" 第%d個(gè)虛函數(shù)地址 :0X%x,->", i, vTable[i]);

VFPTR f = vTable[i];

f();

}

cout << endl;

}

int main()

{

Derive d;

VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);

PrintVTable(vTableb1);

VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));

PrintVTable(vTableb2);

return 0;

}

?多繼承派生類的未重寫的虛函數(shù)放在第一個(gè)繼承基類部分的虛函數(shù)表中。

菱形繼承與虛擬菱形繼承

菱形繼承

class A

{

public:

virtual void Func1()

{

cout << "A::Func1" << endl;

}

public:

int _a;

};

class B : public A

{

public:

virtual void Fun1()

{

cout << "B::Func1" << endl;

}

public:

int _b;

};

class C : public A

{

public:

virtual void Fun1()

{

cout << "B::Func1" << endl;

}

public:

int _c;

};

class D : public B, public C

{

public:

virtual void Fun1()

{

cout << "B::Func1" << endl;

}

public:

int _d;

};

int main()

{

D d;

d.B::_a = 1;

d.C::_a = 2;

d._b = 3;

d._c = 4;

d._d = 5;

return 0;

}

這里需要注意在d的所在地址第一塊區(qū)域是類B的所在區(qū)域,里面包含了虛表的地址,以及數(shù)據(jù)B::_a和_b。第二塊區(qū)域是類C的所在區(qū)域,里面包含了虛表的地址,以及數(shù)據(jù)C::_a和_c。

菱形虛擬繼承

class A

{

public:

virtual void Func1()

{

cout << "A::Func1" << endl;

}

public:

int _a;

};

class B : virtual public A

{

public:

virtual void Fun1()

{

cout << "B::Func1" << endl;

}

public:

int _b;

};

class C : virtual public A

{

public:

virtual void Fun1()

{

cout << "B::Func1" << endl;

}

public:

int _c;

};

class D : public B, public C

{

public:

virtual void Fun1()

{

cout << "B::Func1" << endl;

}

public:

int _d;

};

int main()

{

D d;

d._a = 1;

d._b = 3;

d._c = 4;

d._d = 5;

return 0;

}

????????實(shí)際中不建議設(shè)計(jì)出菱形繼承及菱形虛擬繼承,一方面太復(fù)雜容易出現(xiàn)問題,另一方面這樣的模型,訪問基類成員有一定的性能損耗。

? ? ? ? 所以菱形繼承、菱形虛擬繼承一般實(shí)際中很少使用。?

抽象類

概念

? ? ? ? 在虛函數(shù)的后面寫上=0,則這個(gè)函數(shù)變成純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫接口類),抽象類不能實(shí)例化出對(duì)象。派生類繼承之后也不能實(shí)例化出對(duì)象,只能重寫純虛函數(shù),派生類才能實(shí)例化出對(duì)象。純虛函數(shù)規(guī)范了派生類必須重寫,另外純虛函數(shù)更體現(xiàn)處理接口繼承。

class Car

{

public:

inline virtual void Drive() = 0;

};

class Benz :public Car

{

public:

inline virtual void Drive()

{

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

}

};

class BMW :public Car

{

public:

inline virtual void Drive()

{

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

}

};

class BYD :public Car

{

public:

inline virtual void Drive()

{

cout << "BYD-build year dream" << endl;

}

};

void Func(Car* p)

{

p->Drive();

}

int main()

{

Func(new Benz);

Func(new BMW);

Func(new BYD);

return 0;

}

這個(gè)類在現(xiàn)實(shí)世界沒有對(duì)應(yīng)的實(shí)體,其用途可以是作為指針或者引用使用。

接口繼承和實(shí)現(xiàn)繼承

? ? ? ? 普通函數(shù)的繼承是一種實(shí)現(xiàn)繼承,派生類繼承了基類函數(shù),可以使用函數(shù),繼承的是函數(shù)的實(shí)現(xiàn)。

? ? ? ? 虛函數(shù)的繼承是一種接口繼承,派生類繼承的是基類函數(shù)的接口,目的是為了重寫,達(dá)成多態(tài),繼承的是接口。所以如果不實(shí)現(xiàn)多態(tài),就不需要把函數(shù)定義為虛函數(shù)。

面試相關(guān)題

1.

什么是多態(tài)?

答:多態(tài)分為靜態(tài)多態(tài):函數(shù)重載;和動(dòng)態(tài)多態(tài):繼承中的虛函數(shù)重寫+基類指針引用。

2.

什么是重載、重寫

(

覆蓋

)

、重定義

(

隱藏

)

?

答:參考本節(jié)課件內(nèi)容

3.

多態(tài)的實(shí)現(xiàn)原理?

答:靜態(tài)多態(tài):函數(shù)名修飾規(guī)則;動(dòng)態(tài)多態(tài):虛函數(shù)表

4. inline

函數(shù)可以是虛函數(shù)嗎?

答:可以,不過編譯器就忽略

inline

屬性,這個(gè)函數(shù)就不再是inline,因?yàn)樘摵瘮?shù)要放到虛表中去。

5.

靜態(tài)成員可以是虛函數(shù)嗎?

答:不能,因?yàn)殪o態(tài)成員函數(shù)沒有

this

指針,使用類型

::

成員函數(shù)的調(diào)用方式無法訪問虛函數(shù)表,所以靜態(tài)成員函數(shù)無法放進(jìn)虛函數(shù)表。

6.

構(gòu)造函數(shù)可以是虛函數(shù)嗎?

答:不能,因?yàn)閷?duì)象中的虛函數(shù)表指針是在構(gòu)造函數(shù)初始化列表 階段才初始化的。

7.

析構(gòu)函數(shù)可以是虛函數(shù)嗎?什么場(chǎng)景下析構(gòu)函數(shù)是虛函數(shù)?

答:可以,并且最好把基類的析 構(gòu)函數(shù)定義成虛函數(shù)。參考本節(jié)課件內(nèi)容

8.

對(duì)象訪問普通函數(shù)快還是虛函數(shù)更快?

答:首先如果是普通對(duì)象,是一樣快的。如果是指針 對(duì)象或者是引用對(duì)象,則調(diào)用的普通函數(shù)快,因?yàn)闃?gòu)成多態(tài),運(yùn)行時(shí)調(diào)用虛函數(shù)需要到虛函 數(shù)表中去查找。

9.

虛函數(shù)表是在什么階段生成的,存在哪的?

答:虛函數(shù)表是在編譯階段就生成的,一般情況 下存在代碼段(

常量區(qū)

)

的。

10. C++

菱形繼承的問題?虛繼承的原理?

答:參考繼承課件。注意這里不要把虛函數(shù)表和虛基表搞混了。

11.

什么是抽象類?抽象類的作用?

答:參考(3.

抽象類)。抽象類強(qiáng)制重寫了虛函數(shù),另外抽象類體現(xiàn)出了接口繼承關(guān)系。

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

http://yzkb.51969.com/

相關(guān)閱讀

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

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

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

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

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

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

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

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

文章目錄