虚基类构造函数调用顺序
含有虚基类的对象的构造顺序

含有虚基类的对象的构造顺序含有虚基类的对象的构造顺序在C++中,虚基类是指被继承的基类,通过虚拟继承可以避免派生类对基类的多次继承,从而消除了由多次继承所带来的二义性。
在具体实现中,含有虚基类的对象的构造顺序成为了一个比较复杂且需要重点关注的问题。
在本文中,我们将探讨含有虚基类的对象的构造顺序,并深入了解其中的相关概念和实现细节。
一、什么是虚基类1.1 虚基类的定义在进行类的多重继承时,如果某个类作为基类被多个派生类继承,就有可能出现同一个基类在派生类中存在多份拷贝的情况。
为了解决这一问题,C++引入了虚基类的概念。
通过在继承的基类前加上关键字virtual来表示虚基类,从而确保不论这个虚基类在继承体系中出现多少次,最终在派生类中只有一份拷贝。
1.2 虚基类的作用虚基类的引入避免了由多重继承带来的二义性,确保在派生类中只有一份共同的基类成员。
虚基类的存在使得类的继承关系更清晰,更符合逻辑。
二、含有虚基类的对象的构造顺序2.1 构造函数的调用顺序在含有虚基类的对象构造过程中,构造函数的调用顺序是需要特别注意的。
由于虚基类的特殊性,它的构造函数与普通的基类构造函数有所不同,因此构造函数的调用顺序也会在这种情况下发生变化。
2.2 构造函数的顺序在含有虚基类的对象构造过程中,构造函数的调用顺序如下:(1)最深层派生类的构造函数。
(2)虚基类构造函数。
(3)直接基类构造函数。
2.3 示例分析假设有如下的继承关系:```cppclass A {};class B : virtual public A {};class C : virtual public A {};class D : public B, public C {};```在这种情况下,对象D的构造顺序是:(1)D的构造函数。
(2)B的构造函数。
(3)A的构造函数。
(4)C的构造函数。
(5)A的构造函数。
三、个人观点和总结在C++中,虚基类的使用为多重继承带来了一种解决方案,避免了多次继承所带来的问题。
C++习题答案-4

第4章:继承与派生类[4_1]答:类的继承方式有public(公有继承)、protected(保护继承)和private(私有继承)3种,不同的继承方式导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。
(1)基类中的私有成员无论哪种继承方式,基类中的私有成员不允许派生类继承,即在派生类中是不可直接访问的。
(2)基类中的公有成员当类的继承方式为公有继承时,基类中的所有公有成员在派生类中仍以公有成员的身份出现;当类的继承方式为私有继承时,基类中的所有公有成员在派生类中都以私有成员的身份出现;当类的继承方式为保护继承时,基类中的所有公有成员在派生类中都是以保护成员的身份出现。
(3)基类中的保护成员当类的继承方式为公有继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现;当类的继承方式为私有继承时,基类中的所有保护成员在派生类中都是以私有成员的身份出现:当类的继承方式为保护继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现。
[4_2]答:派生类不能直接访问基类的私有成员,但是可以通过基类提供的公有成员函数间接地访问基类的私有成员。
[4_3]答:保护成员可以被派生类的成员函数访问,但是对于外界是隐藏起来的,外部函数不能访问它。
因此,为了便于派生类的访问,可以将基类私有成员中需要提供给派生类访问的成员定义为保护成员。
C++规定,派生类对于保护成员的继承与公有成员的继承很相似,也分为两种情况:若为公有派生,则基类中的保护成员在派生类中也为保护成员;若为私有派生,则基类中的保护成员在派生类中成为私有成员。
[4_4]答:通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
[4_5]答:当基类的构造函数没有参数或没有显示定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。
C++构造函数初始化顺序详解

C++构造函数初始化顺序详解1.构造函数、析构函数与拷贝构造函数介绍构造函数1.构造函数不能有返回值2.缺省构造函数时,系统将⾃动调⽤该缺省构造函数初始化对象,缺省构造函数会将所有数据成员都初始化为零或空3.创建⼀个对象时,系统⾃动调⽤构造函数析构函数1.析构函数没有参数,也没有返回值。
不能重载,也就是说,⼀个类中只可能定义⼀个析构函数2.如果⼀个类中没有定义析构函数,系统也会⾃动⽣成⼀个默认的析构函数,为空函数,什么都不做3.调⽤条件:1.在函数体内定义的对象,当函数执⾏结束时,该对象所在类的析构函数会被⾃动调⽤;2.⽤new运算符动态构建的对象,在使⽤delete运算符释放它时。
拷贝构造函数拷贝构造函数实际上也是构造函数,具有⼀般构造函数的所有特性,其名字也与所属类名相同。
拷贝构造函数中只有⼀个参数,这个参数是对某个同类对象的引⽤。
它在三种情况下被调⽤:1.⽤类的⼀个已知的对象去初始化该类的另⼀个对象时;2.函数的形参是类的对象,调⽤函数进⾏形参和实参的结合时;3.函数的返回值是类的对象,函数执⾏完返回调⽤者。
【代码】复制代码代码如下:/*version: 1.0author: hellogiserdate: 2014/9/25*/#include "stdafx.h"#include <iostream>using namespace std;class point{private:int x, y;public:point(int xx = 0, int yy = 0){x = xx;y = yy;cout << "Constructor" << endl;}point(const point &p){x = p.x;y = p.y;cout << "Copy Constructor" << endl;}~point(){cout << "Destructor" << endl;}int get_x(){return x;}int get_y(){return y;}};void f(point p){// copy constructorcout << p.get_x() << " " << p.get_y() << endl;// destructor}point g(){point a(7, 33); //constructorreturn a; // copy constructor temp object}void test(){point a(15, 22); // constructorpoint b(a); //(1) copy constructorcout << b.get_x() << " " << b.get_y() << endl; // 15 22f(b);// (2) copy constructorb = g(); // (3) copy constructorcout << b.get_x() << " " << b.get_y() << endl; // 7 33}int main(){test();return 0;}/*ConstructorCopy Constructor15 22Copy Constructor15 22DestructorConstructorCopy ConstructorDestructorDestructor7 33DestructorDestructor*/2. 继承关系中构造函数执⾏顺序(1)任何虚拟基类(virtual)的构造函数按照它们被继承的顺序构造;(2)任何⾮虚拟基类(non-virtual)的构造函数按照它们被继承的顺序构造;(3)任何成员对象(data member)的构造函数按照它们声明的顺序调⽤;(4)类⾃⼰的构造函数(self)。
派生类构造对象时,构造函数执行顺序

派⽣类构造对象时,构造函数执⾏顺序先调⽤基类构造函数,再调⽤派⽣类构造函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18class Base{public:Base() { cout << "Base()" << endl;} };class Derived : public Base{public:Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出:Base()Derived()先调⽤基类构造函数,再调⽤对象成员的构造函数,最后调⽤派⽣类构造函数.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19class Base{public:Base() { cout << "Base()" << endl;} };class Derived : public Base{public:Base b1, b2;Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出:Base()Base()Base()Derived()先调⽤基类构造函数,再调⽤对象成员的构造函数(对象声明顺序,不是继承顺序,更不是初始化成员列表顺序),最后调⽤派⽣类构造函数.5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25};class Base2{public:Base2() { cout << "Base2()" << endl;} };class Derived : public Base1, public Base2 {public:Base1b1;Base2b2;Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出: Base1() Base2() Base1() Base2() Derived()1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25class Base1{public:Base1() { cout << "Base1()" << endl;} };class Base2{public:Base2() { cout << "Base2()" << endl;} };class Derived : public Base1, public Base2 {public:Base2b2;Base1b1;Derived() { cout << "Derived()" << endl;} };int main(){Derived d;return 0;}输出:Base1()Base2()Base2()Base1()Derived()先调⽤虚基类构造函数,再调⽤其他基类构造函数,然后调⽤对象成员构造,最后调⽤派⽣类构造函数.5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25};class Base2{public:Base2() { cout << "Base2()" << endl;}};class Derived : public Base1,virtual public Base2 {public:Base2b2;Base1b1;Derived() { cout << "Derived()" << endl;}};int main(){Derived d;return 0;}Base2()Base1()Base2()Base1()Derived()总结:先基类,再派⽣类;先虚基类,再其他基类;先对象成员,再派⽣类;顺序是声明顺序,⽽不是成员初始化列表顺序。
C++构造函数和析构函数的调用顺序

C++构造函数和析构函数的调⽤顺序1、构造函数的调⽤顺序基类构造函数、对象成员构造函数、派⽣类本⾝的构造函数2、析构函数的调⽤顺序派⽣类本⾝的析构函数、对象成员析构函数、基类析构函数(与构造顺序正好相反)3、特例局部对象,在退出程序块时析构静态对象,在定义所在⽂件结束时析构全局对象,在程序结束时析构继承对象,先析构派⽣类,再析构⽗类对象成员,先析构类对象,再析构对象成员4、例⼦#include <iostream>using namespace std;class Base1{public:Base1(void){cnt++;cout<<"Base1::constructor("<<cnt<<")"<<endl;}~Base1(void){cnt--;cout<<"Base1::deconstructor("<<cnt + 1<<")"<<endl;}private:static int cnt;};int Base1::cnt = 0;class Base2{public:Base2(int m){num = m; cout<<"Base2::constructor("<<num<<")"<<endl;}~Base2(void){cout<<"Base2::deconstructor("<<num<<")"<<endl;}private:int num;};class Example{public:Example(int n){num = n; cout<<"Example::constructor("<<num<<")"<<endl;}~Example(void){cout<<"Example::deconstructor("<<num<<")"<<endl;}private:int num;};class Derived:public Base1, public Base2{public:Derived(int m, int n):Base2(m),ex(n){cnt++;cout<<"Derived::constructor("<<cnt<<")"<<endl;}~Derived(void){cnt--;cout<<"Derived::deconstructor("<<cnt+1<<")"<<endl;}private:Example ex;static Example stex; //Example::constructor(1) //不能输出static int cnt;};int Derived::cnt = 0;Derived ge_a(1,2); // Base1::constructor(1)// Base2::constructor(1)// Example::constructor(2)// Derived::constructor(1)static Derived gs_b(3,4); // Base1::constructor(2)// Base2::constructor(3)// Example::constructor(4)// Derived::constructor(2)int main(void){cout << "---------start---------" << endl;Derived d(5,6); // Base1::constructor(3) // Base2::constructor(5)// Example::constructor(6)// Derived::constructor(3)Derived e(7,8); // Base1::constructor(4) // Base2::constructor(7)// Example::constructor(8)// Derived::constructor(4)cout << "----------end----------" << endl;//Derived e(7,8) 析构// Derived::deconstructor(4)// Example::deconstructor(8)// Base2::deconstructor(7)// Base1::deconstructor(4)//Derived d(5,6) 析构// Derived::deconstructor(3)// Example::deconstructor(6)// Base2::deconstructor(5)// Base1::deconstructor(3)return 0;}//static Derived gs_b(3,4) 析构// Derived::deconstructor(2)// Example::deconstructor(4)// Base2::deconstructor(3)// Base1::deconstructor(2)//Derived ge_a(1,2) 析构// Derived::deconstructor(1)// Example::deconstructor(2)// Base2::deconstructor(1)// Base1::deconstructor(1)//static Example stex 析构//Example::deconstructor(1) //不能输出#include <iostream>using namespace std;class A{public:A(){cout<<"A::constructor"<<endl;};~A(){cout<<"A::deconstructor"<<endl;}; };class B{public:B(){cout<<"B::constructor"<<endl;};~B(){cout<<"B::deconstructor"<<endl;}; };class C : public A{public:C(){cout<<"C::constructor"<<endl;};~C(){cout<<"C::deconstructor"<<endl;}; private:// static B b;B b;};class D : public C{public:D(){cout<<"D::constructor"<<endl;};~D(){cout<<"D::deconstructor"<<endl;}; };int main(void){C* pd = new D();delete pd;return 0;}/* Output----->B bA::constructorB::constructorC::constructorD::constructorC::deconstructorB::deconstructorA::deconstructor ----->static B b A::constructor C::constructor D::constructor C::deconstructor A::deconstructor */。
C++第一版答案

第一章:面向对象程序设计概述[1_1]什么是面向对象程序设计?面向对象程序设计是一种新型的程序设计范型。
这种范型的主要特征是:程序=对象+消息。
面向对象程序的基本元素是对象,面向对象程序的主要结构特点是:第一:程序一般由类的定义和类的使用两部分组成,在主程序中定义各对象并规定它们之间传递消息的规律。
第二:程序中的一切操作都是通过向对象发送消息来实现的,对象接受到消息后,启动有关方法完成相应的操作。
面向对象程序设计方法模拟人类习惯的解题方法,代表了计算机程序设计新颖的思维方式。
这种方法的提出是软件开发方法的一场革命,是目前解决软件开发面临困难的最有希望、最有前途的方法之一。
[1_2]什么是类?什么是对象?对象与类的关系是什么?在面向对象程序设计中,对象是描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。
对象可以认为是:数据+操作在面向对象程序设计中,类就是具有相同的数据和相同的操作的一组对象的集合,也就是说,类是对具有相同数据结构和相同操作的一类对象的描述。
类和对象之间的关系是抽象和具体的关系。
类是多个对象进行综合抽象的结果,一个对象是类的一个实例。
在面向对象程序设计中,总是先声明类,再由类生成对象。
类是建立对象的“摸板”,按照这个摸板所建立的一个个具体的对象,就是类的实际例子,通常称为实例。
[1_3]现实世界中的对象有哪些特征?请举例说明。
对象是现实世界中的一个实体,其具有以下一些特征:(1)每一个对象必须有一个名字以区别于其他对象。
(2)需要用属性来描述它的某些特性。
(3)有一组操作,每一个操作决定了对象的一种行为。
(4 )对象的操作可以分为两类:一类是自身所承受的操作,一类是施加于其他对象的操作。
例如:雇员刘名是一个对象对象名:刘名对象的属性:年龄:36 生日:1966.10.1 工资:2000 部门:人事部对象的操作:吃饭开车[1_4]什么是消息?消息具有什么性质?在面向对象程序设计中,一个对象向另一个对象发出的请求被称为“消息”。
继承与派生类答案
继承与派生类知识要点1.掌握继承和派生的定义,派生类的定义方法。
(1)掌握继承的两种类型:单继承和多继承。
(2)掌握private,public,protected三种继承方式的特点。
继承方式决定了基类中的成员在派生类中的属性。
三种继承方式的共同点:基类的private成员在派生类中不可见。
区别:对于私有继承,基类的public、protected成员在派生类中作为private成员;对于公有继承,基类的public、protected成员在派生类中访问属性不变;对于保护继承,基类的public、protected成员在派生类中作为protected成员。
(3)掌握派生类中的构造函数和析构函数的使用。
基类的构造函数和析构函数不能继承,所以必要时在派生类中定义自己的构造函数和析构函数。
派生列的构造函数完成基类中新增数据成员和基类数据成员的初始化,基类数据成员的初始化通过基类构造函数来实现。
(4)掌握派生类的同名覆盖规则。
(5)掌握赋值兼容规则。
基类对象可以使用公有派生类对象来代替,包括:派生类对象可以赋值给基类对象;派生类对象可以初始化基类对象的引用;基类类型指针可以指向派生类对象。
2.掌握多重继承的概念、定义方法、多重继承派生类构造函数的执行顺序。
派生类构造函数的执行顺序是先执行所有基类的构造函数(顺序按照定义派生类时指定的各基类顺序),在执行对象成员所在类的构造函数(顺序按照他们在类中的声明顺序),最后执行派生类构造函数体中的内容。
3.掌握虚基类的概念和定义方法。
在多重继承中,如果多条继承路径上有一个公共的基类,则在这些路径的汇合点上的派生类会产生来自不同路径的公共基类的多个拷贝,如果用virtual把公共基类定义成虚基类,则只会保留公共基类的一个拷贝。
典型例题分析与解答例题1:下列对派生类的描述中,()是错误的。
A.一个派生类可以作为另一个派生类的基类B.派生类至少有一个基类C.派生类的成员除了它自己的成员外,还包含了它的基类成员D.派生类中继承的基类成员的访问权限到派生类保持不变答案:D分析:一个派生类可以作为另一个派生类的基类。
面向对象程序设计课后习题答案
面向对象程序设计课后习题答案第一章:面向对象程序设计概述[1_1]什么是面向对象程序设计?面向对象程序设计是一种新型的程序设计范型。
这种范型的主要特征是:程序=对象+消息。
面向对象程序的基本元素是对象,面向对象程序的主要结构特点是:第一:程序一般类的定义和类的使用两部分组成,在主程序中定义各对象并规定它们之间传递消息的规律。
第二:程序中的一切操作都是通过向对象发送消息来实现的,对象接受到消息后,启动有关方法完成相应的操作。
面向对象程序设计方法模拟人类习惯的解题方法,代表了计算机程序设计新颖的思维方式。
这种方法的提出是软件开发方法的一场革命,是目前解决软件开发面临困难的最有希望、最有前途的方法之一。
[1_2]什么是类?什么是对象?对象与类的关系是什么?在面向对象程序设计中,对象是描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。
对象可以认为是:数据+操作在面向对象程序设计中,类就是具有相同的数据和相同的操作的一组对象的集合,也就是说,类是对具有相同数据结构和相同操作的一类对象的描述。
类和对象之间的关系是抽象和具体的关系。
类是多个对象进行综合抽象的结果,一个对象是类的一个实例。
在面向对象程序设计中,总是先声明类,再类生成对象。
类是建立对象的“摸板”,按照这个摸板所建立的一个个具体的对象,就是类的实际例子,通常称为实例。
[1_3]现实世界中的对象有哪些特征?请举例说明。
对象是现实世界中的一个实体,其具有以下一些特征:每一个对象必须有一个名字以区别于其他对象。
需要用属性来描述它的某些特性。
有一组操作,每一个操作决定了对象的一种行为。
对象的操作可以分为两类:一类是自身所承受的操作,一类是施加于其他对象的操作。
例如:雇员刘名是一个对象对象名:刘名对象的属性:年龄:36 生日:工资:2000 部门:人事部对象的操作:吃饭开车[1_4]什么是消息?消息具有什么性质?在面向对象程序设计中,一个对象向另一个对象发出的请求被称为“消息”。
虚基类的概念和定义
虚基类的概念和定义1. 概念定义虚基类(Virtual Base Class)是C++中的一个重要概念,用于解决多继承中的菱形继承问题。
菱形继承指的是一个派生类同时继承自两个不同的基类,而这两个基类又共同继承自一个公共的基类。
如果不加以处理,会导致派生类中存在两份公共基类的成员,从而产生二义性。
虚基类通过在派生类中使用关键字virtual来声明,它具有以下特点: - 被声明为虚基类的成员不会被派生类直接继承; - 虚基类的构造函数由最底层派生类负责调用; - 虚基类只会在内存中存在一份副本。
2. 重要性虚基类的引入解决了多继承中菱形继承问题,避免了二义性的发生。
它在面向对象编程中发挥着重要作用: - 避免冗余数据:虚基类可以确保在派生层次结构中只存在一份公共数据,避免了数据冗余和浪费。
- 解决二义性:通过将公共数据放在虚基类中,派生类只需要从虚基类继承一份公共数据,避免了二义性的发生。
- 灵活组合:多继承可以实现更灵活的组合关系,虚基类使得多继承更加可控和可靠。
3. 应用场景虚基类主要应用于多继承的场景中,特别是存在菱形继承的情况。
下面通过一个示例来说明虚基类的应用:#include<iostream>using namespace std;class Animal {public:int age;};class Mammal : virtual public Animal {public:void eat() {cout << "Mammal is eating." << endl;}};class Bird : virtual public Animal {public:void fly() {cout << "Bird is flying." << endl;}};class Platypus : public Mammal, public Bird {public:void swim() {cout << "Platypus is swimming." << endl;}};int main() {Platypus p;p.age = 5; // 访问公共数据成员agep.eat(); // 调用Mammal类的成员函数eatp.fly(); // 调用Bird类的成员函数flyp.swim(); // 调用Platypus类自身的成员函数swim}在上述示例中,Animal类是一个虚基类,它被Mammal和Bird两个派生类虚继承。
C++中构造函数的执行顺序小结
C++中构造函数的执行顺序
1首先,如果类中有静态成员,则先执行静态成员的构造函数。
注意,如果静态成员只是在类定义中声明了,而没有实现,是不用构造的。
必须初始化后才执行其构造函数。
2接下来,如果该类有直接或间接的虚基类,则先执行虚基类的构造函数。
3最后,如果该类有其他基类,则按照它们在继承声明列表中出现的次序,分别执行它们的构造函数,但构造过程中,不再执行它们的虚基类的构造函数。
补充:
1虚基类
虚基类的声明是在派生类的定义过程中进行的,其语法形式为:
class派生类名:virtual继承方式基类名
上述语句声明基类为派生类的虚基类。
声明了虚基类后,虚基类的成员在进一步的派生过程中和派生类一起维护同一个内存数据副本。
2一般虚函数
声明语法:virtual函数类型函数名();
3纯虚函数和抽象类
纯虚函数的声明格式:virtual函数类型函数名(参数表)=0;
抽象类是带有纯虚函数的类。
抽象类不能实例化。
抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以定义自己的对象,而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
class Base1 {
public:
Base1(void) {
cout << "class Base1" << endl;
}
};
class Base2 {
public:
Base2(void) {
cout << "class Base2" << endl;
}
};
class Level1:virtual public Base2,public Base1 { public:
Level1(void) {
cout << "class Level1" << endl;
}
};
class Level2:public Base2,virtual public Base1 { public:
Level2(void) {
cout << "class Level2" << endl;
}
};
class Leaf:public Level1,virtual public Level2 {
public:
Leaf(void) {
cout << "class Leaf" << endl;
}
};
int main(void) {
Leaf obj;
return 0;
}
不看下面的分析,大家觉得输出结果应该是怎么样的?如果没有虚拟继承,也许能很快说出答案。
现在来分析一下在多继承和虚拟继承的情况下,构造函数的调用顺序是怎么样的。
编译器按照直接基类在声明中的顺序,来检查虚拟基类的出现情况。
在我们的例子中,Level1首先被检查,然后是Level2。
每个继承子
树按照深度优先的顺序被检查。
即,查找从树根类开始,然后向下移动。
如对子树Level1而言,先检查Base2,然后是Base1,再到Level1。
但是在虚拟继承中,基类构造函数的查找顺序只是为了知道虚拟继承的情况而已,基类构造函数的调用顺序和查找顺序是不一样的,那应该遵循什么样的一个原则呢?
遵循两个原则,而且按顺序优先满足:1 先调用完所以基类,再调用子类;2 先调用虚拟基类,再调用非虚拟基类。
一旦调用了虚拟基类的构造函数,则非虚拟基类构造函数就按照声明的顺序被调用。
所以针对我们这个例子,因为声明类Leaf的顺序是先Level1后Level2,所以先看看Level1这棵子树吧。
由于Level1虚拟继承Base2,非虚拟继承Base1,所以应该先调用Base2,但是这之后不能接着调用Level1这棵子树的Base1,因为其他子树还有虚拟继承。
现在来看看Level2这棵子树吧,由于Level2虚拟继承Base1,非虚拟继承Base2,所以先调用Base1,后Base2。
既然Level2的两个基类都调用了,并且Level2也是一个虚拟基类,所以现在应该调用Level2的构造函数了。
这样,Level2这棵子树的构造函数都调用完了,又回到Level1这棵
子树了。
接着刚才,由于Level1的Base2已经调用了,接着应该调用Base1了,之后是Level1。
当Leaf的所有直接和间接的基类都调用之后,最后是Leaf构造函数了。
经过以上分析,大家应该知道构造函数的调用顺序了吧,所以该程序的输出应该是
Base2
Base1
Base2
Level2
Base1
Level1
Leaf。