C++中的虚函数

#include<iostream>
using namespace std;

class Base {
public:
	Base(int data)
		:ma(data)
	{}
	void show() {
		cout << "Base::show()" << endl;
	}
	void show(int) {
		cout << "Base::show(int)" << endl;
	}
protected:
	int ma;
};
class Derive :public Base {
public:
	Derive(int data)
		:Base(data)
		, mb(data)
	{}
	void show() {
		cout << "Derive::show()" << endl;
	}
protected:
	int mb;
};
int main() {
	Derive d(50);
	Base* pb = &d;
	pb->show();//call        Base::show (0FF1037h)
	pb->show(5);//call        Base::show (0FF12F3h)
	//静态绑定,编译时期就确定好调用函数

	cout << sizeof(Base) << endl;
	cout << sizeof(Derive) << endl;
	cout << typeid(pb).name() << endl;
	cout << typeid(*pb).name() << endl;

	return 0;
}
虚函数

如果类里定义了一个虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表,其中存储的内容主要是RTTI指针和虚函数的地址

Base vftable

RTTI指针:run-time type information 运行时的类型信息,指向一个保存类型的字符串"Base"

偏移量:大部分情况下是0

虚函数的地址:&Base::show() &Base::show(int)

总结:

1.当程序运行时,每一张虚函数表都会加载到.rodata(只读数据段)区

2.拥有虚函数类型的类大小和对象大小不再仅仅是成员变量的大小,其内存开始部分还要加上一个vfptr指针指向该类类型vftable虚函数表中,虚函数地址的起始部分

该类定义的所有对象,它们的vfptr都指向同一张vftable虚函数表中,虚函数地址的起始部分

3.一个类中只要有一个虚函数,就会生成vfptr,拥有再多的虚函数不会增加对象内存大小,但会增加虚函数表的大小

4.如果派生类中的方法,和基类继承来的某个方法,返回值、函数名、参数列表都相同,并且基类中该方法是virtual虚函数,那么派生类中的该方法自动处理成虚函数。这两个成员方法间的关系是『覆盖』

所以派生类Derive也会生成虚函数表

Derive vftable

RTTI指针:指向"Derive"(实际是一个RTTI类型的对象)

0

虚函数地址:&Base::show(int) &Derive::show(),Derive::show()将Base::show()覆盖了

覆盖:指虚函数表中虚函数地址的覆盖
pb->show();//Base::show()->Derive::show()

如果发现Base::show()是普通函数,那么调用的一定是基类的成员,进行静态绑定call Base::show (函数地址)

如果发现Base::show()是虚函数,那么进行动态绑定

mov eax,dword ptr[pb]//取pb内存的前四个字节,即vfptr指向虚函数地址
mov ecx,dword ptr[eax]
call ecx//虚函数的地址
pb->show(int);//虚函数,依然是动态绑定,Base::show(int)
cout << sizeof(Base) << endl;//4->8
cout << sizeof(Derive) << endl;//8->12
cout << typeid(pb).name() << endl;//class Base* 不变(静态类型语言)
cout << typeid(*pb).name() << endl;//class Base->class Derive

如果Base没有虚函数,*pb识别编译时期的类型Base

如果Base有虚函数,*pb识别运行时期的类型。访问对象中的vfptr,即派生类虚函数表中RTTI中保存的类型

偏移量

指vfptr在对象内存中的偏移量,在起始部分4个字节那么偏移量就是0

实现虚函数的条件?

1.虚函数会产生函数地址保存在vftable中,因此虚函数依赖对象,对象必须存在(vfptr->vftable->虚函数地址)

2.构造函数不能为虚函数(构造函数完成才有对象)。构造函数中调用虚函数也不会发生动态绑定(调用任何函数都是静态绑定),因为一个派生类构造过程中,先调用基类的构造函数,再调用派生类构造函数

3.static静态成员方法不能为虚函数,因为它不需要对象

4.析构函数可以成为虚函数

#include<iostream>
using namespace std;

class Base {
public:
	Base(int data)
		:ma(data)
	{
		cout << "Base()" << endl;
	}
	~Base() {
		cout << "~Base()" << endl;
	}
	void show1() {
		cout << "call Base::show()" << endl;
	}
	virtual void show2() {
		cout << "call Base::show()" << endl;
	}
protected:
	int ma;
};
class Derive :public Base {
public:
	Derive(int data)
		:Base(data)
		, mb(data)
	{
		cout << "Derive()" << endl;
	}
	~Derive() {
		cout << "Derive()" << endl;
	}
protected:
	int mb;
};
int main() {
	Base* pb = new Derive(20);
	pb->show1();//静态绑定
	pb->show2();//动态绑定
	delete pb;//打印发现派生类的析构函数没有被调用!
	/*pb->~Base();基类析构函数是普通函数,所以进行了静态绑定
	call Base::~Base()
	*/

	/*Derive d(20);
	Base* pb = &d;
	pb->show();*/

	return 0;
}

所以将基类析构函数写为虚函数

基类析构函数是虚函数,派生类析构函数自动成为虚函数

virtual ~Base() {
		cout << "~Base()" << endl;
	}
/*将基类析构函数写为虚函数后,派生类析构函数自动成为虚函数
派生类和基类的析构函数视为同名函数,派生类析构函数会覆盖基类析构函数,但是释放完派生类内存后还是会再去释放基类内存
*/
基类析构函数必须写为虚函数的情况:

基类的指针/引用指向堆上new出来的派生类对象,delete(基类指针)调用析构函数必须发生动态绑定,否则会使派生类析构函数没有被调用到

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值