C++學(xué)習(xí)筆記之初始化列表
創(chuàng)建一個(gè)類對(duì)象時(shí),編譯器通過(guò)調(diào)用構(gòu)造函數(shù),給類對(duì)象中各個(gè)成員變量賦初值:
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; };
但上述賦初值不能稱作類對(duì)象成員的初始化,因?yàn)闃?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)允許對(duì)成員變量進(jìn)行多次賦值 } private: int _year; int _month; int _day; };
而初始化列表能只能初始化一次。
一、用初始化列表初始化對(duì)象
1.初始化列表用法
初始化列表:以一個(gè)冒號(hào)開(kāi)始,接著是一個(gè)以逗號(hào)分隔的數(shù)據(jù)成員列表,每個(gè)"成員變量"后面跟一個(gè)放在括 號(hào)中的初始值或表達(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)初始化列表能只能初始化一次,多次初始化會(huì)報(bào)錯(cuò):
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; };
混用時(shí)初始化列表初始化和構(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; };
但混用時(shí)初始化列表初始化還是要遵循只能初始化一次成員變量的原則:
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成員變量、引用成員變量、沒(méi)有默認(rèn)構(gòu)造函數(shù)的自定義類型成員只能在初始化列表初始化。
①const成員變量必須在定義的時(shí)候初始化
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; };
②引用成員變量必須在定義的時(shí)候初始化
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; };
③沒(méi)有默認(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.無(wú)參默認(rèn)構(gòu)造函數(shù) //2.帶參全缺省的默認(rèn)構(gòu)造函數(shù) //3.我們不寫(xiě),編譯器自動(dòng)生成的默認(rèn)構(gòu)造函數(shù) A(int x)//不屬于以上任何一種,所以A類的對(duì)象沒(méi)有默認(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)//沒(méi)有默認(rèn)構(gòu)造函數(shù)的自定義類型成員變量必須在初始化列表進(jìn)行初始化 { _day = day; } private: int _year; int _month; int _day; A _a; };
const成員變量、引用成員變量、沒(méi)有默認(rèn)構(gòu)造函數(shù)的自定義類型成員變量必須在初始化列表內(nèi)初始化的原因:
①初始化列表是對(duì)象的成員變量定義的地方。
②對(duì)象的內(nèi)置類型成員變量在初始化列表定義時(shí)沒(méi)有要求必須初始化,因此既可以在初始化列表進(jìn)行初始化,也可以在構(gòu)造函數(shù)體內(nèi)初始化。
③而const成員變量、引用成員變量、沒(méi)有默認(rèn)構(gòu)造函數(shù)的自定義類型成員變量不能先定義再初始化,它們?cè)诔跏蓟斜韮?nèi)定義,并且必須在定義時(shí)就初始化,因此必須在初始化列表內(nèi)初始化。
(3) 盡量使用初始化列表初始化,因?yàn)椴还苁欠袷褂贸跏蓟斜?,雖然對(duì)于內(nèi)置類型沒(méi)有差別,但是對(duì)于自定義類型成員變量,一定會(huì)先使用初始化列表初始化。
為什么會(huì)先使用初始化列表初始化?
如下,Date類沒(méi)有默認(rèn)構(gòu)造函數(shù),因?yàn)?6行的構(gòu)造函數(shù)不屬于默認(rèn)構(gòu)造函數(shù)中的任意一種,在對(duì)Date類的對(duì)象d進(jìn)行初始化時(shí),會(huì)調(diào)用Date類的默認(rèn)構(gòu)造函數(shù),所以對(duì)象d的day實(shí)參12和hour實(shí)參12都沒(méi)有被傳進(jìn)去,_t作為Date類的自定義類型成員變量會(huì)調(diào)用Time類的默認(rèn)構(gòu)造函數(shù),_hour默認(rèn)傳參為0,因此打印_hour的值也為0,d的參數(shù)沒(méi)有傳成功:
#include<iostream> using namespace std; class Date; // 前置聲明 class Time { friend class Date; // 聲明日期類為時(shí)間類的友元類,則在日期類中就直接訪問(wèn)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)初始化時(shí),要把Date類的構(gòu)造函數(shù)的形參hour的值給d,那么就必須構(gòu)造一個(gè)Time類對(duì)象t,對(duì)該對(duì)象傳參傳hour,再使用賦值運(yùn)算符重載函數(shù)將對(duì)象t拷貝給_t:
#include<iostream> using namespace std; class Date; // 前置聲明 class Time { friend class Date; // 聲明日期類為時(shí)間類的友元類,則在日期類中就直接訪問(wèn)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)成員變量初始化的順序就是成員變量在類中的聲明次序,與初始化列表中的先后次序無(wú)關(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就會(huì)先初始化_a2,用_a1初始化_a2,由于此時(shí)_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)建一個(gè)臨時(shí)變量,再把臨時(shí)變量的值給d,那么d改變的是臨時(shí)變量的值,而不是i的值,因?yàn)槌绦驁?zhí)行完畢后,i的值并未發(fā)生改變。
如果d作為引用,那么必須加上const關(guān)鍵字進(jìn)行修飾,因?yàn)閐不是i的引用,是臨時(shí)變量的引用,而臨時(shí)變量具有常性,不允許引用權(quán)限放大。
int i = 0; const double& d = i;//d引用了臨時(shí)變量,臨時(shí)變量具有常性,所以d也必須具有常性
2.如何避免單參構(gòu)造函數(shù)初始化發(fā)生隱式類型轉(zhuǎn)換
正常的類對(duì)象初始化如下面的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對(duì)象 A aa2(aa1);//拷貝構(gòu)造,程序沒(méi)寫(xiě)拷貝構(gòu)造,編譯器會(huì)自動(dòng)生成拷貝構(gòu)造函數(shù),對(duì)內(nèi)置類型完成淺拷貝 A aa3 = 3;//單參數(shù)的構(gòu)造函數(shù),會(huì)發(fā)生隱式類型轉(zhuǎn)換 return 0; }
那么
A aa3 = 3;
是如何支持類型轉(zhuǎn)換的呢?
對(duì)于自定義類型A,aa3是A類型,3是整形。編譯器會(huì)先拿A構(gòu)造一個(gè)臨時(shí)對(duì)象temp,3作為參數(shù)傳給這個(gè)臨時(shí)對(duì)象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)造
不過(guò)現(xiàn)在的編譯器已經(jīng)優(yōu)化過(guò)了,會(huì)直接調(diào)用構(gòu)造函數(shù)A aa(3)。
如果不想讓單參數(shù)的構(gòu)造函數(shù)發(fā)生隱式類型轉(zhuǎn)換,可以使用explicit關(guān)鍵字修飾構(gòu)造函數(shù),表明該構(gòu)造函數(shù)是顯式的,而不是隱式的,就會(huì)避免發(fā)生不期望的類型轉(zhuǎn)換,使用場(chǎng)景如下:
#include<iostream> using namespace std; class A { public: A(int a) :_a(a) {} private: int _a; }; int main() { A aa1(1);//構(gòu)造aa1對(duì)象 A aa2(aa1);//拷貝構(gòu)造,程序沒(méi)寫(xiě)拷貝構(gòu)造,編譯器會(huì)自動(dòng)生成拷貝構(gòu)造函數(shù),對(duì)內(nèi)置類型完成淺拷貝 A aa3 = 'x';//先拿A構(gòu)造一個(gè)臨時(shí)對(duì)象temp,字符x作為參數(shù)傳給這個(gè)臨時(shí)對(duì)象temp,會(huì)發(fā)生隱式類型轉(zhuǎn)換,再拿aa3(temp)去拷貝構(gòu)造 return 0; }
aa3作為A類的對(duì)象,構(gòu)造時(shí)傳參應(yīng)該傳int型,但卻傳了char型,由于發(fā)生隱式類型轉(zhuǎn)換,因此編譯也沒(méi)毛病,但是它傳參就是不倫不類。這時(shí)候可以給A的構(gòu)造函數(shù)加上explicit聲明不讓該單參構(gòu)造函數(shù)發(fā)生隱式類型轉(zhuǎn)換,編譯就會(huì)報(bào)錯(cuò):
class A { public: explicit A(int a) :_a(a) {} private: int _a; };
這時(shí)候只能乖乖給aa3傳int型參數(shù)了。
三、匿名對(duì)象
1.匿名對(duì)象定義
沒(méi)有名字的對(duì)象叫做匿名對(duì)象,A(3)跟aa1和aa2相比少了個(gè)對(duì)象名,沒(méi)有名字,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)造匿名對(duì)象,生命周期只在這一行 return 0; }
F10調(diào)試:當(dāng)執(zhí)行完A(3)還沒(méi)執(zhí)行return 0時(shí),aa1和aa2的生命周期還沒(méi)有結(jié)束,不會(huì)調(diào)用析構(gòu)函數(shù),此時(shí)打印的析構(gòu)函數(shù)只能是匿名對(duì)象A(3)的析構(gòu)函數(shù):
所以A(3)這一行執(zhí)行完就調(diào)析構(gòu)函數(shù)了。
2.匿名對(duì)象應(yīng)用場(chǎng)景
假設(shè)有一個(gè)函數(shù)f,且A類的構(gòu)造函數(shù)全缺省:
#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)造匿名對(duì)象,生命周期只在這一行 return 0; }
調(diào)用f()函數(shù)時(shí),需要定義一個(gè)A類對(duì)象,才能調(diào)用A類函數(shù)f,這就需要寫(xiě)兩行:
int main() { A aa1(1);//生命周期在main函數(shù)內(nèi) A aa2(aa1);//生命周期在main函數(shù)內(nèi) A(3);//構(gòu)造匿名對(duì)象,生命周期只在這一行 A aa4;//需要定義一個(gè)A類對(duì)象,才能調(diào)用f aa4.f(); return 0; }
對(duì)象aa4 在main函數(shù)結(jié)束后才會(huì)銷毀。如果定義對(duì)象只是為了調(diào)用函數(shù),那么可以考慮直接定義一個(gè)匿名對(duì)象:
int main() { A aa1(1);//生命周期在main函數(shù)內(nèi) A aa2(aa1);//生命周期在main函數(shù)內(nèi) A(3);//構(gòu)造匿名對(duì)象,生命周期只在這一行 A aa4;//需要定義一個(gè)A類對(duì)象,才能調(diào)用f aa4.f(); A().f();//定義匿名對(duì)象來(lái)調(diào)用函數(shù)f() return 0; }
這個(gè)匿名對(duì)象就是為了調(diào)用函數(shù)f,這個(gè)匿名對(duì)象后邊也沒(méi)人用它,在當(dāng)前行調(diào)用完f()函數(shù)就銷毀了。
總結(jié)
到此這篇關(guān)于C++學(xué)習(xí)筆記之初始化列表的文章就介紹到這了,更多相關(guān)C++初始化列表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言超詳細(xì)分析多進(jìn)程的概念與使用
在一個(gè)項(xiàng)目中并發(fā)執(zhí)行任務(wù)時(shí)多數(shù)情況下都會(huì)選擇多線程,但有時(shí)候也會(huì)選擇多進(jìn)程,例如可以同時(shí)運(yùn)行n個(gè)記事本編輯不同文本,由一個(gè)命令跳轉(zhuǎn)到另外一個(gè)命令,或者使用不同進(jìn)程進(jìn)行協(xié)作2022-08-08C語(yǔ)言中switch語(yǔ)句基本用法實(shí)例
switch的中文翻譯是開(kāi)關(guān),顧名思義,開(kāi)關(guān)的作用就是控制連通或者中斷,在C語(yǔ)言中switch語(yǔ)句的作用也是大同小異,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中switch語(yǔ)句基本用法的相關(guān)資料,需要的朋友可以參考下2022-07-07C語(yǔ)言實(shí)現(xiàn)隊(duì)列的示例詳解
隊(duì)列是一種特殊的線性表,特殊之處在于它只允許在表的前端(head)進(jìn)行刪除操作,而在表的后端(tail)進(jìn)行插入操作。本文將用C語(yǔ)言實(shí)現(xiàn)隊(duì)列,感興趣的可以了解一下2022-06-06C語(yǔ)言中#define定義的標(biāo)識(shí)符和宏實(shí)例代碼
C語(yǔ)言中,可以用#define定義一個(gè)標(biāo)識(shí)符來(lái)表示一個(gè)常量,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中#define定義的標(biāo)識(shí)符和宏的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03C++實(shí)現(xiàn)高校人員信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)高校人員信息管理系統(tǒng)項(xiàng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06