我们这里将分析一下继承关系中基类和子类构造函数调用顺序,希望通过本文,能使大家对于继承的理解更加深刻。
首先回顾并讨论先有鸡还是先有蛋的问题在C++中将会是什么情况。如果编写:
- class Egg;
- class Hen
- {
- public:
- int n;
- Egg egg;
- Hen() {
- n=5;
- cout<<"Hen's con "<
- }
- };
- class Egg : public Hen
- {
- public:
- int m;
- Egg(){
- m=10;
- cout<<"Egg's con"<
- }
- };
- int main()
- {
- Egg dan;
- }
这在C++中是无法编译通过的,首先,学过编译原理的都应该知道,所有语言在编译的时候都需要确定一个类的大小。C++的编译器在编译一个类的时候,需要分析这个类的大小,而sizeof(Egg)=sizeof(Hen)+sizeof(Egg)+…,编译器无法获知其大小,自然也无法编译通过;而JAVA、C#则不同,其类的成员皆为基本类型或引用。同时,和Java、C#等语言不同,C++不能做全局优化编译(即使打开全局优化开关也没有用),它的编译是逐步向后的分析方式。C++这样做,也在编译时就防止出现先有鸡还是先有蛋而产生的矛盾。大家可以尝试在C++中尝试其他方法看编译是否能够通过。
JAVA虽然可以在Hen类的初始化时对Egg进行构造(通过new Egg()),但运行时会出现堆栈溢出的错误:
- Exception in thread "main" java.lang.StackOverflowError
- at Egg.(…)
- at Hen.(…)
- at Egg.(…)
- at Hen.(…)
- …
下面回到本文的主题。我们知道,C++和JAVA不一样,C++子类是默认调用基类构造函数的,而JAVA则需要super()。为了研究基类和子类构造函数的调用顺序问题,以上述程序为基础,我编写了这样一个测试:
- class Hen
- {
- public:
- int n;
- Hen() {
- n=5;
- cout<<"Hen's con "<
- }
- Hen(int i) {
- n=i;
- cout<<"Hen's con "<
- }
- };
- class Hen1
- {
- public:
- int x;
- Hen1() {
- x=6;
- cout<<"Hen1's con "<
- }
- };
- class Hen2
- {
- public:
- int y;
- Hen2() {
- y=7;
- cout<<"Hen2's con "<
- }
- };
- class Egg : public Hen, public Hen2, public Hen1
- {
- public:
- int m;
- Hen hen;
- Hen1 hen1;
- Hen2 hen2;
- Egg(int i) : Hen2(),Hen1(),Hen(),hen(i), hen2(), hen1(){
- m=10;
- cout<<"Egg's con"<
- }
- };
- int main()
- {
- Egg dan(1);
- }
- Output:
- Hen's con 5
- Hen2's con 7
- Hen1's con 6
- Hen's con 1
- Hen1's con 6
- Hen2's con 7
- Egg's con
通过这段程序可得如下结论:
1、基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类继承表中出现的顺序,而不是它们在成员初始化表中的顺序。如这里,是按照“class Egg : public Hen, public Hen2, public Hen1”的顺序
2、成员类对象构造函数。如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。如这里,是按照Egg声明里
Hen hen;
Hen1 hen1;
Hen2 hen2;
的顺序。
总1、2而言之,成员初始化表顺序对于构造和赋值顺序没有任何意义
3、成员类对象并不是一开始就被构造,再根据初始化表赋值,而是在调用构造函数的时候,根据传入的参数根据成员初始化表中进行一次构造,构造顺序是对象在类中声明的顺序。
【编辑推荐】