您的当前位置:首页正文

C++——类继承以及类初始化顺序

2024-05-08 来源:汇智旅游网
C++——类继承以及类初始化顺序

对于类以及类继承, ⼏个主要的问题:

1) 继承⽅式: public/protected/private继承.

这是c++搞的, 实际上继承⽅式是⼀种允许⼦类控制的思想. ⼦类通过public继承, 可以把基类真实还原, ⽽private继承则完全把基类屏蔽掉. 这种屏蔽是相对于对象层⽽⾔的, 就是说⼦类的对象完全看不到基类的⽅法, 如果继承⽅式是private的话, 即使⽅法在基类中为public的⽅法.但继承⽅式并不影响垂直⽅向的访问特性, 那就是⼦类的函数对基类的成员访问是不受继承⽅式的影响的.

⽐较(java): java是简化的, 其实可认为是c++中的public继承. 实在没必要搞private/protected继承, 因为如果想控制,就直接在基类控制就好了.

2) 对象初始化顺序: c++搞了个成员初始化列表, 并确明确区分初时化跟赋值的区别. c++对象的初始化顺序是:(a) 基类初始化(b) 对象成员初时化(c) 构造函数的赋值语句

举例:

假设 class C : public A, public B {D d;//}

则初始化的顺序是A, B, D, C的构造函数.

这⾥基类的初始化顺序是按照声明的顺序, 成员对象也是按照声明的顺序. 因此 c(int i, int j) : B(i), A(j) {} //这⾥成员初始化列表的顺序是不起作⽤的;

析构函数的顺序则刚好是调过来, 构造/析构顺序可看作是⼀种栈的顺序;

⽐较(java): java中初始化赋值是⼀回事. ⽽且对基类的构造函数调⽤必须显⽰声明, 按照你⾃⼰写的顺序. 对成员对象, 也叫由你初始化.没有什么系统安排的顺序问题, 让你感觉很舒服;

3) 多继承问题: c++⽀持多继承, 会导致\"根\"不唯⼀. ⽽java则没有该问题;

此外c++没有统⼀的root object, java所有对象都存在Object类使得很多东西很⽅便. ⽐如公共的seriall, persistent等等.

4) 继承中的重载: c++中, 派⽣类会继承所有基类的成员函数, 但构造函数, 析构函数除外.

这意味着如果B 继承A, A(int i)是基类构造函数, 则⽆法B b(i)定义对象. 除⾮B也定义同样的构造函数. c++的理由是, 假如派⽣类定义了新成员, 则基类初始化函数⽆法初始化派⽣类的所有新增成员.

⽐较(java): java中则不管, 就算有新增对象基类函数没有考虑到, ⼤不了就是null, 或者你⾃⼰有缺省值. 也是合理的.

5) 继承中的同名覆盖和⼆义性: 同名覆盖的意思是说, 当派⽣类跟基类有完全⼀样的成员变量或者函数的时候, 派⽣类的会覆盖基类的.

类似于同名的局部变量覆盖全局变量⼀样. 但被覆盖的基类成员还是可以访问的.如B继承A, A, B都有成员变量a,则B b, b.a为访问B的a, b.A::a则为访问基类中的a. 这对于成员函数也成⽴.

但需要注意的是, 同名函数必须要完全⼀样才能覆盖. int func(int j)跟int func(long j)其实是不⼀样的. 如果基类,派⽣类有这两个函数, 则不会同名覆盖.

最重要的是, 两者也不构成重载函数. 因此假如A有函数int func(int j), B有函数int func(long j). 则B的对象b.func(int)调⽤为错误的. 因为B中的func跟它根本就不构成重载.

同名覆盖导致的问题是⼆义性. 假如C->B=>A, 这⾥c继承B, B继承A. 假如A, B都有同样的成员fun, 则C的对象c.fun存在⼆义性. 它到底是指A的还是B的fun呢?

解决办法是⽤域限定符号c.A::fun来引⽤A的fun.

另外⼀个导致⼆义性的是多重继承. 假设B1, B2都继承⾃B, D则继承B1, B2. 那么D有两个B⽽产⽣⼆义性.

这种情况的解决办法是⽤虚基类. class B1 : virtual public B, class B2:virtual public B, D则为class D : public B1, public B2. 这样D中的成员只包含⼀份B的成员使得不会产⽣⼆义性.

⽐较(java). java中是直接覆盖. 不给机会这么复杂, 还要保存基类同名的东西. 同名的就直接覆盖, 没有同名的就直接继承.

虚基类的加⼊, 也影响到类的初始化顺序. 原则是每个派⽣类的成员化初始化列表都必须包含对虚基类的初始化.

最终初始化的时候, 只有真正实例化对象的类的调⽤会起作⽤. 其它类的对虚基类的调⽤都是被忽略的. 这可以保证虚基类只会被初始化⼀次.

c++没有显式接⼝的概念, 我觉得是c++语⾔的败点. 这也是导致c++要⽀持组件级的重⽤⾮常⿇烦. 虽然没有显式的接⼝, 但c++中的纯虚函数以及抽象类的⽀持, 事实上是等同于接⼝设施的. 当⼀个类中, 所有成员函数都是纯虚函数, 则该类其实就是接⼝.java c++

接⼝ 类(所有成员函数都是纯虚函数)抽象类 类(部分函数是虚函数)对象类 对象类

C++构造函数调⽤顺序

1. 如果类⾥⾯有成员类,成员类的构造函数优先被调⽤;

2. 创建派⽣类的对象,基类的构造函数优先被调⽤(也优先于派⽣类⾥的成员类);

3. 基类构造函数如果有多个基类,则构造函数的调⽤顺序是某类在类派⽣表中出现的顺序⽽不是它们在成员初始化表中的顺序;

4. 成员类对象构造函数如果有多个成员类对象,则构造函数的调⽤顺序是对象在类中被声明的顺序⽽不是它们出现在成员初始化表中的顺序;

5. 派⽣类构造函数,作为⼀般规则派⽣类构造函数应该不能直接向⼀个基类数据成员赋值⽽是把值传递给适当的基类构造函数,否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供⼀组适当的基类构造函数)举例:

#include #include class A {public:A{…}~A{…}};

class B {public:B{…}~B{…}};

class D {public:D{…}~D{…}};

class E {public:E{…}~E{…}};

class C :public A,public B {public:C{…}private:D objD_; E objE_;~C{…}}

int main(void){

C test; return 0; }

运⾏结果是:

A{…}//派⽣表中的顺序B{…}

D{…}//成员类的构造函数优先被调⽤E{…}C{…}~C{…}~E{…}~D{…}~B{…}~A{…}

从概念上来讲,构造函数的执⾏可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段:初始化阶段:

所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中;

计算阶段:

⼀般⽤于执⾏构造函数体内的赋值操作。

下⾯的代码定义两个结构体,其中Test1有构造函数,拷贝构造函数及赋值运算符,为的是⽅便查看结果,Test2是个测试类,它以Test1的对象为成员,我们看⼀下Test2的构造函数是怎么样执⾏的。class Test1{

Test1() //⽆参构造函数{

cout << \"Construct Test1\" << endl ;}

Test1(const Test1& t1) //拷贝构造函数{

cout << \"Copy constructor for Test1\" << endl ;this->a = t1.a ;}

Test1& operator = (const Test1& t1) //赋值运算符{

cout << \"assignment for Test1\" << endl ;this->a = t1.a ;return *this;}

int a ;};

struct Test2{

Test1 test1 ;Test2(Test1 &t1){

test1 = t1 ;}};

调⽤代码:Test1 t1 ;Test2 t2(t1) ;输出:

Construct Test1Construct Test1

assignment for Test1解释⼀下:

第⼀⾏输出对应调⽤代码中第⼀⾏,构造⼀个Test1对象;

第⼆⾏输出对应Test2构造函数中的代码,⽤默认的构造函数初始化对象test1 // 这就是所谓的初始化阶段;第三⾏输出对应Test2的赋值运算符,对test1执⾏赋值操作 // 这就是所谓的计算阶段;

为什么使⽤初始化列表?

初始化类的成员有两种⽅式,⼀是使⽤初始化列表,⼆是在构造函数体内进⾏赋值操作。

主要是性能问题,对于内置类型,如int, float等,使⽤初始化类表和在构造函数体内初始化差别不是很⼤,但是对于类类型来说,最好使⽤初始化列表,为什么呢?

由下⾯的测试可知,使⽤初始化列表少了⼀次调⽤默认构造函数的过程,这对于数据密集型的类来说,是⾮常⾼效的。同样看上⾯的例⼦,我们使⽤初始化列表来实现Test2的构造函数。struct Test2{

Test1 test1 ;

Test2(Test1 &t1):test1(t1){}}

使⽤同样的调⽤代码,输出结果如下:Construct Test1

Copy constructor for Test1

第⼀⾏输出对应 调⽤代码的第⼀⾏

第⼆⾏输出对应Test2的初始化列表,直接调⽤拷贝构造函数初始化test1,省去了调⽤默认构造函数的过程。所以⼀个好的原则是,能使⽤初始化列表的时候尽量使⽤初始化列表;

除了性能问题之外,有些时场合初始化列表是不可或缺的,以下⼏种情况时必须使⽤初始化列表:1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表⾥⾯;

2.引⽤类型,引⽤必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表⾥⾯;

3.没有默认构造函数的类类型,因为使⽤初始化列表可以不必调⽤默认构造函数来初始化,⽽是直接调⽤拷贝构造函数初始化;

struct Test1 {Test1(int a):i(a){}int i;};

struct Test2 {Test1 test1 ;};

以上代码⽆法通过编译,因为Test2的构造函数中 test1 = t1 这⼀⾏实际上分成两步执⾏:1. 调⽤Test1的默认构造函数来初始化test1;

由于Test1没有默认的构造函数,所以1 ⽆法执⾏,故⽽编译错误。正确的代码如下,使⽤初始化列表代替赋值操作,struct Test2 {Test1 test1 ;

Test2(int x):test1(x){}}

成员变量的初始化顺序: 先定义的成员变量先初始化

成员是按照他们在类中出现的顺序进⾏初始化的,⽽不是按照他们在初始化列表出现的顺序初始化的,看代码:struct foo {int i ;int j ;

foo(int x):i(x), j(i){}; // ok, 先初始化i,后初始化j};

再看下⾯的代码:struct foo {int i ;int j ;

foo(int x):j(x), i(j){} // i值未定义};

这⾥i的值是未定义的因为虽然j在初始化列表⾥⾯出现在i前⾯,但是i先于j定义,所以先初始化i,⽽i由j初始化,此时j尚未初始化,所以导致i的值未定义。

⼀个好的习惯是,按照成员定义的顺序进⾏初始化。

对于全局对象(global object),VC下是先定义先初始化,但C++标准没做规定。

全局对象默认是静态的,全局静态(static)对象必须在main()函数前已经被构造,告知编译器将变量存储在程序的静态存储区,由C++ 编译器startup代码实现。

startup代码是更早于程序进⼊点(main 或WinMain)执⾏起来的代码,它能做些像函数库初始化、进程信息设⽴、I/O stream产⽣等等动作,以及对static对象的初始化动作(也就是调⽤其构造函数);在main()函数结束后调⽤它的析构函数。----------------派⽣类对象的初始化构造#include using namespace std;

class A { private: int a; public:

A(int x):a(x) { cout <class B: A { private: int b, c; const int d; A x, y; public:

B(int v): b(v),y(b+2),x(b+1),d(b),A(v) { c=v;

cout <int main(void) {

B z(1); return 0; } /*

1.定义⼀个派⽣类对象,⾸先初始化它的基类成员(基类部分),即调⽤基类的构造函数(如果是多继承,则按继承的先后顺序调⽤基类的构造函数)

2.基类部分初始化完之后,初始化派⽣类部分,派⽣类的成员初始化依赖它的声明顺序,并不依赖它的初始化列表的顺序初始化派⽣类成员,总结来说:就是派⽣类成员的初始化,依赖它的声明顺序⽽不是依赖初始化列表的顺序。3.调⽤派⽣类的构造函数,可以理解为就是执⾏派⽣类构造函数的函数体⽽已

4.特别注意:但是,请注意:上⾯两点调⽤构造函数或者其他的参数传递是参考初始化列表给出的参数的

详细解释:

⾸先:B z(1);则依据1,调⽤基类的构造函数,但是这⾥不知道该调⽤基类的哪个构造函数,因为基类有默认的构造函数(即没有参数)和你定义的A(int x)这个构造函数,所以,编译器要进⾏选择。

依据4,参考到初始化列表b(v),y(b+2),x(b+1),d(b),A(v)中有A(v),所以编译器选择调⽤你定义的构造函数A(int x),所以打印输出a的值,输出1,然后,依据2,派⽣类⾃⾝定义的部分是按它的定义顺序初始化的,即按下⾯这个顺序,b,c,d,x,y.int b, c; const int d; A x, y;

所以,依据4,分别参考初始化列表b(v),y(b+2),x(b+1),d(b),A(v) 给出的参数信息,可知道初始化b,使⽤b(v),b被初始化为1。然后,初始化c,由于初始化列表中没有指定c的初始化,所以暂时c不被初始化,然后初始化d,根据初始化列表中的d(b),d被初始化为b的值,即为1。然后初始化A类对象x和y,依据初始化列表中的x(b+1)初始化x,由于b的值为1,所以即相当于x(2),给除了⼀个参数2,则调⽤你定义的构造函数A(int x),打印输出类A的x对象中的a的值,即输出2,同理,依据y(b+2)初始化y,打印输出3。最后,依据3,调⽤派⽣类构造函数,即B(int v) { c=v;

cout <这时,直接忽略初始化列表了,执⾏这个派⽣类的构造函数,那么执⾏函数体c=v;则把那个没初始化的c被赋值为v的值,即c的值为1。最后打印输出b和c的值所以再输出两个1。综上所述:输出1 2 3 1 1 1

⼀、C++成员变量初始化

1、普通的变量:⼀般不考虑啥效率的情况下 可以在构造函数中进⾏赋值。考虑⼀下效率的可以再构造函数的初始化列表中进⾏

2、static 静态变量(本地化数据和代码范围):

static变量属于类所有,⽽不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有⼀个。在这种性质上理解,有点类似于全局变量的唯⼀性。

函数体内static变量的作⽤范围时该函数体,不同于auto变量,该变量内存只被分配⼀次,因此其值在下次调⽤时维持上次的值。在模块内的static全局变量可以被模块内所有函数访问,但不能被模块外的其它函数访问。

在模块内的static函数只可被这⼀模块内的其他函数调⽤,这个函数的适⽤范围被限制在声明它的模块内。在类中的static成员变量属于整个类所拥有,对类的所有对象只有⼀份拷贝。

在类中的static成员函数属于整个类所拥有,这个函数不接受this指针,因⽽只能访问类的static成员变量。

3、const 常量变量:

const常量需要在声明的时候即初始化。因此需要在变量创建的时候进⾏初始化。⼀般采⽤在构造函数的初始化列表中进⾏。4、Reference 引⽤型变量:

引⽤型变量和const变量类似。需要在创建的时候即进⾏初始化。也是在初始化列表中进⾏。但需要注意⽤Reference类型。5、字符串初始化

char str[10] = \"HELLO\";

结尾会被编译器⾃动加上结尾符'/0',编译的时候可以看到它最后是'',ASC码值是0;

\"HELLO\"只有5个字符,加上编译器⾃动添加的'/0',也就是会初始化数组的前6个元素,剩下有元素会被全部初始化为'/0',这个要注意哦;char str[] = \"HELLO\";

编译器⾃动为后⾯的字符串分配⼤⼩并加'/0';

char str[] = {'H','E','L','L','O','/0'};

编译器会根据字符串⼤⼩分配空间,可是不会⾃动分配'/0',所以结尾的时候要⾃⼰加上'/0';char *str = \"HELLO\";

把指向字符串的指针给定义好的字符指针;

1)⽤构造函数确保初始化

对于⼀个空类,编译器会⾃动声明4个默认函数:构造函数、拷贝构造函数、赋值函数、析构函数(如果不想使⽤⾃动⽣成,就应该明确拒绝),这些⽣成的函数都是public且inline的。

2)为什么构造函数不能有返回值(1)假设有⼀个类C,有如下定义:

构造函数的调⽤之所以不设返回值,是因为构造函数的特殊性决定的。从基本语义⾓度来讲,构造函数返回的应当是所构造的对象。否则,我们将⽆法使⽤临时对象:void f(int a) {...} //(1)

void f(const C& a) {...} //(2)f(C()); //(3),究竟调⽤谁?

对于(3),我们希望调⽤的是(2),但如果C::C()有int类型的返回值,那么究竟是调(1)好呢,还是调⽤(2)好呢。于是,我们的重载体系,乃⾄整个的语法体系都会崩溃。

这⾥的核⼼是表达式的类型。⽬前,表达式C()的类型是类C。但如果C::C()有返回类型R,那么表达式C()的类型应当是R,⽽不是C,于是便会引发上述的类型问题。

(2)只是C++标准规定了构造/析构/⾃定义类型转换符不可以指定返回类型。 但你不能据此就说它们没有返回类型。

(3)本⼈的意见是构造函数是有返回值的,返回的就是新构造的对象本⾝,但是不能指定返回类型,因为你⽤这个类的构造函数表明就是返回这个类的⼀个对象,没有必要指定返回类型,即使是指定也必须是指定类本⾝的返回类型,这就多次⼀举了吧。

3)为什么构造函数不能为虚函数

虚函数调⽤的机制,是知道接⼝⽽不知道其准确对象类型的函数,但是创建⼀个对象,必须知道对象的准确类型;当⼀个构造函数被调⽤时,它做的⾸要事情之⼀就是初始化它的VPTR来指向VTABLE。#include

using namespace std;class Base {private:int i;public:

Base(int x) {i = x;}};

class Derived : public Base {private:int i;public:

Derived(int x, int y) {i = x;}

void print() {

cout << i + Base::i << endl;}};

int main(){

Derived A(2,3);A.print();return 0;}

⾸先,是访问权限问题,⼦类中直接访问Base::i是不允许的,应该将⽗类的改为protected或者public(最好⽤protected)

其次,统计⽗类和⼦类i的和,但是通过⼦类构造函数没有对⽗类变量进⾏初始化;此处编译会找不到构造函数,因为⼦类调⽤构造函数会先找⽗类构造函数,但是没有2个参数的,所以可以在初始化列表中调⽤⽗类构造函数

最后个问题,是单参数的构造函数,可能存在隐式转换的问题,因为单参数构造函数,和拷贝构造函数形式类似,调⽤时很可能会发⽣隐式转换,应加上explicit关键字#include using namespace std;class Base {protected:int i;public:

explicit Base(int x) {i = x;}};

class Derived : public Base {private:int i;public:

Derived(int x, int y):Base(x) {i = y;}

void print() {

cout << i + Base::i << endl;}};

int main(){

Derived A(2,3);A.print();return 0;}

初始化类的成员有两种⽅式,⼀是使⽤初始化列表,⼆是在构造函数体内进⾏赋值操作。

主要是性能问题,对于内置类型,如int, float等,使⽤初始化类表和在构造函数体内初始化差别不是很⼤,但是对于类类型来说,最好使⽤

初始化列表,为什么呢?

由下⾯的测试可知,使⽤初始化列表少了⼀次调⽤默认构造函数的过程,这对于数据密集型的类来说,是⾮常⾼效的。

初始化列表

1)使⽤初始化列表提⾼效率class Student {public:

Student(string in_name, int in_age) {name = in_name;age = in_age;}

private :string name;int age;};

在构造函数中,是对name进⾏赋值,不是初始化,⽽string对象会先调⽤它的默认构造函数,再调⽤string类(貌似是basic_string类)的赋值构造函数;class Student {public:

Student(string in_name, int in_age):name(in_name),age(in_age) {}private :string name;int age;};

在初始化的时候调⽤的是string的拷贝构造函数,⽽上例会调⽤两次构造函数,从性能上会有不⼩提升;

有的情况下,是必须使⽤初始化列表进⾏初始化的:const对象、引⽤对象初始化列表初始顺序#include using namespace std;class Base {public:

Base(int i) : m_j(i), m_i(m_j) {}Base() : m_j(0), m_i(m_j) {}int get_i() const {return m_i;}

int get_j() const {return m_j;}private:int m_i;int m_j;};

int main(){

Base obj(98);

cout << obj.get_i() << endl << obj.get_j() << endl;return 0;}

输出为⼀个随机数和98,为什么呢?

因为对于初始化列表⽽⾔,对成员变量的初始化,是严格按照声明次序,⽽不是在初始化列表中的顺序进⾏初始化,如果改为赋值初始化则不会出现这个问题,

当然,为了使⽤初始化列表,还是严格注意声明顺序吧,⽐如先声明数组⼤⼩,再声明数组这样。

C++构造函数初始化按下列顺序被调⽤:

⾸先,任何虚拟基类的构造函数按照它们被继承的顺序构造;其次,任何⾮虚拟基类的构造函数按照它们被继承的顺序构造;再有,任何成员对象的构造函数按照它们声明的顺序调⽤;最后,类⾃⼰的构造函数。

#include using namespace std;class OBJ1{public:

OBJ1(){ cout<<\"OBJ1\\n\"; }};

class OBJ2{public:

OBJ2(){ cout<<\"OBJ2\\n\";}}

class Base1{public:

Base1(){ cout<<\"Base1\\n\";}}

class Base2{public:

Base2(){ cout <<\"Base2\\n\"; }};

class Base3{public:

Base3(){ cout <<\"Base3\\n\"; }};

class Base4{public:

Base4(){ cout <<\"Base4\\n\"; }};

class Derived :public Base1, virtual public Base2,public Base3, virtual public Base4//继承顺序{public:

Derived() :Base4(), Base3(), Base2(),Base1(), obj2(), obj1(){//初始化列表cout <<\"Derived ok.\\n\";}

protected:

OBJ1 obj1;//声明顺序OBJ2 obj2;};

int main(){

Derived aa;//初始化cout <<\"This is ok.\\n\";return 0;}

结果:

Base2 //虚拟基类按照被继承顺序初始化Base4 //虚拟基类按照被继承的顺序

Base1 //⾮虚拟基类按照被继承的顺序初始化Base3 //⾮虚拟基类按照被继承的顺序 OBJ1 //成员函数按照声明的顺序初始化OBJ2 //成员函数按照声明的顺序 Derived ok. This is ok.

重复继承(repeated inheritance):⼀个派⽣类多次继承同⼀个基类.但C++并不允许⼀个派⽣类直接继承同⼀个基类两次或以上.重复继承的两个种类:复制继承和共享继承

重复继承中的共享继承:通过使⽤虚基类,使重复基类在派⽣对象实例中只存储⼀个副本.

涉及到共享继承的派⽣类对象的初始化次序规则① 最先调⽤虚基类的构造函数.

② 其次调⽤普通基类的构造函数,多个基类则按派⽣类声明时列出的次序从左到右.③ 再次调⽤对象成员的构造函数,按类声明中对象成员出现的次序调⽤.④ 最后执⾏派⽣类的构造函数.析构函数执⾏次序与其初始化顺序相反.

例:/*

//Program: repeated inheritance, virtual base class test//Author: Ideal//Date: 2006/3/28*/

#include

class baseA{

public:baseA(){

cout << \"BaseA class. \" << endl; }};

class baseB{

public:baseB(){

cout << \"BaseB class. \" << endl; }};

class derivedA:public baseB, virtual public baseA{

public:derivedA(){

cout << \"DerivedA class. \" << endl;}};

class derivedB:public baseB, virtual public baseA{

public:derivedB(){

cout << \"DerivedB class. \" << endl;}};

class Derived:public derivedA, virtual public derivedB{

public:Derived(){

cout << \"Derived class. \" << endl;}};

void main(){

Derived obj;cout << endl;}

result:

=========

BaseA class.BaseB class.DerivedB class.BaseB class.DerivedA class.Derived class.

————————————————————————————————————————分析:各类的类层次结构关系为

①Derived从derivedA和虚基类derivedB共同派⽣⽽来

②derivedA从baseB和虚基类baseA派⽣⽽来, derivedB从baseB和虚基类baseA派⽣⽽来执⾏顺序(构造函数)

由第①层关系,根据规则可得顺序为derivedB,derivedA,Derived.

然后,对于derivedB,同样根据规则更深⼊分析得到的顺序是baseA,baseB,derivedB.

对于derivedA,值得注意的是derivedA和derivedB都经过虚基类baseA的派⽣,所以根据只存储⼀个副本的处理⽅法,由于baseA在derivedB中已经被初始化过,derivedA中将不必再进⾏初始化,所以执⾏的将是baseB, derivedA.最后就是Derived了.

综合可得对应构造函数顺序: baseA(), baseB(), derivedB(); baseB(), derivedA(); Derived();

Copyright © 2019- 版权所有