其实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类虚函数地址)