C++學(xué)習(xí)筆記之初始化列表
創(chuàng)建一個類對象時,編譯器通過調(diào)用構(gòu)造函數(shù),給類對象中各個成員變量賦初值:
class Date { public: //構(gòu)造函數(shù) Date(int year = 2022, int month = 4, int day = 19) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
但上述賦初值不能稱作類對象成員的初始化,因為構(gòu)造函數(shù)體內(nèi)可以多次賦值:
class Date { public: //構(gòu)造函數(shù) Date(int year = 2022, int month = 4, int day = 19) { _year = year; _month = month; _day = day; _year = 2023;//構(gòu)造函數(shù)體內(nèi)允許對成員變量進(jìn)行多次賦值 } private: int _year; int _month; int _day; };
而初始化列表能只能初始化一次。
一、用初始化列表初始化對象
1.初始化列表用法
初始化列表:以一個冒號開始,接著是一個以逗號分隔的數(shù)據(jù)成員列表,每個"成員變量"后面跟一個放在括 號中的初始值或表達(dá)式。
class Date { public: //構(gòu)造函數(shù) Date(int year = 2022, int month = 4, int day = 19) :_year(year) //初始化列表初始化 ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; };
2.初始化列表特性
(1)初始化列表能只能初始化一次,多次初始化會報錯:
class Date { public: //構(gòu)造函數(shù) 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; };
編譯器也允許構(gòu)造函數(shù)賦初值和初始化列表初始化混用:
class Date { public: //構(gòu)造函數(shù) Date(int year = 2022, int month = 4, int day = 19) :_year(year) //兩者混用 ,_month(month) { _day = day; } private: int _year; int _month; int _day; };
混用時初始化列表初始化和構(gòu)造函數(shù)賦初值不沖突:
class Date { public: //構(gòu)造函數(shù) 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: //構(gòu)造函數(shù) 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成員變量、引用成員變量、沒有默認(rèn)構(gòu)造函數(shù)的自定義類型成員只能在初始化列表初始化。
①const成員變量必須在定義的時候初始化
class Date { public: //構(gòu)造函數(shù) Date(int year = 2022, int month = 4, int day = 19) : _year(year) , _month(month) , _n(2) //const成員變量必須使用初始化列表進(jìn)行初始化 { _day = day; //_n = 2; //const成員變量不能在函數(shù)體內(nèi)初始化 } private: int _year; int _month; int _day; const int _n = 1; };
②引用成員變量必須在定義的時候初始化
class Date { public: //構(gòu)造函數(shù) Date(int year = 2022, int month = 4, int day = 19) : _year(year) , _month(month) ,_ref(year)//引用成員變量要在初始化列表初始化 { _day = day; //_ref = year; //引用成員變量不能在函數(shù)體內(nèi)初始化 } private: int _year; int _month; int _day; int& _ref; };
③沒有默認(rèn)構(gòu)造函數(shù)的自定義類型成員變量
#include <iostream> using namespace std; class A { public: //默認(rèn)構(gòu)造函數(shù)是不用傳參就可以調(diào)用的構(gòu)造函數(shù),有3種: //1.無參默認(rèn)構(gòu)造函數(shù) //2.帶參全缺省的默認(rèn)構(gòu)造函數(shù) //3.我們不寫,編譯器自動生成的默認(rèn)構(gòu)造函數(shù) A(int x)//不屬于以上任何一種,所以A類的對象沒有默認(rèn)構(gòu)造函數(shù) { cout << "A(int x)" << endl; _x = x; } private: int _x; }; class Date { public: //構(gòu)造函數(shù) Date(int year = 2022, int month = 4, int day = 19) : _year(year) , _month(month) , _a(20)//沒有默認(rèn)構(gòu)造函數(shù)的自定義類型成員變量必須在初始化列表進(jìn)行初始化 { _day = day; } private: int _year; int _month; int _day; A _a; };
const成員變量、引用成員變量、沒有默認(rèn)構(gòu)造函數(shù)的自定義類型成員變量必須在初始化列表內(nèi)初始化的原因:
①初始化列表是對象的成員變量定義的地方。
②對象的內(nèi)置類型成員變量在初始化列表定義時沒有要求必須初始化,因此既可以在初始化列表進(jìn)行初始化,也可以在構(gòu)造函數(shù)體內(nèi)初始化。
③而const成員變量、引用成員變量、沒有默認(rèn)構(gòu)造函數(shù)的自定義類型成員變量不能先定義再初始化,它們在初始化列表內(nèi)定義,并且必須在定義時就初始化,因此必須在初始化列表內(nèi)初始化。
(3) 盡量使用初始化列表初始化,因為不管是否使用初始化列表,雖然對于內(nèi)置類型沒有差別,但是對于自定義類型成員變量,一定會先使用初始化列表初始化。
為什么會先使用初始化列表初始化?
如下,Date類沒有默認(rèn)構(gòu)造函數(shù),因為26行的構(gòu)造函數(shù)不屬于默認(rèn)構(gòu)造函數(shù)中的任意一種,在對Date類的對象d進(jìn)行初始化時,會調(diào)用Date類的默認(rèn)構(gòu)造函數(shù),所以對象d的day實參12和hour實參12都沒有被傳進(jìn)去,_t作為Date類的自定義類型成員變量會調(diào)用Time類的默認(rèn)構(gòu)造函數(shù),_hour默認(rèn)傳參為0,因此打印_hour的值也為0,d的參數(shù)沒有傳成功:
#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類的構(gòu)造函數(shù)不使用初始化列表進(jìn)行初始化,使用函數(shù)體內(nèi)初始化時,要把Date類的構(gòu)造函數(shù)的形參hour的值給d,那么就必須構(gòu)造一個Time類對象t,對該對象傳參傳hour,再使用賦值運(yùn)算符重載函數(shù)將對象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: //自定義類型,不使用初始化列表,就需要使用構(gòu)造函數(shù) + operator= Date(int day, int hour) { //函數(shù)體內(nèi)初始化 Time t(hour);//調(diào)用Time類的構(gòu)造函數(shù) _t = t; _day = day; } private: int _day; Time _t; }; int main() { Date d(12, 12); cout << 4 << endl; return 0; }
這還不如直接使用使用初始化列表初始化呢,還不需要賦值運(yùn)算符重載函數(shù):
class Date { public: //自定義類型,使用初始化列表,只需要構(gòu)造函數(shù) Date(int day, int hour) :_t(hour) { _day = day; } private: int _day; Time _t; };
因此,建議盡量直接使用初始化列表進(jìn)行初始化。
(4)成員變量初始化的順序就是成員變量在類中的聲明次序,與初始化列表中的先后次序無關(guān)。
如下代碼,類成員變量中先聲明了_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還是隨機(jī)值,因此_a2的值也是隨機(jī)值,_a1使用a的值1進(jìn)行初始化,因此,_a1的值為1:
所以,建議類中的成員變量聲明的順序和初始化列表中初始化的順序一致。
二、explicit關(guān)鍵字
1.內(nèi)置類型的隱式轉(zhuǎn)換
int i = 0; double d = i;//隱式類型轉(zhuǎn)換
根據(jù)監(jiān)視可以看出:
double d = i;并不是將i直接賦值給d,而是用i創(chuàng)建一個臨時變量,再把臨時變量的值給d,那么d改變的是臨時變量的值,而不是i的值,因為程序執(zhí)行完畢后,i的值并未發(fā)生改變。
如果d作為引用,那么必須加上const關(guān)鍵字進(jìn)行修飾,因為d不是i的引用,是臨時變量的引用,而臨時變量具有常性,不允許引用權(quán)限放大。
int i = 0; const double& d = i;//d引用了臨時變量,臨時變量具有常性,所以d也必須具有常性
2.如何避免單參構(gòu)造函數(shù)初始化發(fā)生隱式類型轉(zhuǎn)換
正常的類對象初始化如下面的aa1,也可以使用拷貝構(gòu)造初始化,如aa2。由于c++支持隱式類型轉(zhuǎn)換,因此也支持單參數(shù)構(gòu)造函數(shù)初始化,如aa3:
#include<iostream> using namespace std; class A { public : A(int a) :_a(a) {} private: int _a; }; int main() { A aa1(1);//構(gòu)造aa1對象 A aa2(aa1);//拷貝構(gòu)造,程序沒寫拷貝構(gòu)造,編譯器會自動生成拷貝構(gòu)造函數(shù),對內(nèi)置類型完成淺拷貝 A aa3 = 3;//單參數(shù)的構(gòu)造函數(shù),會發(fā)生隱式類型轉(zhuǎn)換 return 0; }
那么
A aa3 = 3;
是如何支持類型轉(zhuǎn)換的呢?
對于自定義類型A,aa3是A類型,3是整形。編譯器會先拿A構(gòu)造一個臨時對象temp,3作為參數(shù)傳給這個臨時對象temp,再拿aa3(temp)去拷貝構(gòu)造,發(fā)生隱式類型轉(zhuǎn)換,即先構(gòu)造,再拷貝構(gòu)造:
//A aa3 = 3; A temp(3); //先構(gòu)造 A aa3(temp); //再拷貝構(gòu)造
不過現(xiàn)在的編譯器已經(jīng)優(yōu)化過了,會直接調(diào)用構(gòu)造函數(shù)A aa(3)。
如果不想讓單參數(shù)的構(gòu)造函數(shù)發(fā)生隱式類型轉(zhuǎn)換,可以使用explicit關(guān)鍵字修飾構(gòu)造函數(shù),表明該構(gòu)造函數(shù)是顯式的,而不是隱式的,就會避免發(fā)生不期望的類型轉(zhuǎn)換,使用場景如下:
#include<iostream> using namespace std; class A { public: A(int a) :_a(a) {} private: int _a; }; int main() { A aa1(1);//構(gòu)造aa1對象 A aa2(aa1);//拷貝構(gòu)造,程序沒寫拷貝構(gòu)造,編譯器會自動生成拷貝構(gòu)造函數(shù),對內(nèi)置類型完成淺拷貝 A aa3 = 'x';//先拿A構(gòu)造一個臨時對象temp,字符x作為參數(shù)傳給這個臨時對象temp,會發(fā)生隱式類型轉(zhuǎn)換,再拿aa3(temp)去拷貝構(gòu)造 return 0; }
aa3作為A類的對象,構(gòu)造時傳參應(yīng)該傳int型,但卻傳了char型,由于發(fā)生隱式類型轉(zhuǎn)換,因此編譯也沒毛病,但是它傳參就是不倫不類。這時候可以給A的構(gòu)造函數(shù)加上explicit聲明不讓該單參構(gòu)造函數(shù)發(fā)生隱式類型轉(zhuǎn)換,編譯就會報錯:
class A { public: explicit A(int a) :_a(a) {} private: int _a; };
這時候只能乖乖給aa3傳int型參數(shù)了。
三、匿名對象
1.匿名對象定義
沒有名字的對象叫做匿名對象,A(3)跟aa1和aa2相比少了個對象名,沒有名字,aa1和aa2的生命周期在main函數(shù)內(nèi),A(3)的生命周期只在當(dāng)前行:
#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函數(shù)內(nèi) A aa2(aa1);//生命周期在main函數(shù)內(nèi) A(3);//構(gòu)造匿名對象,生命周期只在這一行 return 0; }
F10調(diào)試:當(dāng)執(zhí)行完A(3)還沒執(zhí)行return 0時,aa1和aa2的生命周期還沒有結(jié)束,不會調(diào)用析構(gòu)函數(shù),此時打印的析構(gòu)函數(shù)只能是匿名對象A(3)的析構(gòu)函數(shù):
所以A(3)這一行執(zhí)行完就調(diào)析構(gòu)函數(shù)了。
2.匿名對象應(yīng)用場景
假設(shè)有一個函數(shù)f,且A類的構(gòu)造函數(shù)全缺?。?/p>
#include<iostream> using namespace std; class A { public: A(int a = 0)//構(gòu)造函數(shù)全缺省 :_a(a) { cout << "A(int a):"<< a << endl; } A(const A& aa) { cout << "A(const A&)" << endl; } ~A() { cout << "~A()" << endl; } void f()//f函數(shù) { cout << "f()" << endl; } private: int _a; }; int main() { A aa1(1);//生命周期在main函數(shù)內(nèi) A aa2(aa1);//生命周期在main函數(shù)內(nèi) A(3);//構(gòu)造匿名對象,生命周期只在這一行 return 0; }
調(diào)用f()函數(shù)時,需要定義一個A類對象,才能調(diào)用A類函數(shù)f,這就需要寫兩行:
int main() { A aa1(1);//生命周期在main函數(shù)內(nèi) A aa2(aa1);//生命周期在main函數(shù)內(nèi) A(3);//構(gòu)造匿名對象,生命周期只在這一行 A aa4;//需要定義一個A類對象,才能調(diào)用f aa4.f(); return 0; }
對象aa4 在main函數(shù)結(jié)束后才會銷毀。如果定義對象只是為了調(diào)用函數(shù),那么可以考慮直接定義一個匿名對象:
int main() { A aa1(1);//生命周期在main函數(shù)內(nèi) A aa2(aa1);//生命周期在main函數(shù)內(nèi) A(3);//構(gòu)造匿名對象,生命周期只在這一行 A aa4;//需要定義一個A類對象,才能調(diào)用f aa4.f(); A().f();//定義匿名對象來調(diào)用函數(shù)f() return 0; }
這個匿名對象就是為了調(diào)用函數(shù)f,這個匿名對象后邊也沒人用它,在當(dāng)前行調(diào)用完f()函數(shù)就銷毀了。
總結(jié)
到此這篇關(guān)于C++學(xué)習(xí)筆記之初始化列表的文章就介紹到這了,更多相關(guān)C++初始化列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!