C++之談?wù)剺?gòu)造函數(shù)的初始化列表
一、引入
- 我們知道,對于下面這個類A的成員變量
_a1
和_a2
屬于【聲明】,還沒有在內(nèi)存中為其開辟出一塊空間以供存放,真正開出空間則是在【定義】的時候,那何時定義呢?也就是使用這個類A去實例化出對象的時候 - 這個對象的空間被開出來了,難道里面的成員變量就一定開出空間了嗎?這一點我們很難去通過調(diào)試觀察
class A { public: int _a1; //聲明 int _a2; };
int main(void) { A aa; // 對象整體的定義,每個成員什么時候定義? return 0; }
如果現(xiàn)在我在類A中加上一個const成員變量的話,初始化的時候似乎就出現(xiàn)了問題
const int _x;
- 在搞清楚上面的問題之前你要明白
const
修飾的變量有哪些特點
const int i;
- 可以看到我在這里定義了一個整型變量i,它前面是用
const
進行修飾的,不過編譯后報出了錯誤說【必須初始化常量對象】,因為對于const
修飾的變量在聲明的時候是必須要去進行初始化的,也就是要給到一個值
現(xiàn)在我們就可以來聊聊有關(guān)上面的成員變量
_x
為什么沒有被初始化的原因了??
- 之前有講過,若是我們自己不去實現(xiàn)構(gòu)造函數(shù)的話,類中會默認(rèn)提供一個構(gòu)造函數(shù)來初始化成員變量,對于【內(nèi)置類型】的變量不會處理,對【自定義類型】的變量會去調(diào)用它的構(gòu)造函數(shù)。那么對于這里的
_a1
、_a2
、_x
都屬于內(nèi)置類型的數(shù)據(jù),所以編譯器不會理睬,可是呢const
修飾的變量又必須要初始化,這個時候該怎么辦呢╮(╯▽╰)╭
??有同學(xué)說:這還不簡單,給個缺省值不就好了
這位同學(xué)說的不錯,這個辦法確實是可以解決我們現(xiàn)在的問題,因為C++11里面為內(nèi)置類型不初始化打了一個補丁,在聲明的位置給到一個初始化值,就可以很好地防止編譯器不處理的問題
但是現(xiàn)在我想問一個問題:如果不使用這個辦法呢?你有其他方法嗎?難道C++11以前就那它沒辦法了嗎?
底下的同學(xué)確實想不出什么很好的解決辦法,于是這個時候就要使用到本模塊要學(xué)習(xí)的【初始化列表】了
二、初始化的概念區(qū)分
- 在了解【初始化列表】前,你要先知道初始化的真正含義是什么
概念:在創(chuàng)建對象時,編譯器通過調(diào)用構(gòu)造函數(shù),給對象中各個成員變量一個合適的初始值。
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
- 上面這個Date類是我們之前寫過的,這里有一個它的有參構(gòu)造函數(shù),雖然在這個構(gòu)造函數(shù)調(diào)用之后,對象中已經(jīng)有了一個初始值,但是不能將其稱為對對象中成員變量的初始化。構(gòu)造函數(shù)體中的語句只能將其稱為【賦初值】,而不能稱作初始化。因為初始化只能初始化一次,而構(gòu)造函數(shù)體內(nèi)可以多次賦值。
三、語法格式及使用
【初始化列表】:以一個冒號開始,接著是一個以逗號分隔的數(shù)據(jù)成員列表,每個"成員變量"后面跟一個放在括號中的初始值或表達(dá)式
- 下面就是它的具體用法,這樣便可以通過外界傳入一些參數(shù)對年、月、日進行初始化
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; }
可以通過調(diào)試來觀察一下它到底是怎么走的
接下去我再來說說這一塊的難點所在,準(zhǔn)備好頭腦風(fēng)暴??
- 還是看回到我們上面的這個類A,知道了【初始化列表】這個東西,此時就不需要再聲明的部分給缺省值了,直接使用初始化列表即可。不過可以看到,對于
_a1
和_a2
我給到了缺省值,寫了初始化列表后,它們還會被初始化嗎?
class A { public: A() :_x(1) {} private: int _a1 = 1; //聲明 int _a2 = 1; const int _x; };
也通過調(diào)試來看一下
- 可以看到,即使在初始化列表沒有給到
_a1
和_a2
的初始化,還是會通過給到的默認(rèn)缺省值去進行一個初始化。根據(jù)上面所學(xué),我給出以下的結(jié)論 - 哪個對象調(diào)用構(gòu)造函數(shù),初始化列表是它所有成員變量定義的位置
- 不管是否顯式在初始化列表寫,編譯器都會為每個變量在初始化列表進行初始化
好,接下去難度升級,請問初始化列表修改成這樣后三個成員變量初始化后的結(jié)果會是什么呢? 會是1、2、1嗎?
class A { public: A() :_x(1) ,_a2(1) {} private: int _a1 = 1; //聲明 int _a2 = 2; const int _x; };
一樣通過調(diào)試來看看
- 可以觀察到,最后初始化完后的結(jié)果為1、1、1,最令你困惑的應(yīng)該就是這個
_a2
了,因為我在聲明的時候給到了缺省值,然后初始化列表去進行定義的時候又去進行了一次初始化,最后的結(jié)果以初始化列表的方式為主
這里要明確的一個概念是,缺省參數(shù)只是一個備份,若是我們沒有去給到值初始化的話,編譯器就會使用這個初始值,若是我們自己給到了明確的值的話,不會去使用這個缺省值了【如果不清楚看看C++缺省參數(shù)】
接下去難度繼續(xù)升級,請問下面這樣初始化后的結(jié)果是多少?
- 可以看到對于構(gòu)造函數(shù)我不僅寫了【初始化列表】,而且在函數(shù)體內(nèi)部還對
_a1
和_a2
進行了++和- -,那此時會有什么變化呢?
class A { public: A() :_x(1) ,_a2(1) { _a1++; _a2--; } private: int _a1 = 1; //聲明 int _a2 = 2; const int _x; };
如果對于上面的原理搞清楚了,那看這個就相當(dāng)于是再鞏固了一遍。也是一樣,無論是否給到缺省值都會去初始化列表走一遍,若是構(gòu)造函數(shù)內(nèi)部有語句的話就會執(zhí)行
四、注意事項【?】
清楚了初始化列表該如何使用,接下去我們來說說其相關(guān)的注意事項
- 每個成員變量在初始化列表中只能出現(xiàn)一次(初始化只能初始化一次)
- 可以看到,若是一個成員變量在初始化列表的地方出現(xiàn)了兩次,編譯器在編譯的時候就會報出【xxx已初始化】
- 類中包含以下成員,必須放在初始化列表位置進行初始化:
const成員變量
- 這個在前面已經(jīng)說到過了,
const
修飾的成員變量和構(gòu)造函數(shù)對于內(nèi)置類型不做處理產(chǎn)生了一個沖突,因此祖師爺就提出了【初始化列表】這個概念
引用成員變量
- 第二點就是對于引用成員變量,如果有點忘記了看看C++引用通過編譯可以看出,這個引用型成員變量
_z
需要被初始化,它必須要引用一個值
沒有默認(rèn)構(gòu)造的自定義類型成員(寫了有參構(gòu)造編譯器就不會提供默認(rèn)構(gòu)造)
- 此時,我又寫了一個類B,將它定義出的對象作為類A的成員變量,在類B中,有一個無參的默認(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; };
- 通過調(diào)試來觀察就可以看到,完全符合我們前面所學(xué)的知識,若是當(dāng)前類中有自定義類型的成員變量,那在為其進行初始化的時候會去調(diào)用它的默認(rèn)構(gòu)造函數(shù)
- 但是現(xiàn)在我對這個構(gòu)造函數(shù)做了一些改動,將其變?yōu)榱擞袇⒌臉?gòu)造函數(shù),此時編譯時就報出了【沒有合適的默認(rèn)構(gòu)造函數(shù)可用】
- 我們知道默認(rèn)構(gòu)造有:無參、全缺省和編譯器自動生成的,都是不需要我們手動去調(diào)的。可以看到若是我在這里將其改為全缺省的話,就不會出問題了,因為它屬于默認(rèn)構(gòu)造函數(shù)
??那對于有參構(gòu)造該如何去初始化呢?
還是可以利用到我們的【初始化列表】
通過調(diào)試來看看編譯器是如何走的
- 盡量使用初始化列表初始化,因為不管你是否使用初始化列表,對于自定義類型成員變量,一定會先使用初始化列表初始化
看完了上面這一種,我們再來看看稍微復(fù)雜一些的自定義類型是否也遵循這個規(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申請空間失敗"); return; } _size = 0; _capacity = capacity; } //.... private: DataType* _array; size_t _size; size_t _capacity; };
- 此處我們主要觀察Stack類的構(gòu)造函數(shù),因為在MyQueue中我沒有寫構(gòu)造函數(shù),為的就是使用它默認(rèn)生成的構(gòu)造函數(shù)去進行初始化。對于【內(nèi)置類型】不做處理,不過我這里給到了一個缺省值,對于【自定義類型】會去調(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; }
可能讀者有所忘卻,我們再通過調(diào)試來看一下
- 可以觀察到在初始化MyQueue類的對象時,因為內(nèi)部有兩個Stack類型的對象,所以就會去調(diào)用兩次Stack類默認(rèn)構(gòu)造來進行初始化
- 那此時我若是將這個默認(rèn)構(gòu)造(全缺省構(gòu)造)改為有參構(gòu)造嗎,它還調(diào)得動嗎?
Stack(size_t capacity)
- 可以看到,此時就報出了我們上面有類似遇到過的【無法引用默認(rèn)構(gòu)造函數(shù)】,為什么呢?原因就在于我們寫了,編譯器自動生成的也就不存在了,但是我又沒有傳入對應(yīng)的參數(shù)
- 此時就可以使用到我們本模塊所學(xué)習(xí)的【初始化列表】了,將需要定義的值放在初始化列表,相當(dāng)于就是為Stack類傳入了一個有參構(gòu)造的參數(shù),不過對于沒有寫在這里的
_t
,依舊會使用我給到的初始值1
MyQueue() :_pushST(10) ,_popST(10) {}
可以通過調(diào)試再來看看
- 當(dāng)然,如果你覺得不想要這個固定的10作為棧容量的話,也可以將這個MyQueue的構(gòu)造函數(shù)設(shè)定為有參,自己傳遞進去也是可以的
- 最后再來看一下無參構(gòu)造,也是默認(rèn)構(gòu)造的一種,在這里編譯器也會去走MyQueue的初始化列表進行初始化
//無參構(gòu)造MyQueue(){<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}
所以可以看出,對于【內(nèi)置類型】不做處理,【自定義類型】會調(diào)用它的默認(rèn)構(gòu)造可以看出其實就是當(dāng)前類構(gòu)造函數(shù)的初始化列表在起作用
在看了MyQueue類各種初始化列表的方式后,其實也可以總結(jié)出一點,無論如何不管有沒有給到缺省值,只要是顯式地寫了一個構(gòu)造函數(shù),就可以通過調(diào)試去看出編譯器都會通過【初始化列表】去進行一個初始化
- 成員變量在類中聲明次序就是其在初始化列表中的初始化順序,與其在初始化列表中的先后次序無關(guān)
- 最后再來看第四點,你認(rèn)為下面這段代碼最后打印的結(jié)果會是多少呢?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
卻是一個隨機值,這是為什么呢?
- 通過調(diào)試可以發(fā)現(xiàn),似乎是先初始化的
_a2
再去初始化的_a1
,對于【內(nèi)置類型】我們可以知道是編譯器是不會去進行初始化的,那若是一開始使用_a1
去初始化_a2
的時候,那_a2
就會是一個隨機值,但是_a1
卻使用傳入進來的形參a進行了初始化,那它的值就是1
- 此時我們只需要讓
_a1
先進行初始化即可,就不會造成隨機值的現(xiàn)象了
現(xiàn)在你在翻上去把所有的調(diào)試圖一幅幅看下來就可以發(fā)現(xiàn)出初始化列表是存在順序的,它的順序不是在列表中誰先誰后的順序,而是類的成員變量聲明的順序
五、總結(jié)與提煉
最后來總結(jié)一下本文所學(xué)習(xí)的內(nèi)容??
面對必須在聲明時期初始化的成員函數(shù),我們引入了初始化列表這個東西,知道了祖師爺在構(gòu)造函數(shù)中還做了這么個小文章??。有了它,我們就再也不用擔(dān)心成員變量不會被初始化的問題了,無論是你是否給到缺省值,編譯器都會去走一遍構(gòu)造函數(shù)的初始化列表,若是沒有在定義處給到初始值,就會采用缺省值;若是給到了初始值就會采用這個值
不僅如此,初始化列表還有很多的注意事項:
- 首先就是每個成員只能初始化一次,可以不要初始化多次哦
- 其次就是對于三類成員一定要在初始化列表進行初始化:包括
const
修飾的成員變量、引用類型成員、無默認(rèn)構(gòu)造函數(shù)的自定義成員變量 - 然后盡量使用初始化列表初始化,因為無論如何編譯器一定會走初始化列表,聲明時期的缺省值其實就是給到初始化列表使用的
- 最后就是初始化列表中的初始化順序,與定義處的順序是無關(guān)的,和聲明處的順序有關(guān)
初始化列表是構(gòu)造函數(shù)這一塊的難點,也是祖師爺面對C++某些地方缺陷設(shè)計出來的,搞懂之后就會豁然開朗了
以上就是C++之談?wù)剺?gòu)造函數(shù)的初始化列表的詳細(xì)內(nèi)容,更多關(guān)于C++構(gòu)造函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
linux c程序中獲取shell腳本輸出的實現(xiàn)方法
以下是對在linux下c程序中獲取shell腳本輸出的實現(xiàn)方法進行了詳細(xì)的分析介紹,需要的朋友可以過來參考下2013-08-08C語言實現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用映射(HashMap)
這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用映射,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11