您好,登錄后才能下訂單哦!
這篇文章主要講解了“C++中怎么初始化列表”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C++中怎么初始化列表”吧!
創建一個類對象時,編譯器通過調用構造函數,給類對象中各個成員變量賦初值:
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
但上述賦初值不能稱作類對象成員的初始化,因為構造函數體內可以多次賦值:
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) { _year = year; _month = month; _day = day; _year = 2023;//構造函數體內允許對成員變量進行多次賦值 } private: int _year; int _month; int _day; };
而初始化列表能只能初始化一次。
初始化列表:以一個冒號開始,接著是一個以逗號分隔的數據成員列表,每個"成員變量"后面跟一個放在括 號中的初始值或表達式。
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) :_year(year) //初始化列表初始化 ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; };
(1)初始化列表能只能初始化一次,多次初始化會報錯:
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) :_year(year) ,_month(month) ,_day(day) ,_month(month) //初始化列表多次初始化 {} private: int _year; int _month; int _day; };
編譯器也允許構造函數賦初值和初始化列表初始化混用:
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) :_year(year) //兩者混用 ,_month(month) { _day = day; } private: int _year; int _month; int _day; };
混用時初始化列表初始化和構造函數賦初值不沖突:
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) : _year(year) //兩者不沖突 , _month(month) { _day = day; _year = 2023; } private: int _year; int _month; int _day; };
但混用時初始化列表初始化還是要遵循只能初始化一次成員變量的原則:
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) : _year(year) //初始化列表初始化 , _month(month) , _year(2023) //_year在初始化列表里被初始化了兩次,不允許 { _day = day; } private: int _year; int _month; int _day; };
(2)const成員變量、引用成員變量、沒有默認構造函數的自定義類型成員只能在初始化列表初始化。
①const成員變量必須在定義的時候初始化
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) : _year(year) , _month(month) , _n(2) //const成員變量必須使用初始化列表進行初始化 { _day = day; //_n = 2; //const成員變量不能在函數體內初始化 } private: int _year; int _month; int _day; const int _n = 1; };
②引用成員變量必須在定義的時候初始化
class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) : _year(year) , _month(month) ,_ref(year)//引用成員變量要在初始化列表初始化 { _day = day; //_ref = year; //引用成員變量不能在函數體內初始化 } private: int _year; int _month; int _day; int& _ref; };
③沒有默認構造函數的自定義類型成員變量
#include <iostream> using namespace std; class A { public: //默認構造函數是不用傳參就可以調用的構造函數,有3種: //1.無參默認構造函數 //2.帶參全缺省的默認構造函數 //3.我們不寫,編譯器自動生成的默認構造函數 A(int x)//不屬于以上任何一種,所以A類的對象沒有默認構造函數 { cout << "A(int x)" << endl; _x = x; } private: int _x; }; class Date { public: //構造函數 Date(int year = 2022, int month = 4, int day = 19) : _year(year) , _month(month) , _a(20)//沒有默認構造函數的自定義類型成員變量必須在初始化列表進行初始化 { _day = day; } private: int _year; int _month; int _day; A _a; };
const成員變量、引用成員變量、沒有默認構造函數的自定義類型成員變量必須在初始化列表內初始化的原因:
①初始化列表是對象的成員變量定義的地方。
②對象的內置類型成員變量在初始化列表定義時沒有要求必須初始化,因此既可以在初始化列表進行初始化,也可以在構造函數體內初始化。
③而const成員變量、引用成員變量、沒有默認構造函數的自定義類型成員變量不能先定義再初始化,它們在初始化列表內定義,并且必須在定義時就初始化,因此必須在初始化列表內初始化。
(3) 盡量使用初始化列表初始化,因為不管是否使用初始化列表,雖然對于內置類型沒有差別,但是對于自定義類型成員變量,一定會先使用初始化列表初始化。
為什么會先使用初始化列表初始化?
如下,Date類沒有默認構造函數,因為26行的構造函數不屬于默認構造函數中的任意一種,在對Date類的對象d進行初始化時,會調用Date類的默認構造函數,所以對象d的day實參12和hour實參12都沒有被傳進去,_t作為Date類的自定義類型成員變量會調用Time類的默認構造函數,_hour默認傳參為0,因此打印_hour的值也為0,d的參數沒有傳成功:
#include<iostream> using namespace std; class Date; // 前置聲明 class Time { friend class Date; // 聲明日期類為時間類的友元類,則在日期類中就直接訪問Time類中的私有成員變量 public: Time(int hour = 0) : _hour(hour) { cout << _hour << endl; } private: int _hour; }; class Date { public: Date(int day, int hour) {} private: int _day; Time _t; }; int main() { Date d(12, 12); return 0; }
假如Date類的構造函數不使用初始化列表進行初始化,使用函數體內初始化時,要把Date類的構造函數的形參hour的值給d,那么就必須構造一個Time類對象t,對該對象傳參傳hour,再使用賦值運算符重載函數將對象t拷貝給_t:
#include<iostream> using namespace std; class Date; // 前置聲明 class Time { friend class Date; // 聲明日期類為時間類的友元類,則在日期類中就直接訪問Time類中的私有成員變量 public: Time(int hour = 0) : _hour(hour) { cout << _hour << endl; } private: int _hour; }; class Date { public: //自定義類型,不使用初始化列表,就需要使用構造函數 + operator= Date(int day, int hour) { //函數體內初始化 Time t(hour);//調用Time類的構造函數 _t = t; _day = day; } private: int _day; Time _t; }; int main() { Date d(12, 12); cout << 4 << endl; return 0; }
這還不如直接使用使用初始化列表初始化呢,還不需要賦值運算符重載函數:
class Date { public: //自定義類型,使用初始化列表,只需要構造函數 Date(int day, int hour) :_t(hour) { _day = day; } private: int _day; Time _t; };
因此,建議盡量直接使用初始化列表進行初始化。
(4)成員變量初始化的順序就是成員變量在類中的聲明次序,與初始化列表中的先后次序無關。
如下代碼,類成員變量中先聲明了_a2,再聲明了_a1,因此初始化的順序是先初始化_a2,再初始化_a1:
#include <iostream> using namespace std; class A { public: A(int a) : _a1(a) , _a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2;//先聲明_a2 int _a1;//后聲明_a1 }; int main() { A aa(1); aa.Print(); }
先聲明_a2就會先初始化_a2,用_a1初始化_a2,由于此時_a1還是隨機值,因此_a2的值也是隨機值,_a1使用a的值1進行初始化,因此,_a1的值為1:
所以,建議類中的成員變量聲明的順序和初始化列表中初始化的順序一致。
int i = 0; double d = i;//隱式類型轉換
根據監視可以看出:
double d = i;并不是將i直接賦值給d,而是用i創建一個臨時變量,再把臨時變量的值給d,那么d改變的是臨時變量的值,而不是i的值,因為程序執行完畢后,i的值并未發生改變。
如果d作為引用,那么必須加上const關鍵字進行修飾,因為d不是i的引用,是臨時變量的引用,而臨時變量具有常性,不允許引用權限放大。
int i = 0; const double& d = i;//d引用了臨時變量,臨時變量具有常性,所以d也必須具有常性
正常的類對象初始化如下面的aa1,也可以使用拷貝構造初始化,如aa2。由于c++支持隱式類型轉換,因此也支持單參數構造函數初始化,如aa3:
#include<iostream> using namespace std; class A { public : A(int a) :_a(a) {} private: int _a; }; int main() { A aa1(1);//構造aa1對象 A aa2(aa1);//拷貝構造,程序沒寫拷貝構造,編譯器會自動生成拷貝構造函數,對內置類型完成淺拷貝 A aa3 = 3;//單參數的構造函數,會發生隱式類型轉換 return 0; }
那么
A aa3 = 3;
是如何支持類型轉換的呢?
對于自定義類型A,aa3是A類型,3是整形。編譯器會先拿A構造一個臨時對象temp,3作為參數傳給這個臨時對象temp,再拿aa3(temp)去拷貝構造,發生隱式類型轉換,即先構造,再拷貝構造:
//A aa3 = 3; A temp(3); //先構造 A aa3(temp); //再拷貝構造
不過現在的編譯器已經優化過了,會直接調用構造函數A aa(3)。
如果不想讓單參數的構造函數發生隱式類型轉換,可以使用explicit關鍵字修飾構造函數,表明該構造函數是顯式的,而不是隱式的,就會避免發生不期望的類型轉換,使用場景如下:
#include<iostream> using namespace std; class A { public: A(int a) :_a(a) {} private: int _a; }; int main() { A aa1(1);//構造aa1對象 A aa2(aa1);//拷貝構造,程序沒寫拷貝構造,編譯器會自動生成拷貝構造函數,對內置類型完成淺拷貝 A aa3 = 'x';//先拿A構造一個臨時對象temp,字符x作為參數傳給這個臨時對象temp,會發生隱式類型轉換,再拿aa3(temp)去拷貝構造 return 0; }
aa3作為A類的對象,構造時傳參應該傳int型,但卻傳了char型,由于發生隱式類型轉換,因此編譯也沒毛病,但是它傳參就是不倫不類。這時候可以給A的構造函數加上explicit聲明不讓該單參構造函數發生隱式類型轉換,編譯就會報錯:
class A { public: explicit A(int a) :_a(a) {} private: int _a; };
這時候只能乖乖給aa3傳int型參數了。
沒有名字的對象叫做匿名對象,A(3)跟aa1和aa2相比少了個對象名,沒有名字,aa1和aa2的生命周期在main函數內,A(3)的生命周期只在當前行:
#include<iostream> using namespace std; class A { public: explicit A(int a) :_a(a) { cout << "A(int a):"<< a << endl; } A(const A& aa) { cout << "A(const A&)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { A aa1(1);//生命周期在main函數內 A aa2(aa1);//生命周期在main函數內 A(3);//構造匿名對象,生命周期只在這一行 return 0; }
F10調試:當執行完A(3)還沒執行return 0時,aa1和aa2的生命周期還沒有結束,不會調用析構函數,此時打印的析構函數只能是匿名對象A(3)的析構函數:
所以A(3)這一行執行完就調析構函數了。
假設有一個函數f,且A類的構造函數全缺省:
#include<iostream> using namespace std; class A { public: A(int a = 0)//構造函數全缺省 :_a(a) { cout << "A(int a):"<< a << endl; } A(const A& aa) { cout << "A(const A&)" << endl; } ~A() { cout << "~A()" << endl; } void f()//f函數 { cout << "f()" << endl; } private: int _a; }; int main() { A aa1(1);//生命周期在main函數內 A aa2(aa1);//生命周期在main函數內 A(3);//構造匿名對象,生命周期只在這一行 return 0; }
調用f()函數時,需要定義一個A類對象,才能調用A類函數f,這就需要寫兩行:
int main() { A aa1(1);//生命周期在main函數內 A aa2(aa1);//生命周期在main函數內 A(3);//構造匿名對象,生命周期只在這一行 A aa4;//需要定義一個A類對象,才能調用f aa4.f(); return 0; }
對象aa4 在main函數結束后才會銷毀。如果定義對象只是為了調用函數,那么可以考慮直接定義一個匿名對象:
int main() { A aa1(1);//生命周期在main函數內 A aa2(aa1);//生命周期在main函數內 A(3);//構造匿名對象,生命周期只在這一行 A aa4;//需要定義一個A類對象,才能調用f aa4.f(); A().f();//定義匿名對象來調用函數f() return 0; }
這個匿名對象就是為了調用函數f,這個匿名對象后邊也沒人用它,在當前行調用完f()函數就銷毀了。
感謝各位的閱讀,以上就是“C++中怎么初始化列表”的內容了,經過本文的學習后,相信大家對C++中怎么初始化列表這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。