zoukankan      html  css  js  c++  java
  • 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数

    一、构造函数初始化列表

    推荐在构造函数初始化列表中进行初始化
    构造函数的执行分为两个阶段

    初始化段

    普通计算段

    (一)、对象成员及其初始化

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
     
    #include <iostream>
    using  namespace std;

    class Object
    {
    public:
        Object( int num) : num_(num)
        {
            cout <<  "Object " << num_ <<  " ..." << endl;
        }
        ~Object()
        {
            cout <<  "~Object " << num_ <<  " ..." << endl;
        }
    private:
         int num_;
    };

    class Container
    {
    public:
        Container( int obj1 =  0int obj2 =  0) : obj2_(obj2), obj1_(obj1)
        {
            cout <<  "Container ..." << endl;
        }
        ~Container()
        {
            cout <<  "~Container ..." << endl;
        }

    private:
        Object obj1_;
        Object obj2_;
    };

    int main( void)
    {
        Container c( 1020);
         return  0;
    }

    从输出可以看出几点,一是构造对象之前,必须先构造对象的成员;二是对象成员构造的顺序与定义时的顺序有关,跟初始化列表顺序无关;三是构造的顺序和析构的顺序相反;四是如果对象成员对应的类没有默认构造函数,那对象成员也只能在初始化列表进行初始化。


    (二)、const成员、引用成员的初始化

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
     
    #include <iostream>
    using  namespace std;

    // const成员的初始化只能在构造函数初始化列表中进行
    // 引用成员的初始化也只能在构造函数初始化列表中进行
    // 对象成员(对象成员所对应的类没有默认构造函数)的初始化,也只能在构造函数初始化列表中进行
    class Object
    {
    public:
         enum E_TYPE
        {
            TYPE_A =  100,
            TYPE_B =  200
        };
    public:
        Object( int num =  0) : num_(num), kNum_(num), refNum_(num_)
        {
             //kNum_ = 100;
             //refNum_ = num_;
            cout <<  "Object " << num_ <<  " ..." << endl;
        }
        ~Object()
        {
            cout <<  "~Object " << num_ <<  " ..." << endl;
        }

         void DisplayKNum()
        {
            cout <<  "kNum=" << kNum_ << endl;
        }
    private:
         int num_;
         const  int kNum_;
         int &refNum_;
    };

    int main( void)
    {
        Object obj1( 10);
        Object obj2( 20);
        obj1.DisplayKNum();
        obj2.DisplayKNum();

        cout << obj1.TYPE_A << endl;
        cout << obj2.TYPE_A << endl;
        cout << Object::TYPE_A << endl;

         return  0;
    }


    因为const 变量或者引用都得在定义的时候初始化,所以const 成员和引用成员必须在初始化列表中初始化。另外,可以使用定义枚举类型来得到类作用域共有的常量。


    二、拷贝构造函数

    (一)、拷贝构造函数

    功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
    声明:只有一个参数并且参数为该类对象的引用 Test::Test(const Test &other) ;
    如果类中没有定义拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员,所做的事情也是简单的成员复制

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    #ifndef _TEST_H_
    #define _TEST_H_

    class Test
    {
    public:
         // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的
         // 默认的构造函数
        Test();
         explicit Test( int num);
        Test( const Test &other);
         void Display();

        Test & operator=( const Test &other);

        ~Test();
    private:
         int num_;
    };
    #endif  // _TEST_H_
     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    #include  "Test.h"
    #include <iostream>
    using  namespace std;

    // 不带参数的构造函数称为默认构造函数
    Test::Test() : num_( 0)
    {
         //num_ = 0;
        cout <<  "Initializing Default" << endl;
    }

    Test::Test( int num) : num_(num)
    {
         //num_ = num;
        cout <<  "Initializing " << num_ << endl;
    }

    Test::Test( const Test &other) : num_(other.num_)
    {
         //num_ = other.num_;
        cout <<  "Initializing with other " << num_ << endl;
    }

    Test::~Test()
    {
        cout <<  "Destroy " << num_ << endl;
    }

    void Test::Display()
    {
        cout <<  "num=" << num_ << endl;
    }

    Test &Test:: operator=( const Test &other)
    {
        cout <<  "Test::operator=" << endl;
         if ( this == &other)
             return * this;

        num_ = other.num_;
         return * this;
    }
     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    #include  "Test.h"

    int main( void)
    {
        Test t( 10);
         //Test t2(t);       // 调用拷贝构造函数
        Test t2 = t;         // 等价于Test t2(t);

         return  0;
    }


    即调用了拷贝构造函数,destroy 的两个分别是t 和 t2。


    (二)、拷贝构造函数调用的几种情况

    当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。


    当函数的返回值是类对象,函数执行完成返回调用者时使用。理由也是要建立一个临时对象中,再返回调用者。为什么不直接用要返回的局部对象呢?因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程类似,只是不调用构造函数。

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    #include  "Test.h"
    #include <iostream>
    using  namespace std;

    void TestFun( const Test t1)
    {

    }

    void TestFun2( const Test &t1)
    {

    }

    Test TestFun3( const Test &t1)
    {
         return t1;
    }

    const Test &TestFun4( const Test &t1)
    {
         //return const_cast<Test&>(t1);
         return t1;
    }

    int main( void)
    {
        Test t( 10);
        TestFun(t);

        cout <<  "........" << endl;

         return  0;
    }


    即在传参的时候调用了拷贝构造函数,函数返回时TestFun 的形参t 1生存期到了,在分割线输出之前销毁t1,最后destroy 的是 t。


    将TestFun(t); 换成 TestFun2(t);


    参数为引用,即没有调用拷贝构造函数。


    将TestFun(t); 换成 t = TestFun3(t);


    函数返回时会调用拷贝构造函数,接着调用赋值运算符,释放临时对象,最后释放t。如果没有用t 接收,则临时对象也会马上释放。


    将TestFun(t); 换成 Test t2 = TestFun3(t);


    函数返回调用拷贝构造函数,但没有再次调用拷贝构造函数,而且没有释放临时对象,可以理解成临时对象改名为t2 了。


    将TestFun(t); 换成 Test& t2 = TestFun3(t);


    函数返回时调用拷贝构造函数,因为t2 引用着临时对象,故没有马上释放。


    将TestFun(t); 换成 Test t2 = TestFun4(t);


    函数传参和返回都没有调用拷贝构造函数,初始化t2 时会调用拷贝构造函数。


    将TestFun(t); 换成 const Test&  t2 = TestFun4(t);


    函数传参和返回都没有调用构造函数,t2 是引用故也不会调用拷贝构造函数。

  • 相关阅读:
    vscode配置备份
    正则替换html代码中img标签的src值
    使用css3原生变量实现主题换肤
    vue3基础知识学习系列(二)响应式原理实现
    vue3基础知识学习系列(一)api使用
    eslint规则中文解释
    开发笔记:使用canvas实现3D金字塔比例图表&#128200;
    实用网页UI调试技巧
    2020年特殊的一年,成就特殊的我们
    java设计模式之责任链模式
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3157130.html
Copyright © 2011-2022 走看看