C++ 中關於初始化列表的教程很多:
初始化和賦值對內置類型的成員沒有什麼大的區別,像上面的任一個構造函數都可以。對非內置類型成員變量,爲了避免兩次構造,推薦使用類構造函數初始化列表。但有的時候必須用帶有初始化列表的構造函數:
1.成員類型是沒有默認構造函數的類。若沒有提供顯示初始化式,則編譯器隱式使用成員類型的默認構造函數,若類沒有默認構造函數,則編譯器嘗試使用默認構造函數將會失敗。
2.const成員或引用類型的成員。因爲const對象或引用類型只能初始化,不能對他們賦值。
開始一直不理解什麼叫二次構造,其實是第一次是調用默認構造函數,第二次是調用賦值操作符。
有的博客這樣寫到:關於類的構造函數,可以分爲兩個部分,初始化部分(初始化列表)和計算部分(花括號間的賦值運算),在花括號作用域間進行的運算不是初始化,而是賦值。
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A的無參構造函數" << endl;
}
A(int a)
{
cout << "A的有參構造函數" << endl;
}
~A()
{
cout << "A的析構函數" << endl;
}
A(const A& a)
{
cout << "A的拷貝構造函數" << endl;
}
A& operator=(const A &a)
{
cout << "默認的等號操作符" << endl;
this->m_a = a.m_a;
return *this;
}
private:
int m_a;
};
class B
{
public:
B(A &a1, A &a2, int b)
{
m_a1 = a1;
m_a2 = a2;
cout << "====" << endl;
m_b = b;
cout << "B的有參構造函數" << endl;
}
/*B(A &a1, A &a2, int b) :m_a1(a1), m_a2(a2), m_b(b)
{
cout << "B的有參構造函數" << endl;
}*/
private :
int m_b;
A m_a1;
A m_a2;
};
int main()
{
A m_a1(10);
A m_a2(20);
//關於類的構造函數,可以分爲兩個部分,初始化部分(初始化列表)和計算部分(花括號間的賦值運算),
//在花括號作用域間進行的運算不是初始化,而是賦值。
B b(m_a1, m_a2, 3);
return 0;
}
可以看,
結論:
初始化列表的一般形式如下:
Object::Object(int _x,int _y):x(_x),y(_y) {}
構造函數初始化一般通過構造函數實現,實現如下:
Object::Object(int _x,int _y)
{
x=_x;
y=_y;
}
上面的構造函數使用初始化列表的會顯式地初始化類的成員;而沒有使用初始化列表的構造函數是對類的成員賦值,並沒有進行顯式的初始化。
初始化和賦值對內置類型的成員沒有什麼的的區別,在成員初始化列表和構造函數體內進行,在性能和結果上都是一樣的。對非內置類型成員變量,因爲類類型的數據成員的數據成員對象在進入函數體前已經構造完成,也就是說在成員初始化列表處進行構造對象的工作,調用構造函數,在進入函數體之後,進行的是對已經構造好的類對象的賦值,又調用一個賦值賦值操作符才能完成(如果並未提供,則使用編譯器提供的默認成員賦值行爲)。爲了避免兩次構造,推薦使用類構造函數初始化列表。
但有很多場合必須使用帶有初始化列表的構造函數。例如,成員類型是沒有默認構造函數的類,若沒有提供顯示初始化時,則編譯器隱式使用成員類型的默認構造函數,若類沒有默認構造函數,則編譯器嘗試調用默認構造函數將會失敗。再例如const成員或者引用類型的成員,因爲const對象或引用類型只能初始化,不能對它們進行賦值。
1,常量成員
常量成員只能初始化不能賦值,所以必須放在初始化列表裏。
2,引用類型
引用必須在定義時初始化,並且不能重新賦值,所以必須放在初始化表裏。
3,對象成員
這個成員是其他類的對象,例如上面的Address addr成員。如果把它放在構造函數的初始化列表裏,此時會調用Address類的copy constructor函數,對這個類對象進行初始化。如果把它放在構造函數體中,會先調用Address類的default constructor函數,然後再調用Address類的copy constructor函數。從性能上考慮,把對象成員的初始化放在初始化列表裏性能會更高。