一、構造函數初始化列表
推薦在構造函數初始化列表中進行初始化
構造函數的執行分為兩個階段
初始化段
普通計算段
(一)、對象成員及其初始化
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成員、引用成員的初始化
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) ;
如果類中沒有定義拷貝構造函數,則系統自動生成一個缺省復制構造函數,作為該類的公有成員,所做的事情也是簡單的成員復制
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_ |
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; } |
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 對象,實際上是調用拷貝構造函數把該對象的值拷入臨時對象。如果
返回的是變量,處理過程類似,只是不調用構造函數。
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++編程規范