zoukankan      html  css  js  c++  java
  • C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容 【转】 参考度4.6星

    源地址:http://blog.csdn.net/daheiantian/article/details/6438782

    ------------------------------------------------------------------------------------------------------

    一、本文目的与说明

        1. 本文目的:理清在各种继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序执行内容

        2. 说明:虽然复制构造函数属于构造函数的一种,有共同的地方,但是也具有一定的特殊性,所以在总结它的性质时将它单独列出来了。

        3. 单继承、多继承、虚继承,既然都属于继承,那么虽然有一定的区别,但还是相同点比较多。如果放在一块讲,但为了将内容制作成递进的,就分开了,对相同点进行重复,(大量的复制粘贴哈),但在不同点进行了标注。 
            注意:三块内容是逐步递进的 
                         如果你懂虚函数,那么单继承和多继承那块你就可以不看; 
                         如果你懂多继承,那单继承你就不要看了,至于虚继承就等你懂虚继承再回来看吧; 
                         如果你只懂单继承,那你就只看单继承就好。

    二、基本知识

        1. 对于一个空类,例如;

    1. class EmptyClass{};  

            虽然你没有声明任何函数,但是编译器会自动为你提供上面这四个方法。

    1. class EmptyClass {  
    2. public:  
    3.     EmptyClass();                        //  默认构造函数  
    4.     EmptyClass(const EmptyClass &rhs);    //  复制构造函数  
    5.     ~EmptyClass();                       // 析构函数  
    6.     EmptyClass& operator=(const EmptyClass &rhs);    //  赋值运算符  
    7. }  

            对于这四个方法的任何一个,你的类如果没有声明,那么编译器就会自动为你对应的提供一个默认的。(在《C++ primer》中,这个编译器自动提供的版本叫做“合成的***”,例如合成的复制构造函数)当然如果你显式声明了,编译器就不会再提供相应的方法。

        2. 合成的默认构造函数执行内容:如果有父类,就先调用父类的默认构造函数。

        2. 合成的复制构造函数执行内容:使用参数中的对象,构造出一个新的对象。

        3. 合成的赋值操作符执行内容:使用参数中的对象,使用参数对象的非static成员 依次对 目标对象的成员赋值。注意:在赋值操作符执行之前,目标对象已经存在。

        4. 在继承体系中,要将基类(或称为父类)的析构函数,声明为virtual方法(即虚函数)。

        5. 子类中包含父类的成员。即子类有两个部分组成,父类部分和子类自己定义的部分。

        6. 如果在子类中显式调用父类的构造函数,只能在构造函数的初始化列表中调用,并且只能调用其直接父类的。

        7. 在多重继承时,按照基类继承列表中声明的顺序初始化父类。

        8. 在虚继承中,虚基类的初始化 早于 非虚基类,并且子类来初始化虚基类(注意:虚基类不一定是子类的直接父类)。

    三、单继承

    核心:在构造子类之前一定要执行父类的一个构造函数。

    1.构造函数(不包括复制构造函数)。

            顺序:①直接父类;②自己  
                注意:若直接父类还有父类,那么“直接父类的父类”会在“直接父类” 之前 构造。 可以理解为这是一个递归的过程,知道出现一个没有父类的类才停止。

        2.1 如果没有显式定义构造函数,则“合成的默认构造函数”会自动调用直接父类的“默认构造函数”,然后调用编译器为自己自动生成的“合成的默认构造函数”。 
        2.2 如果显式定义了自己的构造函数 
            2.2.1 如果没有显式调用直接父类的任意一个构造函数,那么和“合成的默认构造函数”一样,会先自动调用直接父类的 默认构造函数,然后调用自己的构造函数。  
            2.2.2 如果显式调用直接父类的任意一个构造函数,那么会先调用直接父类相应的构造函数,然后调用自己的构造函数。

    2. 复制构造函数

               顺序:①直接父类;②自己 
                   注意:和构造函数一样,若直接父类还有父类,那么“直接父类的父类”会在“直接父类” 之前 构造。 可以理解为这是一个递归的过程,知道出现一个没有父类的类才停止。

        2.1 如果 没有显式定义复制构造函数,则“合成的复制构造函数”会自动调用直接父类的“复制构造函数”,然后调用编译器为自己自动生成的“合成的复制构造函数”(注意:不是默认构造函数)  
        2.2 如果显式定义了自己的复制构造函数 (和构造函数类似) 
            2.2.1 如果没有显式调用父类的任意一个构造函数,那么会先调用直接父类的 默认构造函数(注意:不是 复制构造函数)。 
            2.2.2 如果显式调用直接父类的任意一个构造函数,那么会先调用直接父类相应的构造函数。

    3.赋值操作符重载

        3.1 如果没有显式定义,会自动调用直接父类赋值操作符。(注意:不是 默认构造函数)   
        3.2 如果显式定义了,就只执行自己定义的版本,不再自动调用直接父类的赋值操作符,只执行自己的赋值操作符。 
                注意:如有需要对父类子部分进行赋值,应该在自己编写的代码中,显式调用父类的赋值操作符。 
    4. 析构函数  
        与构造函数 顺序相反。

    四、多继承

    继承的差别就是:需要考虑到多个直接父类。其它的都相同

    1.构造函数(不包括复制构造函数)。

                   顺序:①所有直接父类;(按照基类继承列表中声明的顺序)②自己  
                注意:若直接父类还有父类,那么“直接父类的父类”会在“直接父类” 之前 构造。 可以理解为这是一个递归的过程,知道出现一个没有父类的类才停止。

        2.1 如果 没有 显式定义构造函数,则“合成的默认构造函数”会自动依次调用所有直接父类的“默认构造函数”,然后调用编译器为自己自动生成的“合成的默认构造函数”。 
        2.2 如果显式定义了自己的构造函数 
            2.2.1 如果没有显式调用父类的任意一个构造函数,那么和“合成的默认构造函数”一样,会自动依次调用所有直接父类的 默认构造函数,然后调用自己的构造函数。 
            2.2.2 如果显式调用了父类的任意一个构造函数,那么按照基类列表的顺序,对于每一个父类依次判断:若显式调用了构造函数,那么会调用该父类相应的构造函数;如果没有显式调用,就调用默认构造函数。最后调用自己的构造函数。

    2. 复制构造函数

                   顺序:①所有直接父类;(按照基类继承列表中声明的顺序)②自己  
                注意:和构造函数一样,若直接父类还有父类,那么“直接父类的父类”会在“直接父类” 之前 构造。 可以理解为这是一个递归的过程,知道出现一个没有父类的类才停止。

        2.1 如果 没有显式定义复制构造函数,则“合成的复制构造函数”会自动依次调用所有直接父类的“复制构造函数”,然后调用编译器为自己自动生成的“合成的复制构造函数”(注意:不是默认构造函数) 
        2.2 如果显式定义了自己的复制构造函数 (和构造函数类似) 
            2.2.1 如果没有显式调用父类的任意一个构造函数,那么会先自动依次调用直接父类的 默认构造函数(注意:不是 复制构造函数)。 
            2.2.2 如果显式调用直接父类的任意一个构造函数,那么按照基类列表的顺序,对于每一个父类依次判断:若显式调用了构造函数,那么会调用该父类相应的构造函数;如果没有显式调用,就调用默认构造函数。最后调用自己的复制构造函数。

    3.赋值操作符重载

        3.1 如果没有显式定义,会自动依次调用直接父类赋值操作符。(注意:不是 默认构造函数) 
        3.2 如果显式定义了,就只执行自己定义的版本,不再自动调用直接父类的赋值操作符,只执行自己的赋值操作符。  
                注意:如有需要对父类子部分进行赋值,应该在自己编写的代码中,显式调用所有直接父类的赋值操作符。 
    4. 析构函数  
        与 构造函数 顺序相反。

    五、虚继承

    继承的差别就是:要考虑到虚基类,其它的都相同。(虚基类的初始化要早于非虚基类,并且只能由子类对其进行初始化)

    1.构造函数(不包括复制构造函数)。

                                    顺序:①所有虚基类(按照基类继承列表中声明的顺序进行查找);②所有直接父类;(按照基类继承列表中声明的顺序)③自己  
                注意:若虚基类或者直接父类还有父类,那么“直接父类的父类”会在“直接父类” 之前 构造,“虚基类的父类”也会在“虚基类”之前构造。 可以理解为这是一个递归的过程,知道出现一个没有父类的类才停止。

        2.1 如果 没有 显式定义构造函数,则“合成的默认构造函数”会先依次调用所有虚基类的默认构造函数,然后再自动依次调用所有直接父类的“默认构造函数”,最后调用编译器为自己自动生成的“合成的默认构造函数”。 
        2.2 如果显式定义了自己的构造函数         2.2.1 如果没有显式调用父类的任意一个构造函数,那么和“合成的默认构造函数”一样,会先依次调用所有虚基类的默认构造函数,然后再自动依次调用所有直接父类的 默认构造函数,最后调用自己的构造函数。 
            2.2.2 如果显式调用了父类的任意一个构造函数,那么按照基类列表的顺序,先初始化所有虚基类,再初始化所有直接父类。对于每一个父类依次判断:若显式调用了构造函数,那么会调用该父类相应的构造函数;如果没有显式调用,就调用默认构造函数。最后调用自己的构造函数。

    2. 复制构造函数

                                   顺序:①所有虚基类(按照基类继承列表中声明的顺序进行查找);②所有直接父类;(按照基类继承列表中声明的顺序)③自己  
                注意:和构造函数一样,若虚基类或者直接父类还有父类,那么“直接父类的父类”会在“直接父类” 之前 构造,“虚基类的父类”也会在“虚基类”之前构造。 可以理解为这是一个递归的过程,知道出现一个没有父类的类才停止。

        2.1 如果 没有显式定义复制构造函数,则“合成的复制构造函数”会自动依次调用所有直接父类的“复制构造函数”,然后调用编译器为自己自动生成的“合成的复制构造函数”(注意:不是默认构造函数) 
        2.2 如果显式定义了自己的复制构造函数 (和构造函数类似) 
            2.2.1 如果没有显式调用父类的任意一个构造函数,那么会先依次调用所有虚基类的默认构造函数,然后再依次调用所有直接父类的 默认构造函数(注意:不是 复制构造函数)。 
            2.2.2 如果显式调用直接父类的任意一个构造函数,那么按照基类列表的顺序,先初始化所有虚基类,再初始化所有直接父类。对于每一个父类依次判断:若显式调用了构造函数,那么会调用该父类相应的构造函数;如果没有显式调用,就调用默认构造函数。

    3.赋值操作符重载

        3.1 如果没有显式定义,会自动依次调用所有虚基类所有直接父类赋值操作符。(注意:不是 默认构造函数) 
                 3.2 如果显式定义了,就只执行自己定义的版本,不再自动调用直接父类的赋值操作符,只执行自己的赋值操作符。  
                注意:如有需要对父类子部分进行赋值,应该在自己编写的代码中,显式调用所有虚基类所有直接父类的赋值操作符。 
    4. 析构函数  
        与 构造函数 顺序相反。

    六、总结:

    1. 整体顺序:虚基类  -->  直接父类  -->自己

    2. 在任何显式定义的构造函数中,如果没有显式调用父类的构造函数,那么就会调用父类的默认构造函数。

    3. 合成的复制构造函数合成的赋值操作符,(当没有显式定义时,编译器自动提供),会自动调用的是虚基类直接父类的复制构造函数和赋值操作符,而不是默认构造函数;

    4. 自己显式定义的复制构造函数,除非在初始化列表中显示调用,否则只会调用虚基类和父类的默认构造函数。

    5. 自己显式定义的赋值操作符,除非显式调用,否则只执行自己的代码。

    6. 析构函数的执行顺序与 构造函数 相反。

    七、例子程序

    话说只有自己写一个程序,然后研究运行结果,才会掌握的更好。所以下面就是个例子程序了。可以根据需要,注释掉某个类的相应函数,观察结果。

    1. 该例子的继承层次图为:(M和N是虚基类)

    ClassDiagram2

    2. 代码如下

    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. class A {  
    5. public:  
    6.     A() {  
    7.         cout<<"int A::A()"<<endl;  
    8.     }  
    9.     A(A &a) {  
    10.         cout<<"int A::A(A &a)"<<endl;  
    11.     }  
    12.     A& operator=(A& a) {  
    13.         cout<<"int A::operator=(A &a)"<<endl;  
    14.         return a;  
    15.     }  
    16.     virtual ~A() {  
    17.         cout<<"int A::~A()"<<endl;  
    18.     }  
    19. };  
    20.   
    21. class M :public A {  
    22. public:  
    23.     M() {  
    24.         cout<<"int M::M()"<<endl;  
    25.     }  
    26.     M(M &a) {  
    27.         cout<<"int M::M(M &a)"<<endl;  
    28.     }  
    29.     M& operator=(M& m) {  
    30.         cout<<"int M::operator=(M &a)"<<endl;  
    31.         return m;  
    32.     }  
    33.     virtual ~M() {  
    34.         cout<<"int M::~M()"<<endl;  
    35.     }  
    36. };  
    37.   
    38. class B:virtual public M {  
    39. public:  
    40.     B() {  
    41.         cout<<"int B::B()"<<endl;  
    42.     }  
    43.     B(B &a) {  
    44.         cout<<"int B::B(B &a)"<<endl;  
    45.     }  
    46.     B& operator=(B& b) {  
    47.         cout<<"int B::operator=(B &a)"<<endl;  
    48.         return b;  
    49.     }  
    50.     virtual ~B() {  
    51.         cout<<"int B::~B()"<<endl;  
    52.     }  
    53.   
    54. };  
    55.   
    56. class N :public A {  
    57. public:  
    58.     N() {  
    59.         cout<<"int N::N()"<<endl;  
    60.     }  
    61.     N(N &a) {  
    62.         cout<<"int N::N(N &a)"<<endl;  
    63.     }  
    64.     N& operator=(N& n) {  
    65.         cout<<"int N::operator=(N &a)"<<endl;  
    66.         return n;  
    67.     }  
    68.     virtual ~N() {  
    69.         cout<<"int N::~N()"<<endl;  
    70.     }  
    71. };  
    72. class C:virtual public N {  
    73. public:  
    74.     C() {  
    75.         cout<<"int C::C()"<<endl;  
    76.     }  
    77.     C(C &a) {  
    78.         cout<<"int C::C(C &a)"<<endl;  
    79.     }  
    80.     C& operator=(C& c) {  
    81.         cout<<"int C::operator=(C &a)"<<endl;  
    82.         return c;  
    83.     }  
    84.     virtual ~C() {  
    85.         cout<<"int C::~C()"<<endl;  
    86.     }  
    87. };  
    88. class E:virtual public M{  
    89. public:  
    90.     E() {  
    91.         cout<<"int E::E()"<<endl;  
    92.     }  
    93.     E(E &a) {  
    94.         cout<<"int E::E(E &a)"<<endl;  
    95.     }  
    96.     E& operator=(E& e) {  
    97.         cout<<"int E::operator=(E &a)"<<endl;  
    98.         return e;  
    99.     }  
    100.     virtual ~E() {  
    101.         cout<<"int E::~E()"<<endl;  
    102.     }  
    103. };  
    104. class D:public B, public C, public E {  
    105. public:  
    106.     D() {  
    107.         cout<<"int D::D()"<<endl;  
    108.     }  
    109.     D(D &a) {  
    110.         cout<<"int D::D(D &a)"<<endl;  
    111.     }  
    112.     D& operator=(D& d) {  
    113.         cout<<"int D::operator=(D &a)"<<endl;  
    114.         return d;  
    115.     }  
    116.     virtual ~D() {  
    117.         cout<<"int D::~D()"<<endl;  
    118.     }  
    119. };  
    120.   
    121.   
    122. int main(int argc, char **argv) {  
    123.     cout<<"-------构造函数-------"<<endl;  
    124.     D d;  
    125.     cout<<"-------复制构造函数-------"<<endl;  
    126.     D d1(d);  
    127.     cout<<"-------赋值操作符-------"<<endl;  
    128.     d = d1;  
    129.     cout<<"-------析构函数-------"<<endl;  
    130.   
    131.   
    132.     return 0;  
    133. }  

    3. 运行结果与分析

           分析:M和N是虚基类,但是A不是虚基类。B和E共享一个M,但是M和N都会含有类A的部分,因为A不是虚基类,所以M和N不共享A。下面的注释部分为添加的分析。

      1. -------构造函数-------  
      2. int A::A()  
      3. int M::M()//构造虚基类M时,要先构造其父类A  
      4. int A::A()  
      5. int N::N()//和M一样,构造虚基类N时,也要先构造其父类A  
      6. int B::B()//构造完虚基类,开始构造直接父类,按照声明顺序为B、C、E  
      7. int C::C()  
      8. int E::E()  
      9. int D::D()//最后构造自己  
      10. -------复制构造函数-------  
      11. int A::A()  
      12. int M::M()  
      13. int A::A()  
      14. int N::N()  
      15. int B::B()  
      16. int C::C()  
      17. int E::E()  
      18. int D::D(D &a)//因为D中定义了复制构造函数,并且没有显式调用父类的构造函数,所以所有的“虚基类”和“直接父类”都调用默认构造函数  
      19. -------赋值操作符-------  
      20. int D::operator=(D &a) //因为显式调用了赋值操作符,那么就只调用自己的代码,不会隐式调用其它的函数  
      21. -------析构函数-------  
      22. int D::~D()  
      23. int E::~E()  
      24. int C::~C()  
      25. int B::~B()  
      26. int N::~N()  
      27. int A::~A()  
      28. int M::~M()  
      29. int A::~A()//因为main函数中定义了两个D对象,所以main函数结束时要进行析构两个D对象。析构的顺序与 构造函数相反。  
      30. int D::~D()  
      31. int E::~E()  
      32. int C::~C()  
      33. int B::~B()  
      34. int N::~N()  
      35. int A::~A()  
      36. int M::~M()  
      37. int A::~A()  
      38.   
      39. Press any key to continue.  
  • 相关阅读:
    北京Uber优步司机奖励政策(4月1日)
    滴滴快车奖励政策,高峰奖励,翻倍奖励,按成交率,指派单数分级(4月1日)
    成都Uber优步司机奖励政策(3月31日)
    北京Uber优步司机奖励政策(3月31日)
    滴滴快车奖励政策,高峰奖励,翻倍奖励,按成交率,指派单数分级(3月31日)
    uber司机已经激活了,就是还没有上传头
    优步深圳车辆准入标准降啦!6万8年粤车牌!
    开优步认识各色各样的人,人生需要这样的新鲜体验!
    Uber:中国市场两年内不考虑盈利,每年补贴10亿美金
    设置Windows开机自动启动VirtualBox虚拟机系统
  • 原文地址:https://www.cnblogs.com/nkxyf/p/2598052.html
Copyright © 2011-2022 走看看