初始化列表(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 = 0, int obj2 = 0) : obj2_(obj2), obj1_(obj1)
    {
        cout << "Container ..." << endl;
    }
    ~Container()
    {
        cout << "~Container ..." << endl;
    }

private:
    Object obj1_;
    Object obj2_;
};

int main(void)
{
    Container c(10, 20);
    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 成員和引用成員必須在初始化列表中初始化。另外,可以使用定義枚舉類型來得到類作用域共有的常量。


二、拷貝構造函數

(一)、拷貝構造函數

功能:使用一個已經存在的對象來初始化一個新的同一類型的對象
聲明:只有一個參數並且參數為該類對象的引用 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。


(二)、拷貝構造函數調用的幾種情況

當函數的形參是類的對象,調用函數時,進行形參與實參結合時使用。這時要在內存新建立一個局部對象,並把實參拷貝到新的對象中。理所當然也調

用拷貝構造函數。還有一點,為什么拷貝構造函數的參數需要是引用? 這是因為如果拷貝構造函數中的參數不是一個引用,即形如CClass(const 

CClass c_class),那么就相當於采用了傳值的方式(pass-by-value),而傳值的方式會調用該類的拷貝構造函數,從而造成無窮遞歸地調用拷貝構造函

數。
當函數的返回值是類對象,函數執行完成返回調用者時使用。也是要建立一個臨時對象,再返回調用者。為什么不直接用要返回的局部對象呢?

因為局部對象在離開建立它的函數時就消亡了,不可能在返回調用函數后繼續生存,所以在處理這種情況時,編譯系統會在調用函數的表達式中創建一

個無名臨時對象,該臨時對象的生存周期只在函數調用處的表達式中。所謂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 接收,不會調用operator= 而且臨時對象也會馬上釋放。


將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 是引用故也不會調用拷貝構造函數。


參考:

C++ primer 第四版
Effective C++ 3rd
C++編程規范


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM