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









  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值