IT评测·应用市场-qidao123.com
标题:
两道关于虚函数的c++面试题
[打印本页]
作者:
诗林
时间:
2025-1-5 06:13
标题:
两道关于虚函数的c++面试题
由于工作需要到场了员工雇用工作,于是给来到场面试的职员预备了有点难度的两道题,由于这两道题并不是入门题目,所以一般也不会出给面试职员,但是如果面试职员表现不错,想看看他对c++的理解是否充足深入,则会出这种有难度的题目。
第一题,难度并不算太大,只要对虚函数,虚函数表有肯定的理解,照旧能做出这道题的。
class B
{
public:
virtual void mf() { std::cout << "B::mf" << std::endl; }
void mb()
{
std::cout << "B::mb" << std::endl;
mf();
}
};
class D : public B
{
public:
void mf() { std::cout << "D::mf" << std::endl; }
void mb() { std::cout << "D::mb" << std::endl; }
};
int main(int argc, char* argv[])
{
D x;
B* pB = &x;
pB->mf();
pB->mb();
return 0;
}
复制代码
读者可以考虑一下这道题的答案,文章末尾会公布答案。
第二道题难度较大,说实话我自己都做不出来,这道题我还不太明白原理,目前也没有筹划出题给面试者,不过后来查阅资料,算是明白了其原理,这里先贴出题目,后面再给出分析和答案,读者可以先挑衅一下。
using namespace std;
class A
{
virtual void g()
{
cout << "A::g" << endl;
}
private:
virtual void f()
{
cout << "A::f" << endl;
}
};
class B : public A
{
void g()
{
cout << "B::g" << endl;
}
virtual void h()
{
cout << "B::h" << endl;
}
};
typedef void(*Fun)(void);
void main()
{
B b;
Fun pFun;
for (int i = 0; i < 3; i++)
{
pFun = (Fun) * ((int*)*(int*)(&b) + i);
pFun();
}
}
复制代码
这道题的关键是对指针和虚函数表的理解,表到底是怎么存在的,存的内容是什么,如果你没有充足的理解,这道题肯定没办法做出来。
=====================我是富丽的分割线==================
先发表第一题的答案:
第二题的答案,先直接给出结果吧,如果你能直接看明白也是极好的。
如果你看不明白,下面我把我查到的相干资料给你看看,或许就豁然开朗了。虽然下面的分析并不是针对这道题的,但是对于理解这道题非常有资助。
前言(原文地址:
C++虚函数及虚函数表分析
)
虚函数表
对C++了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表办理了继续、覆盖的题目,包管其容真实反应实际的函数。如许,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为紧张了,它就像一个舆图一样,指明了实际所应该调用的函数。
这里我们偏重看一下这张虚函数表。C++的编译器应该是包管虚函数表的指针存在于对象实例中最前面的位置(这是为了包管取到虚函数表的有最高的性能——如果有多层继续或是多重继续的情况下)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
听我扯了那么多,我可以感觉出来你如今可能比以前更加晕头转向了。 不要紧,下面就是实际的例子,相信聪明的你一看就明白了。
假设我们有如许的一个类:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
复制代码
按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:
int test1(){
typedef void (*Fun)();
Base b;
Fun pFun=NULL;
cout<<"虚函数表地址:"<<(int*)&b<<endl;
cout<<"虚函数表-第一个函数地址:"<<(int*)*(int*)&b<<endl;
cout<<"虚函数表-第二个函数地址:"<<(int*)*(int*)&b+1<<endl;
cout<<"虚函数表-第三个函数地址:"<<(int*)*(int*)&b+2<<endl;
for(int i=0;i<3;i++)
{
pFun=(Fun)*((int*)*(int*)&b+i);
pFun();
}
return 0;
}
复制代码
实际运行结果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
通过这个示例,我们可以看到,我们可以通过强行把&b转成int*,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
复制代码
看完上面的分析,你是否有种豁然开朗的感觉呢,如果没有,估计你对于虚函数表所存储的东西仍旧存疑,这里我进一步表明一下。
由于原题目中,B是A的子类,根据A中函数的定位以及B中函数的界说,我们可以推测虚函数表的存储内容如下:
指针g()
指针f()
指针h()
而函数指针的大小与普通指针的大小一致,我们的重点是怎样取得虚函数表的地址。
在上面的分析中,我们可以通过强行把&b转成int*,取得虚函数表的地址,然后,再次取值就可以得到第一个虚函数的地址了,原文应该是错别字,这里我用红字纠正一下,而通过指针+1,则可以取得其他虚函数的地址,这也是这道题的核心部分,理解了这个,这道题自然就能做出来了。
接待交流与讨论。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/)
Powered by Discuz! X3.4