详谈C++虚函数表那回事(一般继承关系)

欢迎体验登陆、付费,欢迎留言

码到城攻-用户登录码到城攻分享但不限于IT技术经验技巧、软硬资源、所闻所见所领会等,站点提供移动阅读、文章搜索、在线留言、支付打赏、个人中心、免签支付等功能https://www.codecomeon.com/user/recommend/1/

沿途总是会出现关于C++虚函数表的问题,今天做一总结:

1.什么是虚函数表:

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,\

保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得

由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

看一个例子:

#pragma once

//C++中的虚函数的作用主要是实现了多态的机制

//通过Base的实例来得到虚函数表

class Base 
{
public:
	virtual void f() { cout << "Base::f" << endl; }
	virtual void g() { cout << "Base::g" << endl; }
	virtual void h() { cout << "Base::h" << endl; }
};

typedef void(*Fun)(void);
void Test()
{
	Base b;

	Fun pFun = NULL;

	cout << "虚函数表地址:" << (int*)(&b) << endl;
	cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;

	for (int i = 0; i < 3; ++i)   //分别输出三个函数
	{
		pFun = (Fun)*((int*)*(int*)(&b)+i);  //函数指针加一表示找下一个函数的地址
		pFun();
	}
}

结果截图:

关于代码中函数指针,已加说明。通过对对象地址的强转输出得到虚函数表的地址,即所谓的虚表指针内容:

由于是两次进函数,故地址有变,但是道理没错!虚表指针就是 _vfptr;

2.无虚函数覆盖的一般继承:(虚表)

看代码:

#pragma once

//一般继承(无虚函数覆盖)
class Base
{
public:  //三个虚函数
	virtual void f() { cout << "Base::f" << endl; }
	virtual void g() { cout << "Base::g" << endl; }
	virtual void h() { cout << "Base::h" << endl; }
};

//无覆盖 公有继承
class Derive :public Base
{
public:
	virtual void f1() { cout << "Derive::f1" << endl; }
	virtual void g1() { cout << "Derive::g1" << endl; }
	virtual void h1() { cout << "Derive::h1" << endl; }
};

void Test()
{
	Derive d;  

}

请看,在此继承中,子类没有重载任何父类函数,则子类的函数表在VS2013下是这样的:


发现没,d是一个子类对象,子类继承父类,虚函数表中尽然只有父类的虚函数地址,子类自己的虚函数都没有显示,这是不可能的啊

很显然,这可能就是编译器的BUG,但其实,真实的子类虚函数表是这样的:

可以看出:

1》虚函数按照声明顺序存放于虚表中;

2》子类虚函数在基类虚函数之后;

3.有虚函数覆盖的一般继承:(虚表)

看代码:

#pragma once

//有虚函数覆盖的一般继承
//有覆盖是必然的,否则,虚函数将失去作用

class Base
{
public:  //三个虚函数
	virtual void f() { cout << "Base::f" << endl; }
	virtual void g() { cout << "Base::g" << endl; }
	virtual void h() { cout << "Base::h" << endl; }
};

//一个覆盖 公有继承
class Derive :public Base
{
public:
	virtual void f() { cout << "Derive::f" << endl; }  //注意:这里函数覆盖了基类的相同函数;
	virtual void g1() { cout << "Derive::g1" << endl; }
	virtual void h1() { cout << "Derive::h1" << endl; }
};

void Test()
{
	Base B;
	B.f();

	Derive d; 
	Base *b = new Derive();
	b->g();
	b->f();  //调用的是子类中的f(); 实现多态的表现;
}

运行结果:

很明显基类对象调用基类的函数,子类对象,“好像”也调用子类函数,好像的原因看下面:

分析:

1》覆盖的虚函数f()存放在虚表中原来父类虚函数的位置。

2》其他函数依旧

3》这里还是同样没有显示子类虚函数存放在表中,个人坚持认为,是编译器的bug,真实展示如下:

可见:覆盖的虚函数f()存放在虚表中原来父类虚函数的位置,这也是基类对象*p访问 f() 时,访问的直接是子类的虚函数,完美的体现了多态;

有关多态,请在见另一篇博客:C++多态的实现及原理详细解析_码城的博客-CSDN博客_c++多态原理

另外有关多重继承,见下篇;

赐教!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
知识图谱关系抽取是指从自然语言文本中提取实体之间的关系,并将其示为知识图谱中的三元组形式。关系抽取是知识图谱构建的重要环节之一,其作用是自动化地从海量文本中构建知识图谱。 以下是几种常见的知识图谱关系抽取方法: 1. 基于规则的方法 基于规则的方法是指通过手工编写规则,从文本中提取实体之间的关系。这种方法需要大量的人工参与,并且需要不断更新规则以适应新的场景和语言。由于规则的复杂性和数量,这种方法通常只适用于特定的领域和任务。 2. 基于统计的方法 基于统计的方法是指使用机器学习算法从语料库中学习实体之间的关系。这种方法通常包括两个步骤:特征提取和分类器训练。特征提取是指从文本中提取与关系有关的特征,例如实体的词性、距离、共现频率等等。分类器训练是指使用机器学习算法训练一个分类器,用于判断两个实体之间是否存在关系。这种方法可以自动化地从大量文本中提取关系,并且可以适应新的场景和语言。 3. 基于神经网络的方法 基于神经网络的方法是指使用深度学习算法从文本中学习实体之间的关系。这种方法通常包括三个步骤:特征提取、示学习和分类器训练。特征提取和分类器训练与基于统计的方法类似,但示学习是神经网络方法的关键步骤。示学习是指将实体和关系示为高维向量,使得这些向量在语义上相似的实体和关系在向量空间中距离较近。这种方法在一些任务上取得了很好的效果,但需要大量的训练数据和计算资源。 以上是几种常见的知识图谱关系抽取方法,每种方法都有其优缺点和适用场景。在实际应用中,需要根据任务的具体需求和数据情况选择合适的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值