虚函数
(ORZ,又是一系列的问题……)
###第一问 virtual关键字的用法?
这个没什么好说的了,基本就是书上的答案,我答的时候分为两类来答:
-
虚继承(虚拟基类继承),是为了避免多继承带来的重复继承问题,所有直接继承类必须声明为虚继承模式2。
<!-- lang: cpp --> class child:virtual public parent{ };
-
多态,这个就是重点要的考点了。这里的多态是运行时多态,即它允许函数调用与函数体之间的联系在运行时才建立,即在运行时才决定如何动作。
<!-- lang: cpp --> class parent { public: virtual void print(){ } } class child: public parent { public: virtual void print(){ } } int main(void) { parent *p = new child(); p->print(); return 0; }
基本就是上面这个用法,但是……诶,其实这个是最表面的东西,Baidu的面试是一定会问得比较深入的。如果这个我答不上来也就罢了,可是偏偏我就懂一些用法,深层次的就跪了……
###第二问 编译器是怎样去实现多态的?
虚函数是通过一张虚函数表来实现的,在对象中存储的是一个指针,指向虚函数表。在这个表中,存储了虚函数的地址,解决了继承、覆盖的问题,保证反应实际调用函数。通常,这个指针从对象的首地址开始存放。虚函数表的最后一个节点存放的是1 或0, 1代表后面还有虚函数表(多重继承),0代表结束。
图就不画了,皓哥这里画的很好了3。
主要是两个特点:
- 虚函数按照其声明顺序放于表中。
- 父类的虚函数在子类的虚函数前面。
可以分为**一般继承(有虚函数覆盖),多重继承(无虚函数覆盖),多重继承(有虚函数覆盖)**这三类去讨论。
一般继承(有虚函数覆盖),这种情况中,覆盖的函数会放在原来父类虚函数的位置,其他的函数顺序按照上面所说的特点来分配,这里只有一个父类,只有一个虚函数表指针。
多重继承(无虚函数覆盖),因为有多个父类,所以实际上是有多个虚函数表指针的,子类的成员函数在第一个声明的父类虚函数表后面。
多重继承(有虚函数覆盖),结合上述两者,有多个虚函数表指针,又替换原来父类的位置。
具体的验证的话,可以参照皓哥放在最后面的程序,很清晰3。
来一道简单的考题:
<!-- lang: cpp -->
typedef void(*Fun)(void);
class parent
{
private:
virtual void foo1(){
printf("parent:foo1\n");
}
public:
virtual void foo2(){
printf("parent:foo2\n");
}
};
class child:public parent
{
public:
void foo2(){
printf("child:foo2\n");
}
virtual void foo3(){
printf("child:foo3\n");
}
};
int main()
{
child c;
Fun p_fun;
for(int i = 0;i<3;i++){
p_fun = (Fun)*((int*)*(int*)(&c)+i);
p_fun();
}
return 0;
}
结果:
第三问
构造函数能否为虚函数?
用过virtual的都知道,析构函数是要virtual的,因为析构函数的调用是在子类中隐含的。因此,如果调用了析构函数,那么先调用子类的,然后调用父类的。
那么,究竟构造函数能否为虚函数呢? 答案是不能的,首先,c++中,父类的构造函数一定是先于子类调用的,假设将构造函数定义为virtual,那么,代表了该构造函数需要调用子类的具体实现,然而,子类构造的时候又需要先调用父类的构造函数,所以,显然是构造函数不能为虚函数。
第四问
<!-- lang: cpp -->
class parent
{
};
int main()
{
printf("%d\n",sizeof(parent));
}
求输出的答案?
这道题是从《剑指Offer》上看来的,答案是1(g++),因为空类型是会占用一定的地址空间的,由编译器决定具体的大小。
<!-- lang: cpp -->
class parent
{
public:
virtual void foo(){
}
};
int main()
{
printf("%d\n",sizeof(parent));
}
有了前面的虚函数铺垫,相信答案就比较明显了,在32位的机器上就是4。
再来一段:
<!-- lang: cpp -->
class father
{
private:
virtual void foo1(){
printf("father:foo1\n");
}
public:
virtual void foo2(){
printf("father:foo2\n");
}
};
class mother
{
private:
virtual void foo4(){
}
};
class child:public father, public mother
{
public:
void foo2(){
printf("child:foo2\n");
}
virtual void foo3(){
printf("child:foo3\n");
}
};
int main()
{
printf("%d\n",sizeof(child));
return 0;
}
这里的答案就是8了,两条虚地址表记录。
扩展问(内存对齐初步)
<!-- lang: cpp -->
class parent{
public:
int a;
char b;
};
int main()
{
printf("%d\n",sizeof(parent));
return 0;
}
答案是8,这个问题以后再深入讨论。
上一篇: 纯虚函数
下一篇: 如何实现 php 虚拟方法