C++的虛函數和多態性

雖然對象不同但是調用的函數接口相同,使用時更有整體性和一致性,而不同的對象接收同一消息卻有不同的行爲,這種現象就是c++中的多態性。


其實c++的多態性,有兩種表現,分別是編譯時的多態性和運行時的多態性。


編譯時的多態性:比如函數和運行符的重載,對象調用同一個接口函數(參數不同),會有不同的方法。編譯後函數的符號由於參數的不同而不同,因此能定位到程序塊地址。

運行時的多態性:通過基類引用或指針調用基類中定義的函數時,由於並不確定函數執行的對象,有可能是基類也有可能是派生類,所以需要通過後期綁定技術確定執行的函數。


運行時的多態性通過虛函數實現,虛函數必須存在於繼承的環境下。基類的指針指向其派生類,用該指針調用類中的虛函數,則表現的是派生類的方法。那麼虛函數是通過什麼機制來確定當前對象調用的函數地址的呢?


其實虛函數機制也並不神祕,它只是對應的類有一張虛函數表(Virtual Table),在這個表裏是虛函數的入口地址,在實例化一個對象時,對象內存空間的最前端指向這張表,這樣保證高效的獲取虛函數地址。當子類繼承父類並重新定義了父類的虛函數時,子類的虛函數表中的虛函數地址就覆蓋了父類的虛函數。那麼當基類的引用(或指針)調用虛函數時,該引用從實際對象裏獲得虛函數表,在表裏找到虛函數。由於引用的實際類型可能是基類也可能是子類,只有實際運行時才能確定,所以呈現出多態性。


示意圖:


代碼驗證:

virtual_func.h

<span style="font-size:18px;">#ifndef __VIRTUAL_FUNC_H_
#define __VIRTUAL_FUNC_H_
class Shape
{
public:
	Shape(){};
	~Shape(){};
	virtual int perimeter();
	virtual int area();      
};

class Point : public Shape
{
public:
	Point(){};
	~Point(){};
	int coordinate();
};

class Rectangle : public Shape
{
public:
	Rectangle(){};
	~Rectangle(){};
	virtual int perimeter();
	virtual int area();
};
#endif
</span>

virtual_func.cpp
<span style="font-size:18px;">#include <iostream>
#include "virtual_func.h"

int Shape::area()
{
	std::cout << "shape->area\n" << std::endl;
	return 0;
}

int Shape::perimeter()
{
	std::cout << "shape->perimeter\n" << std::endl;
	return 0;
}

int Point::coordinate()
{
	std::cout << "Pint->coordinate\n" << std::endl;
	return 0;
}

int Rectangle::area()
{
	std::cout << "rectangle->area\n" << std::endl;
	return 0;
}

int Rectangle::perimeter()
{
	std::cout << "rectangle->perimeter\n" << std::endl;
	return 0;
}
</span>

main.cpp
<span style="font-size:18px;">#include <stdio.h>
#include "virtual_func.h"

typedef int(* Fun)(void);

int main()
{
	Shape shape_a;
	Point point_a;
	Rectangle rectangle_a;
	Rectangle rectangle_b;

	Shape *pshape;
	Fun pfun = NULL;
	int *virtual_T = NULL;

	pshape = &shape_a;

	virtual_T = (int *)*(int *)(&shape_a);
	pfun = (Fun)*(virtual_T+0);
	printf("class Shape, obj shape_a: VT:0x%x VF:0x%x\n", virtual_T, pfun);
	pfun();

	virtual_T = (int *)*(int *)(&point_a);
	pfun = (Fun)*virtual_T;
	printf("class Point, obj point_a: VT:0x%x VF:0x%x\n", virtual_T, pfun);
	pfun();

	virtual_T = (int *)*(int *)(&rectangle_a);
	pfun = (Fun)*virtual_T;
	printf("class Rectangle, obj rectangle_a: VT:0x%x VF:0x%x\n", virtual_T, pfun);
	pfun();
	
	virtual_T = (int *)*(int *)(&rectangle_b);
	pfun = (Fun)*virtual_T;
	printf("class Rectangle, obj rectangle_b: VT:0x%x VF:0x%x\n", virtual_T, pfun);
	pfun();
}
</span>

運行結果:


基類Shape                                   虛函數表地址:0x400ef0                                      虛函數perimeter地址:0x400a54

派生類Point ,沒有重定義虛函數   虛函數表地址:0x401030                                      虛函數perimeter地址:0x400a54 (基類虛函數地址)

派生類Point ,重定義虛函數         虛函數表地址:0x400ed0                                       虛函數perimeter地址:0x4009c4 (Point類虛函數地址)

派生類Point ,重定義虛函數         虛函數表地址:0x400ed0 (一個類使用同一個表)  虛函數perimeter地址:0x4009c4 (Point類虛函數地址)









發佈了46 篇原創文章 · 獲贊 11 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章