C++,虚函数,
我理解是为:派生类重新定义了基类虚函数,则在派生类虚函数表中,用新虚函数的地址替换基类虚函数的地址,如果派生类定义了新的虚函数,则将地址添加到表中。
但问题是,如果派生类定义的虚函数与基类虚函数名字相同,但是参数不同,这属于替换原基类版本,还是添加到表中? 展开
如果派生类定义了一个与基类同名,但具有不同参数的函数,这个函数究竟有没有被加入到派生类的虚函数表中?答案是yes。注意,不是虚函数就不来凑这个热闹了。
#include "pch.h"
#include <string>
#include <iostream>
using std::cout;
using std::endl;
class brass
{
private:
int a;
public:
brass() :a(5)
{
};
virtual void with()
{
cout << a << endl;
}
};
class brassplus : public brass
{
private:
int b;
public:
brassplus() :b(6)
{
};
virtual void with()
{
cout << "1:" << b << endl;
}
virtual void with(int i)
{
cout << "2:" << b << endl;
}
};
int main()
{
brass* a;
brassplus b;
a = (brass*)&b;
a->with();
b.with(1);
b.brass::with();
}
口说无凭,现在我们就上vs2017调试器,揭开虚函数表的面纱。首先用鼠标在编辑器左栏下断点,断点下在brassplus b;一行,然后点击“本地Windows调试器”进入调试模式。
调试器运行到断点停了下来,此时brassplus b; 这行代码是未执行的,我们将对它单步执行。
单步执行的快捷键是F11。如果想一次执行完一个过程(或称为函数),则用它旁边的那个“逐过程”调试键(F10)。
连按几次F11,直到看到派生类b的成员b被初始化为6。看到红色地址0x00aff7ec了吗,这个地址就是派生类brassplus的对象b在内存中的起始地址。
我们在“内存窗口”的地址栏输入上述地址0x00aff7ec,看到了没有,第一个值(0x00b89b40)就是“虚函数表指针”,后面还跟着已经被分别初始化为5和6的两个类成员。如果你的c语言学得比较扎实,就应该知道所谓的指针就是内存中的地址,现在我们就去“虚函数表指针”指向的地址,看一看虚函数表中究竟包含了几项。
虚函数表包含了两项,分别是0x00b813a2和0x00b8125d,它们表示虚函数在内存中的位置。到了这里,我们已经肯定了开篇的结论。但为了加深初学者的印象,我们继续往下走几步。这两项中的每一项,更准确地说,表示了一个地址,即一条跳转指令在内存中的地址,cpu执行完这条跳转指令就能顺利跳到虚函数在内存中的真正位置。现在我们到这两个地址看一看。
为了把指令弄明白,我们使用反汇编窗口,内存窗口更适于看数据。看到了吧,0x00b813a2所在位置是一条jmp跳转指令,执行了它会跳到哪里去呢?调试器已经帮我们算出来了,是0x00b82890。
在0x00b82890,我们看到了虚函数virtual void with()的实现。
同理,第二项0x00b8125d,所在处是一条jmp指令,跳转至0x00b827e0。
在0x00b827e0,我们看到了虚函数virtual void with(int i)的实现。
最后把上面说的内容总结一下,借几天前画的一张图。总之,在上机后,书中的任何错误和含糊之处就会暴露无疑,每纠正一个错误,你就比别人更前进一步。