C++之談?wù)剺?gòu)造函數(shù)的初始化列表
一、引入
- 我們知道,對(duì)于下面這個(gè)類A的成員變量
_a1和_a2屬于【聲明】,還沒(méi)有在內(nèi)存中為其開(kāi)辟出一塊空間以供存放,真正開(kāi)出空間則是在【定義】的時(shí)候,那何時(shí)定義呢?也就是使用這個(gè)類A去實(shí)例化出對(duì)象的時(shí)候 - 這個(gè)對(duì)象的空間被開(kāi)出來(lái)了,難道里面的成員變量就一定開(kāi)出空間了嗎?這一點(diǎn)我們很難去通過(guò)調(diào)試觀察
class A {
public:
int _a1; //聲明
int _a2;
};
int main(void)
{
A aa; // 對(duì)象整體的定義,每個(gè)成員什么時(shí)候定義?
return 0;
}
如果現(xiàn)在我在類A中加上一個(gè)const成員變量的話,初始化的時(shí)候似乎就出現(xiàn)了問(wèn)題
const int _x;

- 在搞清楚上面的問(wèn)題之前你要明白
const修飾的變量有哪些特點(diǎn)
const int i;
- 可以看到我在這里定義了一個(gè)整型變量i,它前面是用
const進(jìn)行修飾的,不過(guò)編譯后報(bào)出了錯(cuò)誤說(shuō)【必須初始化常量對(duì)象】,因?yàn)閷?duì)于const修飾的變量在聲明的時(shí)候是必須要去進(jìn)行初始化的,也就是要給到一個(gè)值

現(xiàn)在我們就可以來(lái)聊聊有關(guān)上面的成員變量
_x為什么沒(méi)有被初始化的原因了??
- 之前有講過(guò),若是我們自己不去實(shí)現(xiàn)構(gòu)造函數(shù)的話,類中會(huì)默認(rèn)提供一個(gè)構(gòu)造函數(shù)來(lái)初始化成員變量,對(duì)于【內(nèi)置類型】的變量不會(huì)處理,對(duì)【自定義類型】的變量會(huì)去調(diào)用它的構(gòu)造函數(shù)。那么對(duì)于這里的
_a1、_a2、_x都屬于內(nèi)置類型的數(shù)據(jù),所以編譯器不會(huì)理睬,可是呢const修飾的變量又必須要初始化,這個(gè)時(shí)候該怎么辦呢╮(╯▽╰)╭
??有同學(xué)說(shuō):這還不簡(jiǎn)單,給個(gè)缺省值不就好了
這位同學(xué)說(shuō)的不錯(cuò),這個(gè)辦法確實(shí)是可以解決我們現(xiàn)在的問(wèn)題,因?yàn)镃++11里面為內(nèi)置類型不初始化打了一個(gè)補(bǔ)丁,在聲明的位置給到一個(gè)初始化值,就可以很好地防止編譯器不處理的問(wèn)題

但是現(xiàn)在我想問(wèn)一個(gè)問(wèn)題:如果不使用這個(gè)辦法呢?你有其他方法嗎?難道C++11以前就那它沒(méi)辦法了嗎?
底下的同學(xué)確實(shí)想不出什么很好的解決辦法,于是這個(gè)時(shí)候就要使用到本模塊要學(xué)習(xí)的【初始化列表】了
二、初始化的概念區(qū)分
- 在了解【初始化列表】前,你要先知道初始化的真正含義是什么
概念:在創(chuàng)建對(duì)象時(shí),編譯器通過(guò)調(diào)用構(gòu)造函數(shù),給對(duì)象中各個(gè)成員變量一個(gè)合適的初始值。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
- 上面這個(gè)Date類是我們之前寫過(guò)的,這里有一個(gè)它的有參構(gòu)造函數(shù),雖然在這個(gè)構(gòu)造函數(shù)調(diào)用之后,對(duì)象中已經(jīng)有了一個(gè)初始值,但是不能將其稱為對(duì)對(duì)象中成員變量的初始化。構(gòu)造函數(shù)體中的語(yǔ)句只能將其稱為【賦初值】,而不能稱作初始化。因?yàn)槌跏蓟荒艹跏蓟淮?,?strong>構(gòu)造函數(shù)體內(nèi)可以多次賦值。
三、語(yǔ)法格式及使用
【初始化列表】:以一個(gè)冒號(hào)開(kāi)始,接著是一個(gè)以逗號(hào)分隔的數(shù)據(jù)成員列表,每個(gè)"成員變量"后面跟一個(gè)放在括號(hào)中的初始值或表達(dá)式
- 下面就是它的具體用法,這樣便可以通過(guò)外界傳入一些參數(shù)對(duì)年、月、日進(jìn)行初始化
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d(2023, 3, 30);
return 0;
}
可以通過(guò)調(diào)試來(lái)觀察一下它到底是怎么走的

接下去我再來(lái)說(shuō)說(shuō)這一塊的難點(diǎn)所在,準(zhǔn)備好頭腦風(fēng)暴??
- 還是看回到我們上面的這個(gè)類A,知道了【初始化列表】這個(gè)東西,此時(shí)就不需要再聲明的部分給缺省值了,直接使用初始化列表即可。不過(guò)可以看到,對(duì)于
_a1和_a2我給到了缺省值,寫了初始化列表后,它們還會(huì)被初始化嗎?
class A {
public:
A()
:_x(1)
{}
private:
int _a1 = 1; //聲明
int _a2 = 1;
const int _x;
};
也通過(guò)調(diào)試來(lái)看一下

- 可以看到,即使在初始化列表沒(méi)有給到
_a1和_a2的初始化,還是會(huì)通過(guò)給到的默認(rèn)缺省值去進(jìn)行一個(gè)初始化。根據(jù)上面所學(xué),我給出以下的結(jié)論 - 哪個(gè)對(duì)象調(diào)用構(gòu)造函數(shù),初始化列表是它所有成員變量定義的位置
- 不管是否顯式在初始化列表寫,編譯器都會(huì)為每個(gè)變量在初始化列表進(jìn)行初始化
好,接下去難度升級(jí),請(qǐng)問(wèn)初始化列表修改成這樣后三個(gè)成員變量初始化后的結(jié)果會(huì)是什么呢? 會(huì)是1、2、1嗎?
class A {
public:
A()
:_x(1)
,_a2(1)
{}
private:
int _a1 = 1; //聲明
int _a2 = 2;
const int _x;
};
一樣通過(guò)調(diào)試來(lái)看看

- 可以觀察到,最后初始化完后的結(jié)果為1、1、1,最令你困惑的應(yīng)該就是這個(gè)
_a2了,因?yàn)槲以诼暶鞯臅r(shí)候給到了缺省值,然后初始化列表去進(jìn)行定義的時(shí)候又去進(jìn)行了一次初始化,最后的結(jié)果以初始化列表的方式為主
這里要明確的一個(gè)概念是,缺省參數(shù)只是一個(gè)備份,若是我們沒(méi)有去給到值初始化的話,編譯器就會(huì)使用這個(gè)初始值,若是我們自己給到了明確的值的話,不會(huì)去使用這個(gè)缺省值了【如果不清楚看看C++缺省參數(shù)】
接下去難度繼續(xù)升級(jí),請(qǐng)問(wèn)下面這樣初始化后的結(jié)果是多少?
- 可以看到對(duì)于構(gòu)造函數(shù)我不僅寫了【初始化列表】,而且在函數(shù)體內(nèi)部還對(duì)
_a1和_a2進(jìn)行了++和- -,那此時(shí)會(huì)有什么變化呢?
class A {
public:
A()
:_x(1)
,_a2(1)
{
_a1++;
_a2--;
}
private:
int _a1 = 1; //聲明
int _a2 = 2;
const int _x;
};
如果對(duì)于上面的原理搞清楚了,那看這個(gè)就相當(dāng)于是再鞏固了一遍。也是一樣,無(wú)論是否給到缺省值都會(huì)去初始化列表走一遍,若是構(gòu)造函數(shù)內(nèi)部有語(yǔ)句的話就會(huì)執(zhí)行

四、注意事項(xiàng)【?】
清楚了初始化列表該如何使用,接下去我們來(lái)說(shuō)說(shuō)其相關(guān)的注意事項(xiàng)
- 每個(gè)成員變量在初始化列表中只能出現(xiàn)一次(初始化只能初始化一次)
- 可以看到,若是一個(gè)成員變量在初始化列表的地方出現(xiàn)了兩次,編譯器在編譯的時(shí)候就會(huì)報(bào)出【xxx已初始化】

- 類中包含以下成員,必須放在初始化列表位置進(jìn)行初始化:
const成員變量
- 這個(gè)在前面已經(jīng)說(shuō)到過(guò)了,
const修飾的成員變量和構(gòu)造函數(shù)對(duì)于內(nèi)置類型不做處理產(chǎn)生了一個(gè)沖突,因此祖師爺就提出了【初始化列表】這個(gè)概念
引用成員變量
- 第二點(diǎn)就是對(duì)于引用成員變量,如果有點(diǎn)忘記了看看C++引用通過(guò)編譯可以看出,這個(gè)引用型成員變量
_z需要被初始化,它必須要引用一個(gè)值

沒(méi)有默認(rèn)構(gòu)造的自定義類型成員(寫了有參構(gòu)造編譯器就不會(huì)提供默認(rèn)構(gòu)造)
- 此時(shí),我又寫了一個(gè)類B,將它定義出的對(duì)象作為類A的成員變量,在類B中,有一個(gè)無(wú)參的默認(rèn)構(gòu)造,也寫了相關(guān)的初始化列表去初始化
_b
class B {
public:
B()
:_b(0)
{}
private:
int _b;
};
class A {
public:
A()
:_x(1)
,_a1(3)
,_a2(1)
,_z(_a1)
{
_a1++;
_a2--;
}
private:
int _a1 = 1; //聲明
int _a2 = 2;
const int _x;
int& _z;
B _bb;
};
- 通過(guò)調(diào)試來(lái)觀察就可以看到,完全符合我們前面所學(xué)的知識(shí),若是當(dāng)前類中有自定義類型的成員變量,那在為其進(jìn)行初始化的時(shí)候會(huì)去調(diào)用它的默認(rèn)構(gòu)造函數(shù)

- 但是現(xiàn)在我對(duì)這個(gè)構(gòu)造函數(shù)做了一些改動(dòng),將其變?yōu)榱擞袇⒌臉?gòu)造函數(shù),此時(shí)編譯時(shí)就報(bào)出了【沒(méi)有合適的默認(rèn)構(gòu)造函數(shù)可用】
- 我們知道默認(rèn)構(gòu)造有:無(wú)參、全缺省和編譯器自動(dòng)生成的,都是不需要我們手動(dòng)去調(diào)的??梢钥吹饺羰俏以谶@里將其改為全缺省的話,就不會(huì)出問(wèn)題了,因?yàn)樗鼘儆谀J(rèn)構(gòu)造函數(shù)

??那對(duì)于有參構(gòu)造該如何去初始化呢?
還是可以利用到我們的【初始化列表】

通過(guò)調(diào)試來(lái)看看編譯器是如何走的

- 盡量使用初始化列表初始化,因?yàn)椴还苣闶欠袷褂贸跏蓟斜?,?duì)于自定義類型成員變量,一定會(huì)先使用初始化列表初始化
看完了上面這一種,我們?cè)賮?lái)看看稍微復(fù)雜一些的自定義類型是否也遵循這個(gè)規(guī)則
- 也就是我們之前寫過(guò)的Stack和MyQueue類
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10) //全缺省構(gòu)造
{
cout << "Stack()構(gòu)造函數(shù)調(diào)用" << endl;
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申請(qǐng)空間失敗");
return;
}
_size = 0;
_capacity = capacity;
}
//....
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
- 此處我們主要觀察Stack類的構(gòu)造函數(shù),因?yàn)樵贛yQueue中我沒(méi)有寫構(gòu)造函數(shù),為的就是使用它默認(rèn)生成的構(gòu)造函數(shù)去進(jìn)行初始化。對(duì)于【內(nèi)置類型】不做處理,不過(guò)我這里給到了一個(gè)缺省值,對(duì)于【自定義類型】會(huì)去調(diào)用它的默認(rèn)構(gòu)造
class MyQueue{
public:
//默認(rèn)生成構(gòu)造函數(shù)
private:
Stack _pushST;
Stack _popST;
size_t _t = 1;
};
int main(void)
{
MyQueue mq;
return 0;
}
可能讀者有所忘卻,我們?cè)偻ㄟ^(guò)調(diào)試來(lái)看一下

- 可以觀察到在初始化MyQueue類的對(duì)象時(shí),因?yàn)閮?nèi)部有兩個(gè)Stack類型的對(duì)象,所以就會(huì)去調(diào)用兩次Stack類默認(rèn)構(gòu)造來(lái)進(jìn)行初始化
- 那此時(shí)我若是將這個(gè)默認(rèn)構(gòu)造(全缺省構(gòu)造)改為有參構(gòu)造嗎,它還調(diào)得動(dòng)嗎?
Stack(size_t capacity)
- 可以看到,此時(shí)就報(bào)出了我們上面有類似遇到過(guò)的【無(wú)法引用默認(rèn)構(gòu)造函數(shù)】,為什么呢?原因就在于我們寫了,編譯器自動(dòng)生成的也就不存在了,但是我又沒(méi)有傳入對(duì)應(yīng)的參數(shù)

- 此時(shí)就可以使用到我們本模塊所學(xué)習(xí)的【初始化列表】了,將需要定義的值放在初始化列表,相當(dāng)于就是為Stack類傳入了一個(gè)有參構(gòu)造的參數(shù),不過(guò)對(duì)于沒(méi)有寫在這里的
_t,依舊會(huì)使用我給到的初始值1
MyQueue()
:_pushST(10)
,_popST(10)
{}
可以通過(guò)調(diào)試再來(lái)看看

- 當(dāng)然,如果你覺(jué)得不想要這個(gè)固定的10作為棧容量的話,也可以將這個(gè)MyQueue的構(gòu)造函數(shù)設(shè)定為有參,自己傳遞進(jìn)去也是可以的

- 最后再來(lái)看一下無(wú)參構(gòu)造,也是默認(rèn)構(gòu)造的一種,在這里編譯器也會(huì)去走M(jìn)yQueue的初始化列表進(jìn)行初始化
//無(wú)參構(gòu)造MyQueue(){<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}所以可以看出,對(duì)于【內(nèi)置類型】不做處理,【自定義類型】會(huì)調(diào)用它的默認(rèn)構(gòu)造可以看出其實(shí)就是當(dāng)前類構(gòu)造函數(shù)的初始化列表在起作用

在看了MyQueue類各種初始化列表的方式后,其實(shí)也可以總結(jié)出一點(diǎn),無(wú)論如何不管有沒(méi)有給到缺省值,只要是顯式地寫了一個(gè)構(gòu)造函數(shù),就可以通過(guò)調(diào)試去看出編譯器都會(huì)通過(guò)【初始化列表】去進(jìn)行一個(gè)初始化
- 成員變量在類中聲明次序就是其在初始化列表中的初始化順序,與其在初始化列表中的先后次序無(wú)關(guān)
- 最后再來(lái)看第四點(diǎn),你認(rèn)為下面這段代碼最后打印的結(jié)果會(huì)是多少呢?1 1 嗎?
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
但結(jié)果卻和我們想象的不一樣,_a1是1,_a2卻是一個(gè)隨機(jī)值,這是為什么呢?

- 通過(guò)調(diào)試可以發(fā)現(xiàn),似乎是先初始化的
_a2再去初始化的_a1,對(duì)于【內(nèi)置類型】我們可以知道是編譯器是不會(huì)去進(jìn)行初始化的,那若是一開(kāi)始使用_a1去初始化_a2的時(shí)候,那_a2就會(huì)是一個(gè)隨機(jī)值,但是_a1卻使用傳入進(jìn)來(lái)的形參a進(jìn)行了初始化,那它的值就是1

- 此時(shí)我們只需要讓
_a1先進(jìn)行初始化即可,就不會(huì)造成隨機(jī)值的現(xiàn)象了

現(xiàn)在你在翻上去把所有的調(diào)試圖一幅幅看下來(lái)就可以發(fā)現(xiàn)出初始化列表是存在順序的,它的順序不是在列表中誰(shuí)先誰(shuí)后的順序,而是類的成員變量聲明的順序
五、總結(jié)與提煉
最后來(lái)總結(jié)一下本文所學(xué)習(xí)的內(nèi)容??
面對(duì)必須在聲明時(shí)期初始化的成員函數(shù),我們引入了初始化列表這個(gè)東西,知道了祖師爺在構(gòu)造函數(shù)中還做了這么個(gè)小文章??。有了它,我們就再也不用擔(dān)心成員變量不會(huì)被初始化的問(wèn)題了,無(wú)論是你是否給到缺省值,編譯器都會(huì)去走一遍構(gòu)造函數(shù)的初始化列表,若是沒(méi)有在定義處給到初始值,就會(huì)采用缺省值;若是給到了初始值就會(huì)采用這個(gè)值
不僅如此,初始化列表還有很多的注意事項(xiàng):
- 首先就是每個(gè)成員只能初始化一次,可以不要初始化多次哦
- 其次就是對(duì)于三類成員一定要在初始化列表進(jìn)行初始化:包括
const修飾的成員變量、引用類型成員、無(wú)默認(rèn)構(gòu)造函數(shù)的自定義成員變量 - 然后盡量使用初始化列表初始化,因?yàn)闊o(wú)論如何編譯器一定會(huì)走初始化列表,聲明時(shí)期的缺省值其實(shí)就是給到初始化列表使用的
- 最后就是初始化列表中的初始化順序,與定義處的順序是無(wú)關(guān)的,和聲明處的順序有關(guān)
初始化列表是構(gòu)造函數(shù)這一塊的難點(diǎn),也是祖師爺面對(duì)C++某些地方缺陷設(shè)計(jì)出來(lái)的,搞懂之后就會(huì)豁然開(kāi)朗了
以上就是C++之談?wù)剺?gòu)造函數(shù)的初始化列表的詳細(xì)內(nèi)容,更多關(guān)于C++構(gòu)造函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言中對(duì)文件最基本的讀取和寫入函數(shù)
這篇文章主要介紹了C語(yǔ)言中對(duì)文件最基本的讀取和寫入函數(shù),是C語(yǔ)言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08
linux c程序中獲取shell腳本輸出的實(shí)現(xiàn)方法
以下是對(duì)在linux下c程序中獲取shell腳本輸出的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-08-08
C語(yǔ)言實(shí)現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用映射(HashMap)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用映射,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
C++實(shí)踐數(shù)組類運(yùn)算的實(shí)現(xiàn)參考
今天小編就為大家分享一篇關(guān)于C++實(shí)踐數(shù)組類運(yùn)算的實(shí)現(xiàn)參考,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
利用C語(yǔ)言來(lái)求最大連續(xù)子序列乘積的方法
這篇文章主要介紹了利用C語(yǔ)言來(lái)求最大連續(xù)子序列乘積的方法,基本的思路以外文中還附有相關(guān)ACM題目,需要的朋友可以參考下2015-08-08
線段樹(shù)詳解以及C++實(shí)現(xiàn)代碼
線段樹(shù)在一些acm題目中經(jīng)常見(jiàn)到,這種數(shù)據(jù)結(jié)構(gòu)主要應(yīng)用在計(jì)算幾何和地理信息系統(tǒng)中,這篇文章主要給大家介紹了關(guān)于線段樹(shù)以及C++實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2021-07-07

