最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++虛函數(shù)到動(dòng)態(tài)綁定的核心原理深入分析

 更新時(shí)間:2025年05月10日 10:46:37   作者:孫同學(xué)_  
這篇文章主要介紹了C++虛函數(shù)到動(dòng)態(tài)綁定的核心原理,C++??虛函數(shù)??通過??虛函數(shù)表和??虛指針實(shí)現(xiàn)動(dòng)態(tài)綁定,使得基類指針引用在運(yùn)行時(shí)能根據(jù)對象實(shí)際類型調(diào)用正確的函數(shù)版本,從而支持多態(tài),需要的朋友可以參考下

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))。編譯時(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ù)),傳不同的對象就可以完成不同的行為,就達(dá)到多種形態(tài)。就如買票這個(gè)行為,當(dāng)普通人買票時(shí)是全價(jià)買票,當(dāng)學(xué)生買票時(shí)是學(xué)生票,軍人買票時(shí)是優(yōu)先買票。再比如,同樣是動(dòng)物叫的一個(gè)行為,傳“貓”過去就是“喵喵”,傳“狗”過去就是“汪汪”。

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

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

多態(tài)是一個(gè)繼承關(guān)系下的類對象,去調(diào)用同一函數(shù)產(chǎn)生了不同行為。比如Student繼承了Person。Person對象買票全價(jià),Student對象優(yōu)惠買票。

2.1.1實(shí)現(xiàn)多態(tài)還有兩個(gè)必須重要條件

  • 必須是基類的指針或者引用調(diào)用虛函數(shù)
  • 被調(diào)用的函數(shù)必須是虛函數(shù),并且完成了虛函數(shù)重寫/覆蓋。

說明:要實(shí)現(xiàn)多態(tài)的效果,第一必須是基類的指針或者引用,因?yàn)橹挥谢惖闹羔樆蛞貌拍芗戎赶蚧惖膶ο?,又能指向派生類的對象。第二派生類必須對基類的虛函?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)常埋這樣一個(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指向的對象決定的
	ptr->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
	return 0;
}
//引用
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指向的對象決定的
	ptr.BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	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;
}

2.1.4 多態(tài)場景的一個(gè)選擇題

以下程序輸出結(jié)果是什么()

A: A->0 B: B->1 C: A->1 D: B->0 E: 編譯出錯(cuò) F: 以上都不正確

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;
}

這里由this調(diào)用func,構(gòu)成多態(tài)必須是父類的指針或者引用,這里是否構(gòu)成多態(tài)呢?這里的thisA*,所以它是一個(gè)父類的指針。

繼承它會把父類的成員拿下來是一種形象的說法,其實(shí)不會把它拿下來,它的復(fù)用是先去B里面去找有沒有test,再去A里面找,如果A里面還找不到就會報(bào)錯(cuò)。復(fù)用的本質(zhì)先去B里面找,再去A里面找。包括成員變量,如果A里面有一個(gè)_aB里面有一個(gè)_b,它不是把A里面的拷貝下來在B里面也生成一份,是B對象生成的時(shí)候會先生成一個(gè)A對象的編譯器,在這個(gè)對象模型在內(nèi)存里面放的時(shí)候再放B的成員,不會說是把分類的成員函數(shù)和成員對象都拷貝一份下來。

所以說第一個(gè)條件是滿足的。

第二個(gè)條件是虛函數(shù)的重寫,也就是func,func的函數(shù)名,參數(shù)類型,返回值都相同,它是父類的重寫,所以也滿足是虛函數(shù)。

所以是滿足多態(tài)的,滿足多態(tài)是指向誰調(diào)用誰,p傳給了thisp是指向一個(gè)派生類的B對象的。

滿足多態(tài)的情況下,調(diào)用子類重寫的虛函數(shù)

2.1.5 虛函數(shù)重寫的一些其他問題

協(xié)變(了解)

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

class A {};
class B : public A {};
class Person {
public:
	virtual A* BuyTicket()
	{
		cout << "買票-全價(jià)" << endl;
		return nullptr;
	}
};
class Student : public Person {
public:
	virtual B* BuyTicket()
	{
		cout << "買票-打折" << endl;
		return nullptr;
	}
};
void Func(Person* ptr)
{
	ptr->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
	return 0;
}

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

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

class A
{
public:
	virtual ~A()
	{
		cout << "~A()" << endl;
	}
};
class B : public A {
public:
	~B()
	{
		cout << "~B()->delete:" << _p << endl;
		delete _p;
	}
protected:
	int* _p = new int[10];
};
// 只有派生類Student的析構(gòu)函數(shù)重寫了Person的析構(gòu)函數(shù),下面的delete對象調(diào)用析構(gòu)函數(shù),才能
//構(gòu)成多態(tài),才能保證p1和p2指向的對象正確的調(diào)用析構(gòu)函數(shù)。
int main()
{
	A* p1 = new A;
	A* p2 = new B;
	delete p1;
	delete p2;
	return 0;
}

2.1.6 override和final關(guān)鍵字

C++對虛函數(shù)的重寫要求比較嚴(yán)格,但是有些情況寫由于疏忽,比如函數(shù)名寫錯(cuò)或者是參數(shù)寫錯(cuò)導(dǎo)致無法構(gòu)成重寫,而這種錯(cuò)誤是在編譯期間是不會報(bào)錯(cuò)的,只有在程序運(yùn)行時(shí)沒有得到預(yù)期結(jié)果,再進(jìn)行找錯(cuò)誤就會得不償失,因此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;
}

2.1.7 重載/重寫/隱藏的對比

注意:這個(gè)概念對比經(jīng)??迹。。?/p>

3. 純虛函數(shù)和抽象類

在虛函數(shù)的后面加上=0,則這個(gè)函數(shù)為純虛函數(shù),純虛函數(shù)不需要定義實(shí)現(xiàn)(實(shí)現(xiàn)沒啥意義,因?yàn)橐慌缮愔貙?,但語法上可以實(shí)現(xiàn)),只要聲明即可。包含純虛函數(shù)的類稱為抽象類,抽象類不能實(shí)例化出對象,如果派生類繼承后不重寫虛函數(shù),那么派生類也是抽象類。純虛函數(shù)某種程度上強(qiáng)制了派生類必須重寫虛函數(shù),因?yàn)椴恢貙憣?shí)例化不出對象。

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

4.1 虛函數(shù)表指針

下面編譯為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;
}

4.2 多態(tài)的原理

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

從底層的角度Func函數(shù)中ptr->BuyTicket(),是如何作為ptr指向Person對象調(diào)用Person::BuyTicket,ptr指向Student對象調(diào)用Student::BuyTicket的呢?通過下圖我們可以看到滿足多態(tài)條件后,底層不再是編譯時(shí),通過調(diào)用函數(shù)對象確定函數(shù)地址,而是運(yùn)行時(shí)到指向?qū)ο蟮奶摫碇?,確定對應(yīng)的虛函數(shù)的地址。

第一張圖,ptr指向Person對象,調(diào)用的是Person的虛函數(shù);第二張圖ptr指向的是Student對象,調(diào)用的是Student的虛函數(shù)。

多態(tài):指向誰調(diào)用誰的虛函數(shù)

指向父類,運(yùn)行時(shí)到指向父類對象的虛函數(shù)表中找到對應(yīng)的虛函數(shù)進(jìn)行調(diào)用

指向子類,運(yùn)行時(shí)到指向子類對象切片出的父類的虛函數(shù)表中找到對應(yīng)的虛函數(shù)進(jìn)行調(diào)用。

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

  • 對不滿足多態(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)綁定。
// 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)條件。 
 // 這?就是靜態(tài)綁定,編譯器直接確定調(diào)?函數(shù)地址 
 ptr->BuyTicket();
00EA2C91 mov ecx,dword ptr [ptr] 
00EA2C94 call Student::Student (0EA153Ch)

4.2.3 虛函數(shù)表

  • 基類對象的虛函數(shù)表中存放,同類型的對象公用同一張?zhí)摫?,不同類型的對象各自有?dú)立的虛表,所以基類和派生類各自有獨(dú)立的虛表。
  • 派生類由兩部分構(gòu)成,繼承下來的基類和自己的成員,一般情況下,繼承下來的基類中有虛函數(shù)表指針,自己就不再生成虛函數(shù)表指針。但是要注意的是這里繼承下來的虛函數(shù)表指針和基類對象的虛函數(shù)表指針不是同一個(gè),就像基類對象的成員,和派生類繼承下來的基類對象的成員也是獨(dú)立的。
  • 派生類中重寫的基類的虛函數(shù),派生類的虛函數(shù)表中對應(yīng)的虛函數(shù)就會被覆蓋成派生類重寫的虛函數(shù)地址。
  • 派生類的虛函數(shù)表中包括:(1)基類的虛函數(shù)地址(2)派生類重寫的虛函數(shù)地址完成覆蓋(3)派生類自己的虛函數(shù)地址
  • 虛函數(shù)表本質(zhì)上是一個(gè)存儲虛函數(shù)指針的指針數(shù)組,一般情況下這個(gè)指針數(shù)組最后面放了一個(gè)0x00000000標(biāo)記。(這個(gè)C++并沒有進(jìn)行規(guī)定,各個(gè)編譯器自行定義的,vs系列編譯器會再后?放個(gè)0x00000000標(biāo)記,g++系列編譯不會放)
  • 虛函數(shù)存在哪的?虛函數(shù)和普通函數(shù)一樣,編譯好是一段指令,都是存放在代碼段的,只是虛函數(shù)的地址又存在虛表中。
  • 虛函數(shù)表存放在哪的?這個(gè)問題嚴(yán)格說并沒有標(biāo)準(zhǔn)答案C++標(biāo)準(zhǔn)并沒有規(guī)定,我們寫下面的代碼可以對比驗(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ū):%p\n", &j);
	printf("堆:%p\n", p1);
	printf("常量區(qū):%p\n", p2);
	Base b;
	Derive d;
	Base* p3 = &b;
	Derive* p4 = &d;
	printf("Base虛表地址:%p\n", *(int*)p3);
	printf("Derive虛表地址:%p\n", *(int*)p4);
	printf("虛函數(shù)地址:%p\n", &Base::func1);
	printf("普通函數(shù)地址:%p\n", &Base::func5);
	return 0;
}

以上就是C++虛函數(shù)到動(dòng)態(tài)綁定的核心原理深入分析的詳細(xì)內(nèi)容,更多關(guān)于C++虛函數(shù)到動(dòng)態(tài)綁定的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C++之const和static的使用及說明

    C++之const和static的使用及說明

    C++中const與static的用法:const限制變量/對象只讀性,確保數(shù)據(jù)安全;static控制變量/函數(shù)的存儲周期與作用域,實(shí)現(xiàn)數(shù)據(jù)共享,兩者結(jié)合的static const用于定義類級常量,需注意const變量不可強(qiáng)制修改、static局部變量線程安全問題及類static成員必須類外初始化的要點(diǎn)
    2025-09-09
  • C++?手?jǐn)]簡易服務(wù)器

    C++?手?jǐn)]簡易服務(wù)器

    本文主要介紹了C++?手?jǐn)]簡易服務(wù)器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • C語言實(shí)現(xiàn)酒店管理系統(tǒng)

    C語言實(shí)現(xiàn)酒店管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • Win10+VS2017新CUDA項(xiàng)目配置教程

    Win10+VS2017新CUDA項(xiàng)目配置教程

    這篇文章主要為大家詳細(xì)介紹了Win10+VS2017新CUDA項(xiàng)目配置教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • 淺談c++構(gòu)造函數(shù)問題,初始化和賦值問題

    淺談c++構(gòu)造函數(shù)問題,初始化和賦值問題

    下面小編就為大家?guī)硪黄獪\談c++構(gòu)造函數(shù)問題,初始化和賦值問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-12-12
  • C語言標(biāo)準(zhǔn)時(shí)間與秒單位相互轉(zhuǎn)換

    C語言標(biāo)準(zhǔn)時(shí)間與秒單位相互轉(zhuǎn)換

    這篇文章主要介紹了C語言標(biāo)準(zhǔn)時(shí)間與秒單位相互轉(zhuǎn)換,秒單位與標(biāo)準(zhǔn)時(shí)間的轉(zhuǎn)換方式,這份代碼一般用在嵌入式單片機(jī)里比較多,比如:設(shè)置RTC時(shí)鐘的時(shí)間,從RTC里讀取秒單位時(shí)間后,需要轉(zhuǎn)換成標(biāo)準(zhǔn)時(shí)間顯示。下文分享需要的小伙伴可以參考一下
    2022-05-05
  • C++使用模板實(shí)現(xiàn)單鏈表(類外實(shí)現(xiàn))

    C++使用模板實(shí)現(xiàn)單鏈表(類外實(shí)現(xiàn))

    這篇文章主要為大家詳細(xì)介紹了C++使用模板實(shí)現(xiàn)單鏈表的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • 利用Matlab編寫簡易版連連看小游戲

    利用Matlab編寫簡易版連連看小游戲

    連連看作為經(jīng)典的小游戲,一定是很多人的回憶吧。本文將用Matlab實(shí)現(xiàn)這一經(jīng)典的游戲,文中示例代碼具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C語言數(shù)組按協(xié)議存儲與按協(xié)議解析數(shù)據(jù)的實(shí)現(xiàn)

    C語言數(shù)組按協(xié)議存儲與按協(xié)議解析數(shù)據(jù)的實(shí)現(xiàn)

    今天小編就為大家分享一篇關(guān)于C語言數(shù)組按協(xié)議存儲與按協(xié)議解析數(shù)據(jù)的實(shí)現(xiàn),小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • 詳解C語言初階之函數(shù)

    詳解C語言初階之函數(shù)

    這篇文章主要介紹了C語言中的初階函數(shù),介紹了其相關(guān)概念,具有一定參考價(jià)值。需要的朋友可以了解下,希望能夠給你帶來幫助
    2021-11-11

最新評論

砚山县| 常宁市| 武功县| 阳江市| 正阳县| 女性| 磐安县| 江口县| 旬阳县| 新巴尔虎左旗| 密山市| 招远市| 汝城县| 漠河县| 满洲里市| 突泉县| 新绛县| 抚顺市| 大石桥市| 黑山县| 沁源县| 临泽县| 泸州市| 湘潭县| 无极县| 温州市| 运城市| 怀柔区| 洛川县| 邛崃市| 新巴尔虎左旗| 泰安市| 大埔县| 谷城县| 德阳市| 郎溪县| 拜城县| 岱山县| 石棉县| 岳普湖县| 莱州市|