亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

多態(tài)(polymorphism)的概念:通俗來說就是指多種形態(tài),多態(tài)分為編譯時多態(tài)(靜態(tài)多態(tài))和運行時多態(tài)(動態(tài)多態(tài))。編譯時多態(tài)(靜態(tài)多態(tài))主要是前面的函數(shù)重載和函數(shù)模板,它們傳不同類型的參數(shù)就可以調(diào)用不同類型的函數(shù),通過參數(shù)不同達到多種形態(tài),之所以叫做編譯時多態(tài),是因為它們實參傳給形參的參數(shù)匹配是在編譯時完成的,我們把編譯時一般歸為靜態(tài),運行時歸為動態(tài)。

運行時多態(tài),具體點就是去完成某個行為(函數(shù)),傳不同的對象就可以完成不同的行為,就達到多種形態(tài)。就如買票這個行為,當普通人買票時是全價買票,當學生買票時是學生票,軍人買票時是優(yōu)先買票。再比如,同樣是動物叫的一個行為,傳“貓”過去就是“喵喵”,傳“狗”過去就是“汪汪”。

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

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

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

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

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

說明:要實現(xiàn)多態(tài)的效果,第一必須是基類的指針或者引用,因為只有基類的指針或引用才能既指向基類的對象,又能指向派生類的對象。第二派生類必須對基類的虛函數(shù)完成重寫/覆蓋,重寫/覆蓋了基類和派生類之間才能有不同的函數(shù),多態(tài)的不同形態(tài)效果才能達成。

2.1.2 虛函數(shù)

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

class Person 
{
public:
 virtual void BuyTicket() { cout << "買票-全價" << endl;}
};

2.1.3 虛函數(shù)的重寫/覆蓋

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

注意:再重寫基類虛函數(shù)時,派生類的虛函數(shù)在不加virtual關鍵字時,雖然也可以構成重寫,因為繼承后基類的虛函數(shù)被繼承下來了,在派生類中依舊保持虛函數(shù)的屬性),但是該種寫法不是很規(guī)范,不建議這樣使用。在考試題中會經(jīng)常埋這樣一個坑,讓我們判斷是否構成多態(tài)。

//指針
class Person
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "買票-全價" << endl; 
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket() 
	{
		cout << "買票-打折" << endl;
	}
};
void Func(Person* ptr)
{
	//這里雖然都是Person指針ptr調(diào)用BuyTicket
	//但是和ptr沒關系,而是由ptr指向的對象決定的
	ptr->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
	return 0;
}
//引用
class Person
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "買票-全價" << endl; 
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket() 
	{
		cout << "買票-打折" << endl;
	}
};
void Func(Person& ptr)
{
	//這里雖然都是Person指針ptr調(diào)用BuyTicket
	//但是和ptr沒關系,而是由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)場景的一個選擇題

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

A: A->0 B: B->1 C: A->1 D: B->0 E: 編譯出錯 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,構成多態(tài)必須是父類的指針或者引用,這里是否構成多態(tài)呢?這里的thisA*,所以它是一個父類的指針。

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

所以說第一個條件是滿足的。

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

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

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

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

協(xié)變(了解)

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

class A {};
class B : public A {};
class Person {
public:
	virtual A* BuyTicket()
	{
		cout << "買票-全價" << 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;
}

析構函數(shù)的重寫

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

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的析構函數(shù)重寫了Person的析構函數(shù),下面的delete對象調(diào)用析構函數(shù),才能
//構成多態(tài),才能保證p1和p2指向的對象正確的調(diào)用析構函數(shù)。
int main()
{
	A* p1 = new A;
	A* p2 = new B;
	delete p1;
	delete p2;
	return 0;
}

2.1.6 override和final關鍵字

C++對虛函數(shù)的重寫要求比較嚴格,但是有些情況寫由于疏忽,比如函數(shù)名寫錯或者是參數(shù)寫錯導致無法構成重寫,而這種錯誤是在編譯期間是不會報錯的,只有在程序運行時沒有得到預期結(jié)果,再進行找錯誤就會得不償失,因此C++11提供了override,可以幫助用戶檢測是否重寫。如果我們不想讓派生類重寫這個虛函數(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 重載/重寫/隱藏的對比

注意:這個概念對比經(jīng)??迹。?!

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

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

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

4.1 虛函數(shù)表指針

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

A. 編譯報錯 B. 運行報錯 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)是如何實現(xiàn)的

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

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

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

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

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

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

  • 對不滿足多態(tài)條件(指針或者引用 + 調(diào)用虛函數(shù))的函數(shù)調(diào)用是在編譯時綁定,也就是編譯時確定調(diào)用函數(shù)的地址,叫做靜態(tài)綁定。
  • 滿足多態(tài)條件的函數(shù)調(diào)用是在運行時綁定,也就是在運行時到指向?qū)ο蟮奶摵瘮?shù)表中,找到調(diào)用函數(shù)的地址,叫做動態(tài)綁定。
// ptr是指針+BuyTicket是虛函數(shù)滿?多態(tài)條件。 
 // 這?就是動態(tài)綁定,編譯在運?時到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í)摫?,不同類型的對象各自有獨立的虛表,所以基類和派生類各自有獨立的虛表?/li>
  • 派生類由兩部分構成,繼承下來的基類和自己的成員,一般情況下,繼承下來的基類中有虛函數(shù)表指針,自己就不再生成虛函數(shù)表指針。但是要注意的是這里繼承下來的虛函數(shù)表指針和基類對象的虛函數(shù)表指針不是同一個,就像基類對象的成員,和派生類繼承下來的基類對象的成員也是獨立的。
  • 派生類中重寫的基類的虛函數(shù),派生類的虛函數(shù)表中對應的虛函數(shù)就會被覆蓋成派生類重寫的虛函數(shù)地址。
  • 派生類的虛函數(shù)表中包括:(1)基類的虛函數(shù)地址(2)派生類重寫的虛函數(shù)地址完成覆蓋(3)派生類自己的虛函數(shù)地址
  • 虛函數(shù)表本質(zhì)上是一個存儲虛函數(shù)指針的指針數(shù)組,一般情況下這個指針數(shù)組最后面放了一個0x00000000標記。(這個C++并沒有進行規(guī)定,各個編譯器自行定義的,vs系列編譯器會再后?放個0x00000000標記,g++系列編譯不會放)
  • 虛函數(shù)存在哪的?虛函數(shù)和普通函數(shù)一樣,編譯好是一段指令,都是存放在代碼段的,只是虛函數(shù)的地址又存在虛表中。
  • 虛函數(shù)表存放在哪的?這個問題嚴格說并沒有標準答案C++標準并沒有規(guī)定,我們寫下面的代碼可以對比驗證?下。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ù)到動態(tài)綁定的核心原理深入分析的詳細內(nèi)容,更多關于C++虛函數(shù)到動態(tài)綁定的資料請關注腳本之家其它相關文章!

相關文章

  • C++標準模版庫(STL)之vector容器詳解

    C++標準模版庫(STL)之vector容器詳解

    vector的功能和水桶一樣,就是用來裝東西的,并且vector還提供了迭代器來很方便的訪問這些數(shù)據(jù),下面就讓我們一起看下如何使用C++的vector吧
    2023-03-03
  • C++ 中 vector 的常用操作方法匯總

    C++ 中 vector 的常用操作方法匯總

    在C++的STL中,vector是一個動態(tài)數(shù)組,可以在運行時調(diào)整大小,本文介紹了vector的初始化、元素訪問、修改、迭代器操作、容量管理以及性能優(yōu)化技巧,通過這些操作,可以有效地使用vector管理數(shù)據(jù),本文介紹C++  vector 操作,感興趣的朋友一起看看吧
    2024-10-10
  • Matlab利用隨機森林(RF)算法實現(xiàn)回歸預測詳解

    Matlab利用隨機森林(RF)算法實現(xiàn)回歸預測詳解

    這篇文章主要為大家詳細介紹了Matlab如何利用隨機森林(RF)算法實現(xiàn)回歸預測,以及自變量重要性排序的操作,感興趣的小伙伴可以了解一下
    2023-02-02
  • C++中實現(xiàn)WebSocket通信的兩種方法:libwebsockets庫、Boost.Beast?庫

    C++中實現(xiàn)WebSocket通信的兩種方法:libwebsockets庫、Boost.Beast?庫

    C++中WebSocket庫主要有以下幾個?:cpp-websocket?、asio_websocket?、websockets++?、?websocketpp?、?libwebsockets?、?uWebSockets?、Boost.Beast?、Simple-WebSocket-Server?,這篇文章使用libwebsockets庫、Boost.Beast?庫來實現(xiàn)c++中的WebSocket通信
    2025-01-01
  • Cocos2d-x UI開發(fā)之CCControlSlider控件類使用實例

    Cocos2d-x UI開發(fā)之CCControlSlider控件類使用實例

    這篇文章主要介紹了Cocos2d-x UI開發(fā)之CCControlSlider控件類使用實例,本文代碼中包含大量注釋講解了CCControlSlider控件類的使用,需要的朋友可以參考下
    2014-09-09
  • VS2019項目打包生成.exe文件與Setup的步驟實現(xiàn)

    VS2019項目打包生成.exe文件與Setup的步驟實現(xiàn)

    這篇文章主要介紹了VS2019項目打包生成.exe文件與Setup的步驟實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • C語言的動態(tài)內(nèi)存管理你了解嗎

    C語言的動態(tài)內(nèi)存管理你了解嗎

    這篇文章主要為大家詳細介紹了C語言的動態(tài)內(nèi)存管理,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • C++設計模式之策略模式

    C++設計模式之策略模式

    這篇文章主要介紹了C++設計模式之策略模式,本文講解了什么是策略模式、策略模式的使用場合、策略模式的代碼實例等內(nèi)容,需要的朋友可以參考下
    2014-10-10
  • C++實現(xiàn)圖書管理系統(tǒng)課程設計(面向?qū)ο?

    C++實現(xiàn)圖書管理系統(tǒng)課程設計(面向?qū)ο?

    這篇文章主要為大家詳細介紹了C++實現(xiàn)圖書管理系統(tǒng)課程設計,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 基于C語言實現(xiàn)的迷宮游戲代碼

    基于C語言實現(xiàn)的迷宮游戲代碼

    這篇文章主要介紹了基于C語言實現(xiàn)的迷宮游戲代碼,對于學習游戲開發(fā)的朋友相信有一定的借鑒價值,需要的朋友可以參考下
    2014-08-08

最新評論