文章目录
- 问题一(有关菱形虚拟继承)
- 问题二(有关多态)
- 总结
问题一(有关菱形虚拟继承)
前言:该问题涉及菱形虚拟继承的问题,如果不知道菱形虚拟继承是什么的,可以看看博主的另一篇博客,链接如下:
C++——深究继承
这篇博客中含有从语法和底层讲解菱形虚拟继承的部分
#include<iostream>
using namespace std;
class A
{
public:
A(const char* a) { cout << a << endl; }
~A() {}
};
class B : virtual public A
{
public:
B(const char* a, const char* b)
:A(a)
{
cout << b << endl;
}
};
class C:virtual public A
{
public:
C(const char* a, const char* c)
:A(a)
{
cout << c << endl;
}
};
class D : public B, public C
{
public :
D(const char* a, const char* b, const char* c, const char* d)
:B(a, b)
,C(a, c)
,A(a)
{
cout << d << endl;
}
};
int main()
{
D* p = new D("class A", "class B", "class C", "class D");
delete p;
return 0;
}
上面这段代码输出的结果是什么呢?我们知道,初始化列表的执行顺序是按照成员在类中的声明顺序执行的,那么在这道题中是否是先初始化B成员,再初始化C成员呢?我们首先来看一下答案,再说结论。
答案是,先初始化A成员,然后按顺序初始化B和C,最后才初始化D自己的成员。
那么,为什么会这样呢?答案是由于这里B和C使用的是虚拟继承,所以在D中将只会有一份A成员放在D对象的最后,也就是说D对象中的A成员是独立于B,C的。
因此,C++语法规定在使用初始化列表初始化的时候,将会优先初始化A对象(菱形继承最上面的基类对象),然后再根据继承顺序分别调用中间基类的初始化,但要注意的是,在中间基类的初始化过程中,对其基类的初始化列表将不起作用,也就是不会执行!(这点从打印结果就可以看出,并没有打印三次A类构造),并且如果D对象的初始化列表没有初始化A类对象将会报错。
从这里就可以看出,虚继承是非常复杂的,所以我们一定要谨慎使用虚继承,能不用就不用!
问题二(有关多态)
//下面的代码输出结果是什么?
#include<iostream>
using namespace std;
class A
{
public:
virtual void func(int val = 1) { cout << "A->" << val << endl; }
virtual void test() { func(); }
};
class B : public A
{
public :
void func(int val = 0) { cout << "B->" << val << endl; }
};
int main()
{
B* p = new B;
p->test();
return 0;
}
这题有两个考点,一个是多态,另一个是一个非常坑的细节。
首先是第一点,p指向的是一个B类型的对象,我们首先来分析一下该题B类型对象的虚表,如下:
__vfptr |
---|
(B对象重写)B::func() |
A::test() |
用p调用test()之后,test()内部又用this指针调用func(),而由于该this指针是B类型的指针,所以调用的应该是重写的func()。
到了这一步之后,肯定很多人会觉得答案是B->0,但是答案真的是这个吗?
我们先来看答案:
没错,答案是输出B->1!!!是不是很惊讶,那么为什么输出的是这个呢?
这是因为虚函数重写的底层只重写了函数的实现,函数接口仍然用的是基类的接口,所以其缺省函数仍然是1!
总结
通过这两道题,我们更能发现c++这门语言的奇妙了,因此,在实际使用过程中,才更应该加倍小心!