zoukankan      html  css  js  c++  java
  • C++继承与构造函数、复制控制

            每个派生类对象由派生类中定义的(非static)成员加上一个或多个基类子对象构成,因此,当构造、复制、赋值和撤销派生类型对象时,也会构造、复制、赋值和撤销这些基类子对象。

            构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。

     

    1:构造函数和继承

           派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。

     

            派生类的合成默认构造函数:除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化:

    class father
    {
    public:
        int publ_i;
        father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
        {
            cout << "father constructor" << endl;
        }
        virtual void display()
        {
            cout << "[father]publ_i is " << publ_i << endl;
            cout << "[father]priv_i is " << priv_i << endl;
            cout << "[father]prot_i is " << prot_i << endl;
        }
    private:
        int priv_i;
    protected:
        int prot_i;
    };
    
    class son: public father
    {
    public:
        void display()
        {
            cout << "[son]publ_i is " << publ_i << endl;
            cout << "[son]prot_i is " << prot_i << endl;
        }
    };
    int main()
    {
        son ss1;
        ss1.display();
    }

            执行”son ss1;”语句时,调用派生类son的合成的默认构造函数,该函数会首先调用基类的默认构造函数。上述代码的结果是:

    father constructor
    [son]publ_i is 1
    [son]prot_i is 3
    


            如果派生类自己定义了构造函数,则该构造函数会隐式调用基类的默认构造函数:

    class son: public father
    {
    public:
        son(int a = 2):mypriv_i(a)
        {
            cout << "son constructor" << endl;
        }
        void display()
        {
            cout << "[son]publ_i is " << publ_i << endl;
            cout << "[son]prot_i is " << prot_i << endl;
            cout << "[son]mypriv_i is " << mypriv_i << endl;
        }
    private:
        int mypriv_i;
    };
    
    int main()
    {
        son ss1;
        ss1.display();
    }

            执行”son ss1;”语句时,调用派生类son的默认构造函数,该函数首先调用基类的默认构造函数初始化基类部分,然后,使用初始化列表初始化son::mypriv_i,最后,在执行son构造函数的函数体。上述代码的结果是:

    father constructor
    son constructor
    [son]publ_i is 1
    [son]prot_i is 3
    [son]mypriv_i is 2
    


            派生类构造函数还可以明确的在初始化列表中调用基类的构造函数,来间接的初始化基类成员。注意,在派生类列表中,不能直接初始化基类成员:

    class son: public father
    {
    public:
        son(int a = 2):mypriv_i(a),father(100)
        {
            cout << "son constructor" << endl;
        }
        void display()
        {
            cout << "[son]publ_i is " << publ_i << endl;
            cout << "[son]prot_i is " << prot_i << endl;
            cout << "[son]mypriv_i is " << mypriv_i << endl;
        }
    private:
        int mypriv_i;
    };
    int main()
    {
        son ss1;
        ss1.display();
    }

            不管初始化列表中的顺序如何,派生类构造函数,都是首先调用基类构造函数初始化基类成员,然后是根据派生类成员的声明顺序进行初始化。

             如果尝试在初始化列表中直接初始化基类成员,会发生编译错误:

        son(int a = 2):mypriv_i(a),publ_i(100)
        {
            cout << "son constructor" << endl;
        }

            报错如下:

    test2.cpp: In constructor ‘son::son(int)’:
    test2.cpp:29:29: error: class ‘son’ does not have any field named ‘publ_i’
      son(int a = 2):mypriv_i(a),publ_i(100)
                                 ^

            派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为 public 或 protected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。

            注意,一个类只能初始化自己的直接基类。

     

    2:复制控制和继承

            派生类合成的复制控制函数,对对象的基类部分连同派生部分的成员一起进行复制、赋值或撤销,自动调用基类的复制构造函数、赋值操作符或析构函数对基类部分进行复制、赋值或撤销:

    class father
    {
    public:
        int publ_i;
        father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
        {
            cout << "father constructor" << endl;
        }
        father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
        {
            cout << "father copy constructor" << endl;
        }
        
        father& operator=(const father& src)
        {
            publ_i = src.publ_i;
            priv_i = src.priv_i;
            prot_i = src.prot_i;
            
            cout << "father operator = " << endl;
            return *this;
        }
        ~father()
        {
            cout << "father destructor" << endl;
        }
        virtual void display()
        {
            cout << "[father]publ_i is " << publ_i << endl;
            cout << "[father]priv_i is " << priv_i << endl;
            cout << "[father]prot_i is " << prot_i << endl;
        }
    private:
        int priv_i;
    protected:
        int prot_i;
    };
    
    class son: public father
    {
    public:
        son(int a = 2):mypriv_i(a)
        {
            cout << "son constructor" << endl;
        }
        void display()
        {
            cout << "[son]publ_i is " << publ_i << endl;
            cout << "[son]prot_i is " << prot_i << endl;
            cout << "[son]mypriv_i is " << mypriv_i << endl;
        }
    private:
        int mypriv_i;
    };
    int main()
    {
        son ss1(3);
        son ss2(ss1);
        ss2.display();
        
        son ss3;
        ss3 = ss1;
        ss3.display();
    }

            “son ss2(ss1);”语句,将调用派生类son的合成的复制构造函数,该构造函数将会自动调用基类的复制构造函数;”ss3 = ss1;”语句,将调用派生类son的合成的赋值操作符函数,该函数会自动调用基类的赋值操作符函数;最后,程序退出之前,会将派生类son的三个对象进行析构,从而自动调用基类的析构函数;上述代码的结果如下:

    father constructor
    son constructor
    father copy constructor
    [son]publ_i is 1
    [son]prot_i is 3
    [son]mypriv_i is 3
    father constructor
    son constructor
    father operator = 
    [son]publ_i is 1
    [son]prot_i is 3
    [son]mypriv_i is 3
    father destructor
    father destructor
    father destructor
    


            如果派生类自己定义了复制构造函数或赋值操作符,则应该显示的调用基类的复制构造函数或赋值操作符:

    class father
    {
    public:
        int publ_i;
        father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
        {
            cout << "father constructor" << endl;
        }
        father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
        {
            cout << "father copy constructor" << endl;
        }
        
        father& operator=(const father& src)
        {
            publ_i = src.publ_i;
            priv_i = src.priv_i;
            prot_i = src.prot_i;
            
            cout << "father operator = " << endl;
            return *this;
        }
        ~father()
        {
            cout << "father destructor" << endl;
        }
        virtual void display()
        {
            cout << "[father]publ_i is " << publ_i << endl;
            cout << "[father]priv_i is " << priv_i << endl;
            cout << "[father]prot_i is " << prot_i << endl;
        }
    private:
        int priv_i;
    protected:
        int prot_i;
    };
    
    class son: public father
    {
    public:
        son(int a = 2):mypriv_i(a),father(100)
        {
            cout << "son constructor" << endl;
        }
        son(const son& src):mypriv_i(src.mypriv_i),father(src)
        {
            cout << "son copy constructor" << endl;
        }
        son& operator=(const son& src)
        {
            father::operator=(src);
            mypriv_i = src.mypriv_i;
            
            cout << "son operator = " << endl;
            return *this;
        }
        void display()
        {
            cout << "[son]publ_i is " << publ_i << endl;
            cout << "[son]prot_i is " << prot_i << endl;
            cout << "[son]mypriv_i is " << mypriv_i << endl;
        }
    private:
        int mypriv_i;
    };
    int main()
    {
        son ss1(3);
        son ss2(ss1);
        ss2.display();
        
        son ss3;
        ss3 = ss1;
        ss3.display();
    }

            在派生类son的复制构造函数中,初始化列表中显示调用基类复制构造函数使用src初始化基类部分。这里如果省略调用基类复制构造函数的调用的话,编译器会自动调用基类的默认构造函数初始化基类部分,这就造成新构造的对象会有比较奇怪的配置,它的基类部分保存默认值,而他的派生类部分是src的副本;

            在派生类son的赋值操作符函数中,显示调用了基类的赋值操作符函数。如果省略了这一调用,则赋值操作,仅仅使该对象的mypriv_i与src的mypriv_i相同;

            上述代码的结果如下:

    father constructor
    son constructor
    father copy constructor
    son copy constructor
    [son]publ_i is 100
    [son]prot_i is 3
    [son]mypriv_i is 3
    father constructor
    son constructor
    father operator = 
    son operator = 
    [son]publ_i is 100
    [son]prot_i is 3
    [son]mypriv_i is 3
    father destructor
    father destructor
    father destructor
    


            如果定义了派生类的析构函数,则不同于复制构造函数和赋值操作符:编译器会自动调用基类的析构函数,因此在派生类的析构函数中,只负责清除自己的成员:

        ~son()
        {
            cout << "son destructor" << endl;
        }

            定义了son的析构函数之后,上述代码的结果是:

    ...
    son destructor
    father destructor
    son destructor
    father destructor
    son destructor
    father destructor

            对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。

     

            另外,如果基类指针指向了一个派生类对象,则删除该基类指针时,如果基类的析构函数不是虚函数的话,则只会调用基类的析构函数,而不会调用派生类析构函数。比如还是上面的father和son两个类,运行下面的代码:

        father *fp = new son;
        delete fp;

            结果就是:

    father constructor
    son constructor
    father destructor

            为了避免这种情况的发生,应该将基类的析构函数定义为虚函数,这样,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。从而可以得到正确的结果。

            因此,即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。

     

    3:构造函数或析构函数中调用虚函数

            在运行构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。

            因此,如果是在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。如果在基类构造函数中调用虚函数,则构造派生类的时候,该虚函数实际上还是调用的基类的版本:

    class father
    {
    public:
        int publ_i;
        father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
        {
            cout << "father constructor" << endl;
            display();
        }
        virtual void display()
        {
            cout << "[father]publ_i is " << publ_i << endl;
            cout << "[father]priv_i is " << priv_i << endl;
            cout << "[father]prot_i is " << prot_i << endl;
        }
    
    private:
        int priv_i;
    protected:
        int prot_i;
    };
    
    class son: public father
    {
    public:
        son()
        {
            cout << "son constructor" << endl;
        }
        
        void display()
        {
            cout << "[son]publ_i is " << publ_i << endl;
            cout << "[son]prot_i is " << prot_i << endl;
        }
    };
    
    int main()
    {
        father *fp = new son;
    }

            在构造son对象时,需要调用father的构造函数,在father的构造函数中调用了虚函数display。此时,虽然构造的是派生类对象,但是这里依然还是调用基类的display函数。因此,上述代码的结果为:

    father constructor
    [father]publ_i is 1
    [father]priv_i is 2
    [father]prot_i is 3
    son constructor
    


            无论由构造函数(或析构函数)直接调用虚函数,或者从构造函数(或析构函数)所调用的函数间接调用虚函数,都应用这种绑定。

            要理解这种行为,考虑如果从基类构造函数(或析构函数)调用虚函数的派生类版本会怎么样。虚函数的派生类版本很可能会访问派生类对象的成员,但是,对象的派生部分的成员不会在基类构造函数运行期间初始化,实际上,如果允许这样的访问,程序很可能会崩溃。

  • 相关阅读:
    机器翻译评测——BLEU算法详解 (新增 在线计算BLEU分值)
    机器翻译评测——一份评测集的艰辛制作过程
    机器翻译评测——一种检测批量译文是否参考机器翻译的方法
    Recall(召回率)and Precision(精确率)
    “图像识别技术”的一次实践体验
    kappa系数在大数据评测中的应用
    Linux下crontab的使用
    Bing的Translation API 接入
    Julia体验 语言特性 元编程,宏
    Windows多个应用程序共享全局变量,静态变量
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247022.html
Copyright © 2011-2022 走看看