国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【C++進階之路】多態(tài)篇

這篇具有很好參考價值的文章主要介紹了【C++進階之路】多態(tài)篇。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言

?多態(tài),顧名思義,就是一件事物具備不同的形態(tài),是繼承之后,面向?qū)ο蟮牡谌筇匦?/code>,可以這樣說:有了繼承才有了類的多態(tài),而類的多態(tài)是為了更好的實現(xiàn)繼承。

?多態(tài)的列車即將起航,不知你準(zhǔn)備好了嗎?

一、概念

?繼承與多態(tài)相輔相成
舉個例子:
?我們都是人(具備人都有的信息——性別,年齡等),在社會上我們又會具備不同的身份——老師,學(xué)生,工人等,那這時放假回家,要去買火車票,那這時如果你是學(xué)生火車票五折。如果是老師火車票七五折。

? 老師和學(xué)生都是人,人又具有不同的身份,而不同身份面臨的同一件事情表現(xiàn)的具體形態(tài)不同。

因此:老師和學(xué)生繼承人的信息,從而使不同的事物(具有相同特征)對同一件事表現(xiàn)出不同的形態(tài),這樣就是多態(tài)。

補充:

網(wǎng)友比較形象的解釋:
英雄聯(lián)盟里的每個英雄都是一個對象,其父類(基類)有個方法:按Q、W、E、R會產(chǎn)生傷害、消耗藍量、并在屏幕上顯示攻擊效果,每個對象(英雄)繼承這個方法,并具有具體的屬性,每個對象的傷害量不同、消耗藍量不同,產(chǎn)生的效果不同,而這個方法具有多態(tài),在編寫代碼和編譯的時候并不確定,而在運行的時候,依據(jù)你選則了哪個英雄(對象),在調(diào)用父類的這個方法的時候,會依據(jù)具體對象執(zhí)行能產(chǎn)生不同狀態(tài)的方法,也就是說這個父類的方法具有了多種狀態(tài),想要其產(chǎn)生新的狀態(tài),只需基于父類再編寫新的對象


?那語法層面上是如何實現(xiàn)多態(tài)呢?

1.分類

?多態(tài),我們其實已經(jīng)接觸過一種,叫函數(shù)重載,根據(jù)傳參不同而調(diào)用同一函數(shù)的不同形態(tài)。這叫做靜態(tài)的多態(tài),也叫靜態(tài)綁定,而我們今天講的主要是在繼承之后延伸出的類的多態(tài),叫動態(tài)的多態(tài),也叫動態(tài)綁定

這里根據(jù)上面的例子,列出一段代碼:

#include<iostream>
using namespace std;
class Person
{
public:
	virtual void BuyTickets()
	{
		cout << "全價" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTickets()
	{
		cout << "學(xué)生->半價" << endl;
	}
};

class Teacher : public Person
{
public:
	virtual void BuyTickets()
	{
		cout << "老師->七五折" << endl;
	}
};
void BuyTicket(Person & per)
{
	per.BuyTickets();
}
int main()
{
	Student stu;
	Teacher tea;

	BuyTicket(stu);
	BuyTicket(tea);

	return 0;
}

運行結(jié)果如下:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

初學(xué)者看懂個大概即可,語法和原理下面會細(xì)講。


2.實現(xiàn)條件

主要實現(xiàn)方法有兩個。

①重寫虛函數(shù)

  • 所謂虛函數(shù),就是在函數(shù)的前面加上virtual。

那重點就在于重寫,也叫覆蓋。

如何才能構(gòu)成重寫呢?或者什么叫做重寫呢?

  1. 首先父類至少得有虛函數(shù)。
  2. 一般來說,子類虛函數(shù)得跟父類的虛函數(shù)的函數(shù)名,參數(shù)類型,返回類型相同,簡稱三同。

細(xì)節(jié):

  1. 子類的函數(shù)前可不加virtual。
  2. 返回類型可以不同,但是必須是父子類的指針或者引用,且父類只能是父類的指針或者引用,子類必須是子類的指針或者引用。 —— 協(xié)變
  3. 重寫的是實現(xiàn)。

?到這重寫的條件就講清了,至于什么叫重寫,其實很簡單就是:在達成重寫的條件下,子類的虛函數(shù)替換掉父類的虛函數(shù),從而達成用指向子類的父類指針,在調(diào)用此虛函數(shù)時,會調(diào)用子類的虛函數(shù),而不是父類的虛函數(shù)。

列一段代碼,看結(jié)果便可明了。

#include<iostream>
using namespace std;
class Person
{
public:
	//其它類的引用和指針也行,但必須是父是父的,子是子的!
	virtual Person* BuyTickets(int val1 = 1)
	{
		cout << "全價" << endl;
		cout << val1 << endl;

		return nullptr;
	}
};

class Student : public Person
{
public:
	virtual Student* BuyTickets(int val2 = 0)
	{
		cout << "學(xué)生->半價" << endl;
		cout << val2 << endl;

		return nullptr;
	}
};

class Teacher : public Person
{
public:
	virtual  Teacher* BuyTickets(int val3 = - 1)
	{
		cout << "老師->七五折" << endl;
		cout << val3 << endl;

		return nullptr;
	}
};
void BuyTicket(Person & per)
{
	per.BuyTickets();
}
int main()
{
	Student stu;
	Teacher tea;

	BuyTicket(stu);
	BuyTicket(tea);

	return 0;
}

運行結(jié)果如下:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 解釋細(xì)節(jié)3:殼還是套的父類,僅僅改變了作用域和實現(xiàn), 如果便于理解,你也可以認(rèn)為參數(shù)名也改了——但實際上底層用的是地址和寄存器,根本不關(guān)心參數(shù)名,這也是細(xì)節(jié)1的原因。
1.1總結(jié)三重

【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

1.2 final與override
  1. override——對虛函數(shù)的重寫進行檢查
class Student : public Person
{
public:
	Student* BuyTickets  (int val2 = 0) override
	{
		cout << "學(xué)生->半價" << endl;
		cout << val2 << endl;
		return nullptr;
	}
	//void fun1() override
	//{
	//}
	// 
	//此注釋代碼不符合重寫條件,故報錯。
};
  1. final
  • 禁止派生類重寫此虛函數(shù)
class Person
{
public:
	virtual void BuyTickets()final
	{
		cout << "全價" << endl;
	}
};

class Student : public Person
{
public:
	//因為此函數(shù)構(gòu)成重寫,派生類會重寫虛函數(shù),因此會報錯。
	void BuyTickets ()
	{
		cout << "學(xué)生->半價" << endl;
	}
};
  • 禁止此類被繼承(語法層面) —— C++11
class A final
{};

//因為B繼承A,所以會報錯。
class B : public A
{};

C++98采用構(gòu)造函數(shù)/析構(gòu)函數(shù)私有來進行實現(xiàn)不可被繼承(應(yīng)用層面)。

  1. 構(gòu)造私有
class A
{
public:
	static A* CreatObj()
	{
		return new A;
	}
private:
	A()
	{}
};
class B : public A
{
	//原理為父類的私有成員在派生類中不可見。
};
int main()
{
	A* a = A::CreatObj();
	//B b;報錯
	return 0;
}
  1. 析構(gòu)私有
class A
{
public:
	void Destory()
	{
		A::~A();
	}
private:
	~A()
	{}
};
class B : public A
{
public:
	//原理為父類的私有成員在派生類中不可見。
};
int main()
{
	A* ptra = new A;
	ptra->Destory();
	operator delete (ptra);
	//B b;
	//報錯
	return 0;
}

②父類的指針或者引用

為啥必須是父類的指針和引用呢?

從概念上理解,是人具有多種形態(tài),而不是老師具有多種形態(tài),因為人是比較抽象的,賦予了某種身份才具象化。
再換一個例子,是植物具有多種形態(tài),還是玫瑰花具有多種形態(tài)?其原因還是一樣的,植物沒有賦予固定的形態(tài),是比較抽象的,而給植物賦予玫瑰花的身份是具象的。

  • 所以父類這種比較抽象的狀態(tài)是符合多態(tài)的。 當(dāng)然從C++語法上來說父類也可以是具體的。 但都是為了改變父類的指向從而調(diào)用父類的虛函數(shù)。

補充:子類也可以有多種形態(tài),這是把子類當(dāng)做父類來看的,就比如動物里面有貓,而貓分為很多種,比如波斯貓,布偶貓等。

細(xì)節(jié): 指定作用域,可破壞多態(tài)的條件

void BuyTicket(Person & per)
{
	per.Person::BuyTickets();
}
  • 為啥不能是子類的指針或者引用?

  • 為啥不是是父類對象?(涉及原理之后再講)


2.1普通調(diào)用VS多態(tài)調(diào)用
#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};
class B
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
};

class C : public A, public B
{
public:
	virtual void fun1()
	{
		cout << "C::fun1()" << endl;
	}
};

int main()
{
	C c;
	//普通調(diào)用
	c.fun1();
	//多態(tài)調(diào)用
	B* b = &c;
	b->fun1();
	//看匯編代碼之后,想一下為什么,不構(gòu)成多態(tài)也去虛函數(shù)里面找,再進行調(diào)用。
	C* ptrc = &c;
	return 0;
}

匯編圖解:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

3.抽象類

3.1. 純虛函數(shù)

  • 在虛函數(shù)后面加上 “= 0” 即為純虛函數(shù),切記語法規(guī)定只能是0。
class A
{
public:
	virtual void fun1() = 0;
};

純虛函數(shù)所在類是不能實例化的。

補充:空函數(shù)——實現(xiàn)啥也沒有
如:void func() {}

class B : public A
{}

這里B繼承了A,純虛函數(shù)也被繼承了,因此B也無法進行實例化。

像這樣,有純虛函數(shù)的類,就是抽象類。

那如何使用呢?很簡單子類將純虛函數(shù)進行重寫,不就能使用了么。

  • 因此抽象類,會強制子類重寫虛函數(shù)(應(yīng)用)。
class B : public A
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
};

B進行重寫后,就不含純虛函數(shù),也就不是抽象類了。

如果你執(zhí)意要調(diào)用,也是可以的,不過會報錯

int main()
{
	B b;
	A* a = &b;
	a->fun1();
	return 0;
}

【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

3.2. 接口繼承和實現(xiàn)繼承

  • 普通的繼承,直接對成員函數(shù)進行復(fù)用,俗稱接口繼承。
  • 多態(tài)的繼承,對虛函數(shù)進行重寫,重寫的是實現(xiàn)。俗稱實現(xiàn)繼承。

二、原理及使用

1.虛函數(shù)表 —— 虛表

引入:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{}
private:
	char _a = 1;
};
int main()
{
	A a;
	cout << sizeof(A) << endl;
	return 0;
}

運行結(jié)果:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
為啥不是4(8字節(jié)對齊)呢?難道多了什么嗎?

查看監(jiān)視窗口:

調(diào)用構(gòu)造函數(shù)前~
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
調(diào)用構(gòu)造函數(shù)后~
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 首先在構(gòu)造函數(shù)調(diào)用前,比我們預(yù)想多了一個指針,是void** 類型的,并且沒有被初始化。這就足以證明,8字節(jié)是咋來的了。
  • 在調(diào)用構(gòu)造函數(shù)后,可以看到_vftptr的指針的具體信息,并被初始化了,大概是_vftptr指針指向的是一個數(shù)組,數(shù)組有兩個元素,元素所存的元素的類型為void (*) ()的虛函數(shù)指針,也就是一張存放函數(shù)指針的表。且最后一個位置存放的應(yīng)該是虛函數(shù)表的結(jié)束位置。 之后證明。

那這張存放虛函數(shù)的表,我們稱之為虛函數(shù)表,簡稱虛表。

那虛表是用來干啥呢?當(dāng)然是肯定是用來實現(xiàn)多態(tài)的了,再說細(xì)點就是為了實現(xiàn)重寫。

既然是這樣,那我們對以下代碼進行調(diào)試。

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{}
	virtual void fun2()
	{}
private:
	char _a = 1;
};
class B : public A
{
public:
	virtual void fun1()
	{}
};
int main()
{
	A a;
	B b;
	return 0;
}

調(diào)試結(jié)果:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 很顯然,不同類的虛表是不同的。根據(jù)虛表指針存的值即可看出 。
  • 當(dāng)子類生成虛表時,把子類的虛表拷貝下來,然后對構(gòu)成重寫的虛函數(shù)進行覆寫,這里是對原來的位置進行覆蓋實現(xiàn)。而子類的不構(gòu)成重寫的虛函數(shù)則繼續(xù)在虛表的后面進行排列。
  • 至于這里監(jiān)視窗口為啥看不到fun3,我的理解是監(jiān)視窗口是站在父類的角度進行查看的,當(dāng)然只能看到父類重寫的虛函數(shù)和父類沒有被重寫的虛函數(shù),如果能看到子類的虛函數(shù)不就怪了嗎?

至于如何驗證第三個位置是fun3,給出如下代碼。

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << " A :: fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << " A :: fun2()" << endl;
	}
private:
	int _a = 1;
};
class B : public A
{
public:
	virtual void fun1()
	{
		cout << " B :: fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << " B :: fun3()" << endl;
	}
	int _b = 1;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
}
int main()
{
	B b;
	int ptr = *(int*)(&b);
	Print((FUN_PTR*)ptr);
	return 0;
}

運行結(jié)果:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

這是虛表的內(nèi)存地址(小端):
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

總結(jié)一下:

  1. 不同類的虛表指針的值是不同的。相同類的虛表指針的值是相同的。(不再證明,有興趣自己看監(jiān)視窗口)。
  2. 監(jiān)視窗口看不到fun3, 是因為站的視角為父類,而fun3是子類的。
  3. vs2019虛表的結(jié)束位置為 0
  4. 子類虛表先放父類(虛函數(shù)),再重寫,再放子類(從上往下)。
  5. 虛表指針隨著構(gòu)造函數(shù)的調(diào)用而初始化,且虛表指針在對象模型的第一個位置。

下面我們繼續(xù)討論遺留下來的問題:

  • 為啥不是是父類對象?
class A
{
public:
	virtual void fun1()
	{}
};
class B : public A
{
public:
	virtual void fun1()
	{}
};
int main()
{
	A a;
	B b;
	a = b;
	//拷貝不拷貝虛表?
	return 0;
}

我們只看賦值之后的監(jiān)視窗口:

【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 可見,虛表是不會被拷貝過去的,因此,無法完成指向父類調(diào)子類的情況(多態(tài)),并且如果拷貝過去子類能調(diào)用父類的虛函數(shù),就亂套了!因此是不拷貝虛表的。
  • 為啥不是是父類對象?

那虛表存在哪呢?給出如下代碼進行驗證。

#include<iostream>
class A
{
public:
	virtual void fun1()
	{}
};
int main()
{
	A a;
	//虛表的地址
	void** ptr = (void**)(*(int*)&a);
	//棧區(qū)的地址
	int _a = 0;
	//靜態(tài)區(qū)的地址
	static int b = 0;
	//常量區(qū)地址
	const char* str = "abc";
	//堆的地址
	int* ptr1 = new int;
	printf("虛表地址->%p\n", ptr);
	printf("棧區(qū)地址->%p\n", &_a);
	printf("堆區(qū)地址->%p\n", ptr1);
	printf("靜態(tài)區(qū)地址->%p\n", &b);
	printf("常量區(qū)地址->%p\n", str);
	return 0;
}

運行結(jié)果:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 可見虛表地址與常量區(qū)地址僅僅差8個字節(jié)。
  • 因此:虛表至少在VS2019下是存在常量區(qū)的。

2.默認(rèn)成員函數(shù)

2.1構(gòu)造函數(shù)

  • 語法上,不允許在構(gòu)造函數(shù)前加virtual。

那為什么呢?

利用之前得到的結(jié)論,虛表指針是在構(gòu)造函數(shù)調(diào)用時才被初始化的! 如果構(gòu)造函數(shù)是虛函數(shù),那虛表指針都沒有初始化,如何調(diào)用虛函數(shù)呢?典型的先有虛函數(shù)指針 還是 先調(diào)用構(gòu)造函數(shù)的問題。因此語法上禁掉了。

除此之外,構(gòu)造(包括拷貝構(gòu)造)函數(shù)里面調(diào)用成員函數(shù),不會從虛表進行調(diào)用,而是直接進行調(diào)用!

2.2析構(gòu)函數(shù)

  • 語法上,允許在析構(gòu)函數(shù)前加virtual。

這是為啥呢?

舉一段錯誤代碼,一看便知:

#include<iostream>
using namespace std;
class A
{
public:
	~A()
	{
		cout << "A::~A()" << endl;
	}
	int _a = 0;
};

class B : public A
{
public:
	~B()
	{
		cout << "B::~B()" << endl;
	}
	int _b = 1;
};

int main()
{
	B* b = new B;
	A* a = b;
	delete a;

	return 0;
}

運行結(jié)果 :
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 很明顯的問題出來了,竟然沒有調(diào)用子類的析構(gòu)函數(shù)。這是由于向上轉(zhuǎn)換發(fā)生的切割現(xiàn)象。會導(dǎo)致內(nèi)存泄漏的問題。

如何解決?

  • 析構(gòu)函數(shù)前加virtual,那假設(shè)上面的代碼加上virtual,那delete 子類指針會形成多態(tài),指向父類調(diào)用父類的析構(gòu)函數(shù),指向子類調(diào)用子類的析構(gòu)函數(shù)。

3. 多繼承

3.1普通的多繼承 + 虛函數(shù)

舉出如下代碼進行實驗:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "A::fun2()" << endl;

	}
	int _a = 0;
};

class B
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
	int _b = 0;
};

class C : public A ,public B
{
public:
	virtual void  fun1()
	{
		cout << "C::fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << "C::fun3()" << endl;
	}
	int _c = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
	cout << endl;
}
int main()
{
	C c;
	void** vftptr1 = (void **)(*(int*)(&c));

	B* ptr = &c;
	void** vftptr2 = (void **)(*(int*)(ptr));


	Print((FUN_PTR*)vftptr1);
	Print((FUN_PTR*)vftptr2);

	return 0;
}

首先我們要看初始化之后的類C的對象模型
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 細(xì)節(jié):可以根據(jù)_vfptr的的信息看出有幾個元素。這里A的有四個,B的有三個。
    根據(jù)此畫出對象模型:
    【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
    運行結(jié)果:
    【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
  • 總結(jié):繼承的子類的不構(gòu)成重寫虛函數(shù)不會再生成虛表(除非沒有),而是將虛函數(shù)放在第一個父類的虛表中 ; 多繼承父類不共享虛表,而是各用個的。

除此之外,這里還會衍生出一個問題:運行結(jié)果C::fun1()的地址竟然不同,這是為什么呢?

將上述代碼進行簡化:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};
class B
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
};

class C : public A, public B
{
public:
	virtual void fun1()
	{
		cout << "C::fun1()" << endl;
	}
};

int main()
{
	C c;
	B* b = &c;
	A* a = &c;

	a->fun1();
	b->fun1();
	return 0;
}

調(diào)用函數(shù)的反匯編流程圖:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 可見,b對象在調(diào)用真正的fun1時拐了一個彎,然后再調(diào)用fun1。

為啥要這樣這樣做呢?

  • 看關(guān)鍵動作——對ecx減8,ecx存放的是this指針,對this指針減8,到C對象的this指針位置,通過C的this指針再進行調(diào)用fun1。為啥要這樣做呢?因為fun1的作用域是C的類域,直接用B的this指針顯然不合理。
  • 因此:調(diào)整B的this指針是為了類域的獨立性,那A對象咋不用呢?因為A的this指針本就可以當(dāng)做D的this指針進行使用,沒必要再偏。

3.2菱形繼承 + 虛函數(shù)

同樣給出一段代碼實驗:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};

class B : public A
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
	int _b = 0;
};

class C : public A 
{
public:
	virtual void  fun1()
	{
		cout << "C::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "C::fun2()" << endl;
	}
	int _c = 0;
};
class D : public B , public C
{
public:
	virtual void  fun2()
	{
		cout << "D::fun2()" << endl;
	}
	virtual void fun3()
	{
		cout << "D::fun3()" << endl;
	}
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
	cout << endl;
}
int main()
{
	D d;
	void** vftptr1 = (void**)(*((int*)(&d)));

	C* b = &d;
	void** vftptr2 = (void**)(*((int*)(b)));

	Print((FUN_PTR*)vftptr1);
	Print((FUN_PTR*)vftptr2);

	return 0;
}

初始化對象后的D類的監(jiān)視窗口:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
運行結(jié)果:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
據(jù)此畫出D類對象的對象模型:

【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 其實跟多繼承差不多,也就多套了一層,這里解釋一下,B類的fun1重寫A類的fun1,D類的fun2重寫B(tài)類的fun2,D類的fun3放在第一個父類對象的虛表中。C類同理這里就不多說了。

3.3菱形虛擬繼承 + 虛函數(shù)

貼出一段代碼進行實驗:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};

class B : virtual public A
{
public:
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
	int _b = 0;
};

class C : virtual public A
{
public:
	virtual void fun2()
	{
		cout << "C::fun2()" << endl;
	}
	int _c = 0;
};
class D : public B , public C
{
public:
	virtual void fun3()
	{
		cout << "D::fun3()" << endl;
	}
	virtual void  fun4()
	{
		cout << "D::fun4()" << endl;
	}
	int _d = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
	cout << endl;
}

int main()
{
	D d;
	void** vftptr1 = (void**)(*((int*)(&d)));

	C* b = &d;
	void** vftptr2 = (void**)(*((int*)(b)));

	A* a = &d;
	void** vftptr3 = (void**)(*((int*)(a)));

	Print((FUN_PTR*)vftptr1);
	Print((FUN_PTR*)vftptr2);
	Print((FUN_PTR*)vftptr3);
	return 0;
}

d初始化后的監(jiān)視窗口:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

運行結(jié)果:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
結(jié)合內(nèi)存畫出對象模型:

【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • D單獨的虛函數(shù)放在第一張?zhí)摫碇?,如果除A的虛表外,沒有虛表可以放,那就放自己的虛表中。
  • B,C的各自的虛函數(shù),分別存在兩張?zhí)摫碇小?/li>
  • A,存放一張?zhí)摫怼?/li>
  • 類對象第一個位置存放的是虛表指針,而不是虛基表指針。 因此虛基表指針指向的第一個位置變成了ff ff ff fc,轉(zhuǎn)換成int也就是 - 4 ,這是虛表指針的地址相對類的this指針偏移量(this指針 - 虛表指針的地址)。第二個位置存的是虛表指針的地址相對于A的偏移量。第三個位置存的是0,個人理解:表示終止位置。

此外,我們還需注意避免不同子類重寫基類的問題:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{}

};
class B : virtual public A
{
public:
	virtual void fun1()
	{}

};

class C : virtual public A
{
public:
	virtual void fun1()
	{}

};
class D : public B, public C
{
public:
	virtual void fun2()
	{}
	

};

編譯結(jié)果:
【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)
畫個圖理解一下:

【C++進階之路】多態(tài)篇,C++進階之路,c++,多態(tài)

  • 問題:二義性,A不知該繼承誰的。
  • 解決方法:交給孫子類決定。

代碼如下:

class A
{
public:
	virtual void fun1()
	{}
	int _a = 0;
};
class B : virtual public A
{
public:
	virtual void fun1()
	{}
	int _b = 0;
};

class C : virtual public A
{
public:
	virtual void fun1()
	{}
	int _c = 0;
};
class D : public B, public C
{
public:
	virtual void fun1()
	{}
	virtual void fun2()
	{}
	int _d;
};

4.inline與static

4.1inline

補充知識:

1.內(nèi)聯(lián)函數(shù),可以說不是函數(shù),是一段代碼。
2.類里面的成員函數(shù),默認(rèn)inline修飾。
3. inline修飾函數(shù),不一定是內(nèi)聯(lián)函數(shù),取決于函數(shù)的實現(xiàn)是否復(fù)雜,最終還是要編譯器決定的。但內(nèi)聯(lián)函數(shù)一定是被inline修飾的!

  • 虛函數(shù)前是可以加inline的,但其不是內(nèi)聯(lián)函數(shù),原因是因為虛函數(shù)是需要被放在虛表中的。

4.2 static

  • static是不能修飾虛函數(shù)的,其原因在于虛函數(shù)是為了實現(xiàn)多態(tài)的,而多態(tài)的條件是父類的指針或者引用,其本質(zhì)上都是傳了子類的this指針,但static修飾的函數(shù)是沒有this指針的,無法實現(xiàn)多態(tài),因此不能用static修飾虛函數(shù)。

總結(jié)

?今天的分享就到這里了,如果覺得文章不錯,點個贊鼓勵一下吧!我們下篇文章再見!文章來源地址http://www.zghlxwxcb.cn/news/detail-618784.html

到了這里,關(guān)于【C++進階之路】多態(tài)篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【C++進階】:多態(tài)

    【C++進階】:多態(tài)

    虛函數(shù) 虛函數(shù)重寫 注意,虛函數(shù)重寫的是實現(xiàn)部分,如果父類在參數(shù)上給了缺省值,那么重寫時依然使用父類的缺省值,與子類的缺省值無關(guān)。 1.協(xié)變 派生類重寫基類虛函數(shù)時,與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對象的指針或者引用,派生類虛函數(shù)返回

    2024年02月15日
    瀏覽(14)
  • 【C++進階】多態(tài)的理解

    【C++進階】多態(tài)的理解

    多態(tài)是在不同繼承關(guān)系的類對象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。 對于多態(tài), 不同的對象傳過去,會調(diào)用不同的函數(shù) ; 即多態(tài)調(diào)用看的是 指向的對象 。 多態(tài)分為兩種: ? ? ? ? 1. 靜態(tài)綁定,也稱為靜態(tài)多態(tài),是在程序編譯階段確定的,例如:函數(shù)重載和模板 ;

    2024年02月15日
    瀏覽(16)
  • 【C++進階】繼承、多態(tài)的詳解(繼承篇)

    【C++進階】繼承、多態(tài)的詳解(繼承篇)

    作者:愛寫代碼的剛子 時間:2023.7.28 前言:本篇博客主要介紹C++進階部分內(nèi)容——繼承,C++中的繼承和多態(tài)是比較復(fù)雜的,需要我們認(rèn)真去深挖其中的細(xì)節(jié)。 繼承的概念及定義 繼承的概念 繼承(inheritance)機制是面向?qū)ο蟪绦蛟O(shè)計使代碼可以復(fù)用的最重要的手段,它允許程序

    2024年02月13日
    瀏覽(22)
  • 【C++進階之路】第一篇:C++中的繼承

    【C++進階之路】第一篇:C++中的繼承

    ??hello,各位讀者大大們你們好呀?? ????系列專欄:【C++學(xué)習(xí)與應(yīng)用】 ????本篇內(nèi)容:繼承的基礎(chǔ)概念,定義方法,基類和派生類的轉(zhuǎn)換,繼承中類的作用域,什么是隱藏,派生類和默認(rèn)成員函數(shù),繼承和友元,繼承與靜態(tài)函數(shù),菱形繼承和虛擬繼承,菱形繼承總結(jié)

    2024年02月02日
    瀏覽(20)
  • 【C++進階之路】類和對象(中)

    【C++進階之路】類和對象(中)

    1.構(gòu)造函數(shù)——完成對象成員變量的初始化 2.析構(gòu)函數(shù)——完成空間(主要是堆)的釋放 3.拷貝構(gòu)造——用一個已初始化的對象初始另一個正在初始化的對象 4.賦值重載——用一個已初始化的對象賦值給另一個已經(jīng)初始化的對象 5.取地址重載——對一個不加const的對象取地址 6.

    2024年02月03日
    瀏覽(20)
  • 【C++進階之路】模擬實現(xiàn)string類

    【C++進階之路】模擬實現(xiàn)string類

    本文所屬專欄——【C++進階之路】 ?上一篇,我們講解了string類接口的基本使用,今天我們就實戰(zhàn)從底層實現(xiàn)自己的string類,當(dāng)然實現(xiàn)所有的接口難度很大,我們今天主要實現(xiàn)的常用的接口~ 1.為了 不與庫里面的string沖突 ,我們需要 命名空間 對 自己實現(xiàn)的類進行封裝 2.這里

    2024年02月13日
    瀏覽(18)
  • 【C++進階之路】適配器、反向迭代器、仿函數(shù)

    【C++進階之路】適配器、反向迭代器、仿函數(shù)

    我們先來籠統(tǒng)的介紹一下今天的三個內(nèi)容。 適配器——簡單的理解就是 復(fù)用 ,用已經(jīng)實現(xiàn)的輪子,來繼續(xù)實現(xiàn)某種功能。 反向迭代器——原理很簡單,就是 對稱+復(fù)用 (已經(jīng)造好的正向迭代器) 仿函數(shù)—— 與函數(shù)用法相同 的類,用于排序,比如大堆,小堆,升序,降序。

    2024年02月16日
    瀏覽(16)
  • Edu第3關(guān):封裝、繼承和多態(tài)進階(三)

    任務(wù)描述 本關(guān)任務(wù):通過一個簡單實例講解并自己動手編寫一個Java應(yīng)用程序,全面復(fù)習(xí)Java面向?qū)ο笾R。 相關(guān)知識 為了完成本關(guān)任務(wù),我們通過一個實例來一步一步總結(jié)歸納Java面向?qū)ο蟮闹R。 package test; /*知識點目錄 1,Java繼承 1.1 繼承的概念 1.2 繼承的特性 1.3 繼承關(guān)

    2023年04月24日
    瀏覽(26)
  • 【Lua學(xué)習(xí)筆記】Lua進階——Table(4)繼承,封裝,多態(tài)

    【Lua學(xué)習(xí)筆記】Lua進階——Table(4)繼承,封裝,多態(tài)

    現(xiàn)在我們可以像面向?qū)ο笠粯?,new一個對應(yīng)基類的對象了。但是這里的new也不完全相似與面向?qū)ο蟮膎ew,例如我們可以這樣做: 我們在封裝Object類的時候可完全沒有name這個索引,而在Lua中我們new了一個新對象,還能新加入一些變量和方法,這些特性明顯是繼承了父類的子類才

    2024年02月15日
    瀏覽(29)
  • [C++] 多態(tài)(下) -- 多態(tài)原理 -- 動靜態(tài)綁定

    [C++] 多態(tài)(下) -- 多態(tài)原理 -- 動靜態(tài)綁定

    上一篇文章我們了解了虛函數(shù)表,虛函數(shù)表指針,本篇文章我們來了解多態(tài)的底層原理,更好的理解多態(tài)的機制。 [C++] 多態(tài)(上) – 抽象類、虛函數(shù)、虛函數(shù)表 下面這段代碼中,F(xiàn)unc函數(shù)傳Person調(diào)用的Person::BuyTicket,傳Student調(diào)用的是Student::BuyTicket,這就是多態(tài)調(diào)用,但是這里我

    2024年02月04日
    瀏覽(18)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包