其實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類虛函數地址)