C++自制心得——類(lèi)與對(duì)象中(構(gòu)造+析構(gòu))
前言:?
C++的類(lèi)一共有六個(gè)默認(rèn)(編譯器自動(dòng)生成)成員函數(shù),分別是構(gòu)造函數(shù),析構(gòu)函數(shù),拷貝構(gòu)造,賦值重載,取地址重載(普通對(duì)象+const對(duì)象,共兩個(gè)),其中取地址重載函數(shù)編譯器默認(rèn)生成的就夠用了,重點(diǎn)就是前四個(gè)函數(shù),這篇講構(gòu)造函數(shù)和析構(gòu)函數(shù)。
構(gòu)造函數(shù):?
這里有一個(gè)新鮮出爐的棧,讓我們測(cè)試一下...?

怎么報(bào)錯(cuò)了,棧這種東西我早已寫(xiě)的滾瓜爛熟,不可能有問(wèn)題,f10,啟動(dòng)。

(⊙﹏⊙),野指針,等一下,為什么會(huì)有野指針......忘初始化了。我相信忘記初始化這種尷尬的事情不止一個(gè)人犯過(guò),同樣,忘記釋放內(nèi)存也是大坑(等我敲到這里我才發(fā)現(xiàn)我甚至沒(méi)寫(xiě)釋放函數(shù))。不過(guò)在C++里我們有一個(gè)完美的解決方案,那就是構(gòu)造函數(shù)。
基礎(chǔ)版
高級(jí)版
第二個(gè)是初始化列表,后面再講。我們看第一個(gè)
構(gòu)造函數(shù)是六大默認(rèn)成員函數(shù)之一,其主要功能為給已開(kāi)辟好空間的對(duì)象進(jìn)行初始化。
基本特點(diǎn)如下:
1. 函數(shù)名與類(lèi)名相同
2. 無(wú)返回值類(lèi)型(連void都不用寫(xiě))
3. 可重載
一個(gè)對(duì)象可以擁多個(gè)初始化方式,因此構(gòu)造函數(shù)應(yīng)當(dāng)具有重載能力(拷貝構(gòu)造函數(shù)與其重名)
4. 自動(dòng)調(diào)用
怎么證明這個(gè)函數(shù)是自動(dòng)調(diào)用的?我給構(gòu)造函數(shù)加了一個(gè)printf,打印一下

看,的確可以自動(dòng)調(diào)用。
main函數(shù)(修改后)
注意,不傳參的初始化不要加括號(hào),會(huì)被認(rèn)作函數(shù)聲明。
具體應(yīng)用這里就不展開(kāi)寫(xiě)了,我們來(lái)看一些麻煩的東西。
5. 默認(rèn)構(gòu)造函數(shù)
默認(rèn)構(gòu)造函數(shù)有三種,編譯器自動(dòng)生成的,無(wú)參的,全缺省的。其余構(gòu)造函數(shù)均不是構(gòu)造函數(shù)。
6. 如未顯式定義構(gòu)造函數(shù),編譯器會(huì)自動(dòng)生成默認(rèn)構(gòu)造函數(shù)
對(duì)于這個(gè)默認(rèn)構(gòu)造函數(shù),它有三個(gè)特點(diǎn),不處理內(nèi)置類(lèi)型(處理的是編譯器行為,而非標(biāo)準(zhǔn)規(guī)定),自動(dòng)調(diào)用自定義類(lèi)型的默認(rèn)構(gòu)造函數(shù),你寫(xiě)了就不會(huì)生成了。如果你真的想讓編譯器處理內(nèi)置類(lèi)型,你可以在成員變量的聲明處定義缺省值,這樣編譯就會(huì)自動(dòng)處理內(nèi)置類(lèi)型,用日期類(lèi)舉個(gè)例子

自定義類(lèi)型的自動(dòng)調(diào)用很有用處,還記得leetcode上的用棧實(shí)現(xiàn)隊(duì)列嗎?我用它再舉個(gè)例子

看,我們什么都沒(méi)做,編譯器自動(dòng)調(diào)了兩次Stack的構(gòu)造函數(shù)。所以像這種成員變量全是自定義類(lèi)型的類(lèi),可以考慮不寫(xiě)構(gòu)造函數(shù)。
如果你寫(xiě)了一個(gè)非默認(rèn)構(gòu)造函數(shù),但是定義對(duì)象時(shí)不傳參,就會(huì)出現(xiàn)如下情況。

如果MyQueue類(lèi)中Stack類(lèi)沒(méi)有默認(rèn)構(gòu)造函數(shù)就會(huì)出現(xiàn)如下情況

想解決這個(gè)問(wèn)題,要么寫(xiě)默認(rèn)構(gòu)造,要么......走初始化列表。

7.?初始化列表(難點(diǎn))
我們知道成員變量的聲明在類(lèi)里面,那么它的定義在哪里?首先,我們要清楚構(gòu)造函數(shù)也是有隱含的this指針,所以我們?cè)跇?gòu)造函數(shù)函數(shù)體里做的工作都叫賦值操作,而非初始化,我可以證明這一點(diǎn)
我們?cè)陬?lèi)里加入了一個(gè)const對(duì)象,它的特點(diǎn)是必須在初始化時(shí)給值,后面不能再改,如果構(gòu)造函數(shù)的函數(shù)體里是初始化操作,那就不會(huì)報(bào)錯(cuò),否則

這是一個(gè)超級(jí)巨坑,如果說(shuō)像引用,const這類(lèi)必須在定義時(shí)初始化的成員變量不可以被構(gòu)造函數(shù)初始化,那這個(gè)構(gòu)造函數(shù)就是一個(gè)失敗的半成品,因此就有了初始化列表這個(gè)概念。
初始化列表是成員變量定義的地方,在初始化列表里進(jìn)行的操作就是初始化操作,這樣就能解決上述問(wèn)題。把上面的代碼用初始化列表表示,就是這樣的
好,那怎樣寫(xiě)初始化列表,函數(shù)名下方以“ : ”作為起始符號(hào),后接成員變量,以函數(shù)調(diào)用的形式填寫(xiě)初始化參數(shù)(因?yàn)橐?guī)則與形式幾乎與函數(shù)調(diào)用完全一致,只要最后結(jié)果類(lèi)型能匹配初始化參數(shù)給函數(shù)也是可以的),兩個(gè)成員用“,”隔開(kāi),以函數(shù)體為結(jié)束標(biāo)志({})。
下面是一些初始化列表的特性。
1. 每一個(gè)成員變量都會(huì)走一次初始化列表,無(wú)論你是否顯式寫(xiě)出了那個(gè)成員變量的初始化。

用這個(gè)代碼來(lái)舉例子,首先,成員變量聲明處的缺省值會(huì)在初始化列表處被應(yīng)用,又因?yàn)樗鼈兪侨笔≈?,一旦某個(gè)變量被顯式初始化編譯器就不會(huì)用缺省值初始化。如果某些變量沒(méi)有顯式初始化,那么編譯器就會(huì)用缺省值進(jìn)行隱式初始化。因此整個(gè)程序的輸出結(jié)果就是2022/2/1
2. 變量初始化順序只和聲明有關(guān)

以這段代碼為例,成員變量初始化順序?yàn)閍a,a,初始化列表就先走aa(a),再走a(x),所以aa就成了隨機(jī)值
3. 編譯器生成的默認(rèn)初始化列表對(duì)于內(nèi)置類(lèi)型,用缺省值或隨機(jī)值(是不是看編譯器)初始化,而對(duì)于自定義類(lèi)型,會(huì)調(diào)用該類(lèi)型的默認(rèn)構(gòu)造函數(shù)(其實(shí)就是把構(gòu)造函數(shù)的特性又說(shuō)了一遍,不過(guò)這次更具體了)
析構(gòu)函數(shù)
相比于構(gòu)造函數(shù)這只怪物,析構(gòu)函數(shù)簡(jiǎn)單的多。析構(gòu)函數(shù)的功能與構(gòu)造函數(shù)正好相反,它主要完成對(duì)對(duì)象內(nèi)資源的清理工作,而非回收對(duì)象空間。
析構(gòu)函數(shù)有以下特性:
1. 函數(shù)名為“~” + “類(lèi)名”
2. 無(wú)返回值類(lèi)型與參數(shù)
3. 如未顯式定義,編譯器會(huì)自動(dòng)生成(特性同構(gòu)造函數(shù))
4. 對(duì)象生命周期結(jié)束后自動(dòng)調(diào)用
對(duì)于一些簡(jiǎn)單的類(lèi),比如日期類(lèi),它就完全不需要寫(xiě)析構(gòu)函數(shù),只有像棧這種需要手動(dòng)回收資源的類(lèi)才需要寫(xiě)析構(gòu)函數(shù)
MyQueue類(lèi)又躺贏了,它也不需要寫(xiě)析構(gòu)函數(shù),編譯器調(diào)Stack類(lèi)現(xiàn)成的就行,妥妥的人生贏家。
最后補(bǔ)充一點(diǎn),如果有多個(gè)對(duì)象同時(shí)出生命周期,根據(jù)函數(shù)棧幀的開(kāi)辟與銷(xiāo)毀規(guī)則,析構(gòu)函數(shù)的調(diào)用遵循FIFO原則。
下一章專(zhuān)欄講拷貝構(gòu)造和賦值重載,又要掉頭發(fā)咯。