虚函数

39 篇文章 0 订阅
18 篇文章 0 订阅

目录

一、什么是虚函数(virtual function)

二、虚函数的实现机制——虚函数表(virtual table)

2.1 虚函数表

2.2 单继承下的虚函数表

2.2.1 无覆盖

2.2.2 有覆盖

2.3 多继承下的虚函数表

2.3.1 无覆盖

2.3.2 有覆盖

2.4 构造函数不能是虚函数,析构函数可以是虚函数

2.4.1 构造函数不能是虚函数的原因

2.4.2 析构函数可以是虚函数

参考资料:


一、什么是虚函数(virtual function)

虚函数用来实现C++的多态性(polymorphism)。

当基类的指针指向派生类实例时,可以用基类指针调用派生类中的成员函数。如果基类指针指向不同的派生类,则该指针可以调用同一个函数实现不同的逻辑。这种机制可以让基类指针具有多种形态。示例如下:

/*********************************
 * 虚函数
*/
#include <iostream>
#include <cstdlib>

using namespace std;

class Base {
public:
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
};

class Derive : public Base {
public:
	virtual void funcA() { std::cout << "Derive::funcA" << std::endl; }
};

class Derive2 : public Base {
public:
	virtual void funcA() { std::cout << "Derive2::funcA" << std::endl; }
};

int main(int argc, char** argv) {
	Derive d;
	Base* bptr = &d;
	bptr->funcA();
	Derive2 d2;
	bptr = &d2;
	bptr->funcA();
	system("pause");
	return 0;
}

运行结果:

二、虚函数的实现机制——虚函数表(virtual table)

2.1 虚函数表

虚函数机制是通过虚函数表实现的。虚函数表是指每个包含虚函数的类中,都存在的一个函数地址数组。虚函数表位于对象实例的最前面,后面是成员变量。即:

代码示例:

/*********************************
 * 虚函数表
 */
#include <iomanip>
#include <iostream>
#include <cstdlib>

using namespace std;

class Base {
public:
	Base() :
		m_item1(0),
		m_item2(0) {

	}
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
	virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
	virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
	int m_item1;
	int m_item2;
};

int main(int argc, char** argv) {
	std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
	Base base;
	// 对象base的地址
	int* bAddress = (int*)&base;
	std::cout << "base address: 0x" << std::setw(8) << bAddress << std::endl;

	// 对象base虚函数表的地址
	int* vtptr = (int*)*(bAddress + 0);
	std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
	// funcA的地址
	int* pfuncA = (int *)*(vtptr + 0);
	std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
	// funcB的地址
	int* pfuncB = (int *)*(vtptr + 1);
	std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
	// funcC的地址
	int* pfuncC = (int *)*(vtptr + 2);
	std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
	system("pause");
	return 0;
}

在visual studio中可以看到输出局部变量的情况:

控制台输出为:

2.2 单继承下的虚函数表

2.2.1 无覆盖

新定义子类Derived,继承自Base,不覆盖Base中的虚函数,如下

 

示例代码如下:

/*********************************
 * 虚函数表,单继承,无覆盖
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>

using namespace std;

class Base {
public:
	Base() :
		m_item1(0),
		m_item2(0) {

	}
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
	virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
	virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
	int m_item1;
	int m_item2;
};

class Derive : public Base {
public:
	Derive() : m_item3(0) {}
	virtual void funcD() { std::cout << "Derive::funcD" << std::endl; }
private:
	int m_item3;
};

int main(int argc, char** argv) {
	std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
	Derive d;
	// 对象d的地址
	int* dAddress = (int*)&d;
	std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
	// 对象d虚函数表的地址
	int* vtptr = (int*)*(dAddress + 0);
	std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
	
	// funcA的地址
	int* pfuncA = (int *)*(vtptr + 0);
	std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
	// funcB的地址
	int* pfuncB = (int *)*(vtptr + 1);
	std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
	// funcC的地址
	int* pfuncC = (int *)*(vtptr + 2);
	std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
	// funcD的地址
	int* pfuncD = (int *)*(vtptr + 3);
	std::cout << "pfuncD address: 0x" << std::setw(8) << pfuncD << std::endl;

	system("pause");
	return 0;
}

2.2.2 有覆盖

代码如下:

/*********************************
 * 虚函数表-单继承有覆盖
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>

using namespace std;

class Base {
public:
	Base() :
		m_item1(0),
		m_item2(0) {

	}
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
	virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
	virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
	int m_item1;
	int m_item2;
};
class Derive : public Base {
public:
	Derive() : m_item3(0) {}
	virtual void funcA() { std::cout << "Derive::funcA" << std::endl; }
private:
	int m_item3;
};

int main(int argc, char** argv) {
	std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
	Derive d;
	// 对象d的地址
	int* dAddress = (int*)&d;
	std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
	// 对象d虚函数表的地址
	int* vtptr = (int*)*(dAddress + 0);
	std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
	
	// funcA的地址
	int* pfuncA = (int *)*(vtptr + 0);
	std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
	// funcB的地址
	int* pfuncB = (int *)*(vtptr + 1);
	std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
	// funcC的地址
	int* pfuncC = (int *)*(vtptr + 2);
	std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;

	system("pause");
	return 0;
}

运行结果如下:

可以看到d中基类的虚函数funcA已经被继承类覆盖。这种现象叫做重写(override)基类中的funcA函数被隐藏了。

这时,如果想要继续调用积累的funcA改如何实现呢?

d.Base::funcA();

2.3 多继承下的虚函数表

2.3.1 无覆盖

派生类Derive同时继承基类 Base,Base2,则继承类中有两个虚函数表,如下:

代码如下:

/*********************************
 * 虚函数表
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>

using namespace std;

class Base {
public:
	Base() :
		m_item1(0),
		m_item2(0) {

	}
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
	virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
	virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
	int m_item1;
	int m_item2;
};

class Base2 {
	virtual void funcD() { std::cout << "Base2::funcD" << std::endl; }
};

class Child :public Base, public Base2 {
	
};


int main(int argc, char** argv) {
	std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
	Child d;
	// 对象d的地址
	int* dAddress = (int*)&d;
	std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
	// 对象d虚函数表的地址
	int* vtptr = (int*)*(dAddress + 0);
	std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
	
	// funcA的地址
	int* pfuncA = (int *)*(vtptr + 0);
	std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
	// funcB的地址
	int* pfuncB = (int *)*(vtptr + 1);
	std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
	// funcC的地址
	int* pfuncC = (int *)*(vtptr + 2);
	std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;

	system("pause");
	return 0;
}

2.3.2 有覆盖

代码:

/*********************************
 * 虚函数表
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>

using namespace std;

class Base {
public:
	Base() :
		m_item1(0),
		m_item2(0) {

	}
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
	virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
	virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
	int m_item1;
	int m_item2;
};

class Base2 {
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
	virtual void funcD() { std::cout << "Base2::funcD" << std::endl; }
};

class Child :public Base, public Base2 {
	virtual void funcD() { std::cout << "Child::funcD" << std::endl; }
};

int main(int argc, char** argv) {
	std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
	Child d;
	// 对象d的地址
	int* dAddress = (int*)&d;
	std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
	// 对象d虚函数表的地址
	int* vtptr = (int*)*(dAddress + 0);
	std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
	
	// funcA的地址
	int* pfuncA = (int *)*(vtptr + 0);
	std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
	// funcB的地址
	int* pfuncB = (int *)*(vtptr + 1);
	std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
	// funcC的地址
	int* pfuncC = (int *)*(vtptr + 2);
	std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
	
	system("pause");
	return 0;
}

 

基类Base和基类Base2中都有的函数,应该如何调用?

/*********************************
 * 虚函数表
*/
#include <iomanip>
#include <iostream>
#include <cstdlib>

using namespace std;

class Base {
public:
	Base() :
		m_item1(0),
		m_item2(0) {

	}
	virtual void funcA() { std::cout << "Base::funcA" << std::endl; }
	virtual void funcB() { std::cout << "Base::funcB" << std::endl; }
	virtual void funcC() { std::cout << "Base::funcC" << std::endl; }
private:
	int m_item1;
	int m_item2;
};

class Base2 {
public:
	virtual void funcA() { std::cout << "Base2::funcA" << std::endl; }
	virtual void funcD() { std::cout << "Base2::funcD" << std::endl; }
};

class Child :public Base, public Base2 {
public:
	virtual void funcA() { std::cout << "Child::funcA" << std::endl; }
	virtual void funcD() { std::cout << "Child::funcD" << std::endl; }
};
class Derive : public Base {
public:
	Derive() : m_item3(0) {}
	virtual void funcA() { std::cout << "Derive::funcA" << std::endl; }
private:
	int m_item3;
};

int main(int argc, char** argv) {
	std::cout.setf(ios::hex);//设置十六进制输出 ios::dec 十进制 ios::oct八进制
	Child d;
	// 对象d的地址
	int* dAddress = (int*)&d;
	std::cout << "d address 0x" << std::setw(8) << dAddress << std::endl;
	// 对象d虚函数表的地址
	int* vtptr = (int*)*(dAddress + 0);
	std::cout << "virtual table address: 0x" << std::setw(8) << vtptr << std::endl;
	
	// funcA的地址
	int* pfuncA = (int *)*(vtptr + 0);
	std::cout << "pfuncA address: 0x" << std::setw(8) << pfuncA << std::endl;
	// funcB的地址
	int* pfuncB = (int *)*(vtptr + 1);
	std::cout << "pfuncB address: 0x" << std::setw(8) << pfuncB << std::endl;
	// funcC的地址
	int* pfuncC = (int *)*(vtptr + 2);
	std::cout << "pfuncC address: 0x" << std::setw(8) << pfuncC << std::endl;
	d.funcA();
	d.Base::funcA();
	d.Base2::funcA();
	Base* ptr = (Base*)&d;
	ptr->funcA();
	
	system("pause");
	return 0;
}

2.4 构造函数不能是虚函数,析构函数可以是虚函数

2.4.1 构造函数不能是虚函数的原因

从内存角度:构造函数的作用是用于类对象的初始化。而虚函数的动态绑定是通过虚函数表实现的。调用构造函数之前系统只是对类对象分配了内存,并没有对内存进行初始化,此时内存空间上不存在虚函数表,也就无法通过虚函数表调用到虚函数。

从必要性角度:虚函数的意义是动态绑定。但是当定义派生类对象时,是依次调用构造函数,基类的构造函数——> 一级派生类的构造函数——> ……——>派生类的构造函数。不需要动态绑定。

 

2.4.2 析构函数可以是虚函数

基类指针或者引用,可以实现对虚函数的动态绑定。当我们用积累指针或者引用析构一个对象时,无法判断当前指针或者引用指向的是基类还是派生类,如果析构函数不是虚函数,就会直接调用基类的析构函数,这时候就会发生问题。因此需要析构函数是虚函数,才能通过动态绑定,调用到应该调用的析构函数。

参考资料:

https://jocent.me/2017/08/07/virtual-table.html

https://blog.csdn.net/linpengbin/article/details/50810584

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值