一、為了更好的說明初始化列表,首先由一個錯誤的代碼引入:

1 #include<iostream> 2 3 #include"string.h" 4 using namespace std; 5 6 class Student 7 { 8 private: 9 int Num,Chinese,Maths,English; 10 float Total; 11 char Name[20]; 12 public: 13 Student(){}//默認構造函數 14 Student(char name[20],int num,int chinese,int maths,int english); 15 16 }; 17 Student::Student(char name[20],int num,int chinese,int maths,int english){ 18 int n; 19 for(n=0;n<strlen(name);n++) 20 Name[n]=name[n]; 21 Num=num; 22 Chinese=chinese; 23 English=english; 24 Maths=maths; 25 Total=maths+chinese+english; 26 } 27 28 int main() 29 { 30 int i,j; 31 int num,chinese,maths,english; 32 char name[20]; 33 Student std[5];//因為有了默認構造函數所以此時編譯可以通過 34 35 for(i=0;i<5;i++) 36 { 37 cin>>name[20]>>num>>chinese>>maths>>english; 38 std[i](name[20],num,chinese,maths,english); 39 for(j=0;j<20;j++) 40 name[j]='\0'; 41 42 } 43 }
上面代碼在編譯的時候出現一個錯誤提示:
[Error] no match for call to '(Student) (char&, int&, int&, int&, int&)'
該錯誤定位到下面這行std[i](name[20],num,chinese,maths,english);該段代碼錯誤的原因是誤認為先定義一個Student 的數組,之后調用構造函數來進行賦值,實際上,構造函數是在Student std[5];這句定義的時候就調用默認構造函數初始化了 (如果讀者不明白構造函數的執行過程請參考我的上篇筆記)。所有構造函數(可以是默認構造或者非平凡構造)都是在定義的時候就調用,定義以后的調用就不叫初始化了,而是賦值。寫這個錯誤代碼的人,明顯是沒有理解構造函數是怎么執行的,
他的目的是通過循環依次調用構造函數來進行初始化。當然我們可以按照他的想法來進行"正確"的編寫代碼,將上面的錯誤行出修改為std[i].Student(name[20],num,chinese,maths,english);
這樣看起來才像是對象本身來調用構造函數進行初始化。遺憾的是,這樣也是無法編譯的,出現下面的錯誤信息:
[Error] invalid use of 'Student::Student'
出現這樣的錯誤是因為,C++本身就要求在定義的時候系統自動調用構造函數,而對象是不能調用構造函數的。
解決方案:
1、可以在定義的時候直接初始化,為了方便的說明問題,我們把上面的Student std[5];這句代碼中改為1個數組元素,for循環去掉,
就變成下面的代碼了(這段代碼僅僅c++11支持):

1 #include<iostream> 2 3 #include"string.h" 4 using namespace std; 5 6 class Student 7 { 8 private: 9 int Num,Chinese,Maths,English; 10 float Total; 11 char Name[20]; 12 public: 13 Student(){}//默認構造函數 14 Student(char name[20],int num,int chinese,int maths,int english); 15 16 }; 17 Student::Student(char name[20],int num,int chinese,int maths,int english){ 18 int n; 19 for(n=0;n<strlen(name);n++) 20 Name[n]=name[n]; 21 Num=num; 22 Chinese=chinese; 23 English=english; 24 Maths=maths; 25 Total=maths+chinese+english; 26 } 27 28 int main() 29 { 30 int i,j; 31 int num,chinese,maths,english; 32 char name[20]; 33 Student std[1] = { { name,num,chinese,maths,english} }; 34 35 36 }
2、還可以定義一系列指針,然后再每次構造的時候調用new來構造類對象。
如:
1 Student *std[5]; 2 for(i=0;i<5;i++) 3 { 4 cin>>name[20]>>num>>chinese>>maths>>english; 5 std[i]=new Student(name,num,chinese,maths,english); 6 */
為了更好的看到new對象的時候,構造函數是怎么執行的,簡單的更改一下上面的代碼如下:

1 #include<iostream> 2 3 #include"string.h" 4 using namespace std; 5 6 class Student 7 { 8 private: 9 int Num,Chinese,Maths,English ; 10 float Total; 11 public: 12 Student(){}//默認構造函數 13 Student( int num ); 14 15 }; 16 Student::Student(int num ){ 17 18 cout<<"第" << num <<"個構造函數" <<endl; 19 Num = num; 20 cout<<"Num = "<<num<<endl; 21 } 22 23 int main() 24 { 25 int i; 26 int num,chinese,maths,english; 27 28 Student *std[5]; 29 for(i=0;i<5;i++) 30 { 31 std[i]=new Student( i );//每申請一塊內存就自動調用構造函數 32 } 33 }
結果如圖所示:
通過上面的錯誤實例,我們發現有些代碼出現錯誤的原因就是沒有理解好構造函數的執行過程。不知道你有沒有發現
我在上面的代碼中總是在構造函數的內部,對Student類的成員變量進行賦值,這里嚴格來說並不是初始化(有關初始化概念請參考上篇筆記),
這是一個賦值計算的過程,在定義一個變量的時候自動的執行構造函數里面的賦值代碼。
二、面引出初始化列表。
形如下面的代碼:在構造函數的后面有個 : 符號,后面緊跟着對成員變量的初始化。
1 class Student 2 { 3 private: 4 int m_num,m_maths ; 5 public: 6 Student(){}//默認構造函數 7 Student(int num,int maths ):m_num(num),m_maths(maths) 8 { 9 10 cout<<"進入計算階段" <<endl; 11 cout<<"m_num = "<<num<<endl; 12 } 13 14 };
為了更好的了解初始化列表,首先我們要知道,構造函數的執行可以分成兩個階段,初始化階段和計算階段,初始化階段先於計算階段。而初始化階段就是對應着初始化列表那部分,而計算階段就是構造函數的函數體部分。初始化階段先於計算階段執行,如下面程序:
1 #include<iostream> 2 3 #include"string.h" 4 using namespace std; 5 6 class Student 7 { 8 private: 9 int m_num,m_maths ; 10 public: 11 Student(){}//默認構造函數 12 Student(int num,int maths ); 13 }; 14 Student::Student(int num,int maths ):m_num(num),m_maths(maths) 15 { 16 cout<<"進入計算階段前m_num = "<<m_num <<endl; 17 this->m_num = 2; 18 cout<<"進入計算階段后查看m_um = "<<m_num<<endl; 19 } 20 21 int main() 22 { 23 24 Student A(1,2); 25 }
試驗結果圖如下:
從圖中可以看出,在進入計算階段前我們看到了m_num的值變為了1,在計算階段后m_num變為了2,所以說初始化階段先於計算階段完成。
下面說明一下初始化列表的優點:它比在構造函數計算階段(函數體內)進行賦值效率更高(僅僅針對類類型的變量)。下面有兩段代碼和相應的結果圖片,代碼2是在代碼1的基礎上更改了Student2的構造函數為初始化列表形式。
代碼1:

1 #include<iostream> 2 3 using namespace std; 4 5 class Student1 { 6 public: 7 int a; 8 9 Student1() // 無參構造函數 10 { 11 cout << "默認構造函數Student1" << endl ; 12 } 13 14 Student1(const Student1& t1) // 拷貝構造函數 15 { 16 cout << "拷貝構造函數Student1" << endl ; 17 this->a = t1.a ; 18 } 19 20 Student1& operator = (const Student1& t1) // 賦值運算符 21 { 22 cout << "賦值函數Student1" << endl ; 23 this->a = t1.a ; 24 return *this; 25 } 26 }; 27 class Student2 28 { 29 public: 30 31 Student1 test ; 32 Student2(Student1 &t1){ //圖1結果 33 test = t1 ; 34 } 35 }; 36 int main() 37 { 38 39 Student1 A; 40 Student2 B(A); 41 42 }
代碼2:

1 #include<iostream> 2 3 using namespace std; 4 5 class Student1 { 6 public: 7 int a; 8 9 Student1() // 無參構造函數 10 { 11 cout << "默認構造函數Student1" << endl ; 12 } 13 14 Student1(const Student1& t1) // 拷貝構造函數 15 { 16 cout << "拷貝構造函數Student1" << endl ; 17 this->a = t1.a ; 18 } 19 20 Student1& operator = (const Student1& t1) // 賦值運算符 21 { 22 cout << "賦值函數Student1" << endl ; 23 this->a = t1.a ; 24 return *this; 25 } 26 }; 27 class Student2 28 { 29 public: 30 31 Student1 test ; 32 Student2(Student1 &t1):test(t1){} 33 }; 34 int main() 35 { 36 37 Student1 A; 38 Student2 B(A); 39 40 }
代碼1圖:
代碼2圖:
從圖中可以看出第一段代碼的計算結果調用了2次構造函數和1次賦值函數,而代碼2結果,調用了1次構造函數和1次拷貝函數。
當代碼量比較多的時候會發現,代碼2比代碼1效率更高。
歡迎大家關注我的微信公眾號「佛系師兄」,里面有更多技術文章。
比如
「反復研究好幾遍,我才發現關於 CMake 變量還可以這樣理解!」
更多好的文章會優先在里面不定期分享!打開微信客戶端,掃描下方二維碼即可關注!