個人主頁:平行線也會相交??
歡迎 點贊?? 收藏? 留言? 加關(guān)注??本文由 平行線也會相交 原創(chuàng)
收錄于專欄【C++之路】??
本專欄旨在記錄C++的學(xué)習(xí)路線,望對大家有所幫助???
希望我們一起努力、成長,共同進步。??
我們知道類包含成員變量和成員函數(shù),當(dāng)一個類中既沒有成員函數(shù)也沒有成員變量時,我們把這個類稱之為空類。就比如說下面這個空類:
class Date{};
雖然說Date
這個類中啥都沒有,即被我們稱之為空類,相當(dāng)于我們在這個類中啥都沒寫,但是編譯器會生成6個默認(rèn)成員函數(shù)。
默認(rèn)成員函數(shù):即用戶沒有顯式實現(xiàn),但編譯器會自動生成的成員函數(shù)就被稱為默認(rèn)成員函數(shù)。
具體是哪六個成員函數(shù)呢?請看:
一、構(gòu)造函數(shù)
構(gòu)造函數(shù)是個比較特殊的成員函數(shù),需要注意:構(gòu)造函數(shù)不是真的然我們手把手的去寫一個函數(shù)出來,構(gòu)造函數(shù)的任務(wù)不是開辟空間創(chuàng)建對象,而是對對象進行初始化
。
構(gòu)造函數(shù)特征:
1.函數(shù)名與類名相同。
2.沒有返回值(不需要寫void
)。
3.對象實例化時編譯器會自動調(diào)用其對應(yīng)的構(gòu)造函數(shù)。
4.構(gòu)造函數(shù)可以重載。
5.如果類中沒有顯式定義構(gòu)造函數(shù),則C++編譯器會自動生成一個無參的默認(rèn)構(gòu)造函數(shù),但是如果用戶顯式定義了構(gòu)造函數(shù)則編譯器將不再生成。
6.如果我們不自己實現(xiàn)構(gòu)造函數(shù)的話,編譯器生就會成默認(rèn)構(gòu)造函數(shù),但是編譯器調(diào)用默認(rèn)構(gòu)造函數(shù)之后依舊會出現(xiàn)隨機值的問題。故實際上這里編譯器生成的默認(rèn)構(gòu)造函數(shù)并沒有起作用。
7.無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都被稱為默認(rèn)構(gòu)造函數(shù),并且二者中只能有一個,如果我們既沒有寫無參的構(gòu)造函數(shù)也沒有寫全缺省的構(gòu)造函數(shù),那么編譯器就會自動生成默認(rèn)構(gòu)造函數(shù)??傊?,無參的構(gòu)造函數(shù)、全缺省的構(gòu)造函數(shù)、編譯器自動生成的構(gòu)造函數(shù)都被稱為默認(rèn)構(gòu)造函數(shù),而且這三者中只能存在其中一個。
舉個構(gòu)造函數(shù)的例子,請看:
上述代碼我們并沒有調(diào)用初始化的函數(shù),但是我們依然可以對類Date
中的成員函數(shù)進行初始化。打印結(jié)果如下:
//這就是一個構(gòu)造函數(shù)
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
當(dāng)對象實例化時,編譯器會自動調(diào)用該構(gòu)造函數(shù),即Date da1(2023,5,1);
。
1.1構(gòu)造函數(shù)的重載
我們已經(jīng)知道構(gòu)造函數(shù)的特性之一就是構(gòu)造函數(shù)支持重載。我們已經(jīng)知道構(gòu)造函數(shù)可以對對象進行初始化操作,然而初始化的方式也有很多種,這也就是構(gòu)造函數(shù)支持重載的原因。
舉個例子,比如說數(shù)據(jù)結(jié)構(gòu)中的棧,初始化時我一上來就上壓入n個數(shù)據(jù),我們應(yīng)該怎樣做呢?請看:
上述代碼的構(gòu)造函數(shù)的重載如下:
//構(gòu)造函數(shù)
Stack(int capacity = 4)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申請空間失敗!!!");
return;
}
_capacity = capacity;
_size = 0;
}
Stack(DataType* a, int n)
{
cout << "Stack(DataType* a, int n)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * n);
if (NULL == _array)
{
perror("malloc申請空間失敗!!!");
return;
}
memcpy(_array, a, sizeof(DataType) * n);
_capacity = n;
_size = n;
}
1.2自動生成構(gòu)造函數(shù)
如果類中沒有顯式定義構(gòu)造函數(shù),則C++編譯器會自動生成一個無參的默認(rèn)構(gòu)造函數(shù),但是如果用戶顯式定義了構(gòu)造函數(shù)則編譯器將不再生成。
上述類中我們并沒有顯式的定義構(gòu)造函數(shù),此時C++就會生成一個無參的默認(rèn)構(gòu)造函數(shù),來看運行結(jié)果:
請看,一般而言我們認(rèn)為應(yīng)該初始化為0,但是這里結(jié)果看起來想隨機值,所以一些友友認(rèn)為可能編譯器啥也沒干,其實并不是。C++的標(biāo)準(zhǔn)并沒有說一定要初始化成0
,但是有些編譯器會初始化成0(這就是編譯器自己的原因了,即個性化)。
1.C++中分為內(nèi)置類型(或叫基本類型),即語言本身定義的基礎(chǔ)類型,如int/char/double/指針等等。
2.與內(nèi)置類型相對應(yīng)的就是自定義類型,即用struct/class等等定義的類型。注意:我們?nèi)绻粚憳?gòu)造函數(shù)則編譯器就會生成默認(rèn)構(gòu)造函數(shù),該默認(rèn)構(gòu)造函數(shù)對內(nèi)置類型不做初始化處理,而自定義類型就會調(diào)用它的默認(rèn)構(gòu)造函數(shù)。
請看舉例:
如果我將自定義類型_st
省略后,再來看調(diào)試結(jié)果,請看:
這算是vs2022的一個小bug吧,不同的編譯器會有不同的表現(xiàn),按理來說加上自定義類型_st
后,編譯器不應(yīng)該對三個內(nèi)置類型的變量進行處理,但是vs2022這里的確對這三個內(nèi)置類型進行了特殊的處理(即初始化成了0)并不是所有的編譯器都會對這三個內(nèi)置類型進行處理,這里注意就好。但是有一點是確定的,自定義類型_st
編譯器必須要進行處理,因為我們的祖師爺本賈尼規(guī)定了自定義類型會調(diào)用它的默認(rèn)構(gòu)造函數(shù)進行處理
。再次強調(diào),祖師爺本賈尼規(guī)定了編譯器可以不對內(nèi)置類型進行處理,但是編譯器一定要對自定義類型進行處理,即自定義類型會調(diào)用它的默認(rèn)構(gòu)造函數(shù)進行處理
。我們最好認(rèn)為編譯器不會對內(nèi)置類型進行處理。
舉個例子: 編譯器生成默認(rèn)的構(gòu)造函數(shù)會對自定類型成員_t調(diào)用的它的默認(rèn)成員函數(shù)。請看:
類中若有內(nèi)置類型,理論上編譯器不會生成默認(rèn)構(gòu)造函數(shù),所以如果有內(nèi)置類型,我們就需要自己去寫構(gòu)造函數(shù),切記不要讓編譯器自己生成默認(rèn)構(gòu)造函數(shù),否則可能就會存在隨機值的風(fēng)險(有的編譯器會對內(nèi)置類型處理,有的編譯器就不會對內(nèi)置類型進行處理)。
那什么情況下:編譯器會自己生成默認(rèn)構(gòu)造函數(shù)呢?
1.一般情況下,有內(nèi)置類型成員就需要我們自己寫構(gòu)造函數(shù),不能用編譯器自己生成的。
2.如果全部都是自定義類型成員,可以考慮讓編譯器自己生成默認(rèn)構(gòu)造函數(shù)。
舉個讓編譯器自己生成默認(rèn)構(gòu)造函數(shù)的例子:leetcode232.用棧實現(xiàn)隊列
現(xiàn)在再來看下一個問題:C++11覺得內(nèi)置類型不做處理而自定義類型會進行處理這里不是很好。所以C++11標(biāo)準(zhǔn)對這里進行了一些彌補:在成員進行聲明的時候可以給缺省值。
請看舉例:
1.3構(gòu)造函數(shù)的調(diào)用
構(gòu)造函數(shù)本身就已經(jīng)很特殊了,而構(gòu)造函數(shù)的調(diào)用和構(gòu)造函數(shù)本身一樣依然是特殊的,哈哈?。?!
一般函數(shù)的調(diào)用是這樣的:函數(shù)名+參數(shù)列表,即
da1.Print();
。 構(gòu)造函數(shù)的調(diào)用是這樣的:對象+參數(shù)列表,即Date da2(23,5,20);
。但是我們不可以這樣寫:Date da3();
沒參數(shù)的時候這樣寫是錯誤的,因為這樣寫的化就會和函數(shù)聲明有點沖突,仔細(xì)看Date da3();
,按照函數(shù)聲明來理解的話:Date
是返回值類型,而且無參,這樣的話就會導(dǎo)致編譯器不好識別,所以這里一定要注意:Date da3();
這種寫法是錯誤的。
語法上無參的和全缺省的函數(shù)重載是可以同時存在的,依然是構(gòu)成函數(shù)重載。但是編譯器調(diào)用的時候會存在歧義。即符合構(gòu)成函數(shù)重載,但是無參調(diào)用會出現(xiàn)歧義,故是不能同時存在的。
所以構(gòu)造函數(shù)構(gòu)成重載時無參調(diào)用存在歧義。
1.4三個默認(rèn)構(gòu)造函數(shù)(無參、全缺省、編譯器自動生成)只能存在一個
下面是構(gòu)造函數(shù)的特征7:
無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都被稱為默認(rèn)構(gòu)造函數(shù),并且二者中只能有一個,如果我們既沒有寫無參的構(gòu)造函數(shù)也沒有寫全缺省的構(gòu)造函數(shù),那么編譯器就會自動生成默認(rèn)構(gòu)造函數(shù)。總之,無參的構(gòu)造函數(shù)、全缺省的構(gòu)造函數(shù)、編譯器自動生成的構(gòu)造函數(shù)都被稱為默認(rèn)構(gòu)造函數(shù),而且這三者中只能存在其中一個。
我們對這個三個默認(rèn)構(gòu)造函數(shù)一一進行對比,請看:
-
只存在全缺省的默認(rèn)構(gòu)造函數(shù)
同時注意下面這兩種寫法的對比:
左圖中既然是半缺省,所以我們必須要傳一個參數(shù),由于左圖中并沒有傳參,所以就報錯了;而右圖中由于是全缺省,所以我們當(dāng)然可以不傳參數(shù)。
對于左圖的修改請看: -
只存在無參的默認(rèn)構(gòu)造函數(shù)
-
編譯器生成默認(rèn)構(gòu)造函數(shù)(隨機值)
小結(jié)
1.構(gòu)造函數(shù)可以構(gòu)成重載,但是注意構(gòu)造函數(shù)無參調(diào)用時會存在歧義。所以我們一般留一個全缺省的構(gòu)造函數(shù),因為全缺省的完全可以替代函數(shù)無參的。三種默認(rèn)構(gòu)造函數(shù)中有且只能有一個。
2.編譯器對內(nèi)置類型不做處理(有些編譯器會對此進行處理,但這是少數(shù),故C++11為了彌補這里的不足,可以給內(nèi)置類型成員缺省值),對自定義類型會去調(diào)用它的默認(rèn)構(gòu)造。
3.不要依賴編譯器的默認(rèn)構(gòu)造函數(shù)(不同的編譯器會有不同的表現(xiàn))。
什么情況下不需要我們?nèi)憳?gòu)造函數(shù)呢?
情況一:內(nèi)置類型成員都有缺省值且初始化符合我們的要求。
情況二:全是自定義類型,且這些自定義類型都已經(jīng)定義了其所對應(yīng)的默認(rèn)構(gòu)造,這種情況下編譯器一般只能去調(diào)用那個無參的默認(rèn)構(gòu)造函數(shù)。那倘若那個默認(rèn)構(gòu)造并不是無參的而是帶參的話就需要用到初始化列表
的內(nèi)容了,后面在給大家推出吧。
下面是情況一的舉例:
class TreeNode
{
TreeNode* _left;
TreeNode* _right;
int _val;
};
class Tree
{
private:
TreeNode* _root = nullptr;
};
int main()
{
Tree t1;
return 0;
}
注意看上述代碼:我們給內(nèi)置類型成員_root
了一個缺省值,此時我們就不需要寫默認(rèn)構(gòu)造函數(shù)了。
再來看看情況二:
struct TreeNode
{
TreeNode* _left;
TreeNode* _right;
int _val;
TreeNode(int val = 0)
{
_left = nullptr;
_right = nullptr;
_val = val;
}
};
class Tree
{
private:
TreeNode* _root = nullptr;//這里給了缺省值
};
int main()
{
Tree t1;
TreeNode n1(1);
TreeNode n2(2);
}
下面來看一下不用寫默認(rèn)構(gòu)造函數(shù)的經(jīng)典場景:
//編譯器對自定義類型會去調(diào)用它的默認(rèn)構(gòu)造函數(shù)
class MyQueue
{
Stack _pushst;
Stack _popst;
};
int main()
{
MyQueue q;
return 0;
}
假設(shè)這里我們我們知道一開始就要插入幾個數(shù)據(jù)的話,及想讓Stack
中的capacity
變?yōu)槲覀兿胍囊粋€值,就需要用到初始化列表的內(nèi)容
。
二、析構(gòu)函數(shù)
析構(gòu)函數(shù):析構(gòu)函數(shù)與構(gòu)造函數(shù)的功能相反,析構(gòu)函數(shù)并不是完成對對象本身的銷毀,局部對象銷毀工作是由編譯器來完成的,而對象在銷毀時會自動調(diào)用析構(gòu)函數(shù),完成對象中資源的清理工作。相當(dāng)于我們在數(shù)據(jù)結(jié)構(gòu)中學(xué)習(xí)的Destroy函數(shù)
。
析構(gòu)函數(shù)特征:
1.析構(gòu)函數(shù)名是在類名前加上字符~
.
2.無參數(shù)也沒有返回值類型。
3.一個類只能有一個析構(gòu)函數(shù)。若我們沒有顯式定義析構(gòu)函數(shù),則系統(tǒng)會自動生成默認(rèn)的析構(gòu)函數(shù)(內(nèi)置類型不做處理,自定義類型的話系統(tǒng)會去調(diào)用它的默認(rèn)析構(gòu)函數(shù))。同時析構(gòu)函數(shù)不可以重載。
4.對象生命周期結(jié)束時,C++編譯系統(tǒng)會自動調(diào)用析構(gòu)函數(shù)。
上述代碼中構(gòu)造函數(shù)和析構(gòu)函數(shù)如下:
//構(gòu)造函數(shù)
Stack(int capacity = 4)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申請空間失敗!!!");
return;
}
_capacity = capacity;
_size = 0;
}
//析構(gòu)函數(shù)
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
有了構(gòu)造函數(shù)和析構(gòu)函數(shù),我們就可以簡化我們的初始化和清理工作,我們也不需要擔(dān)心會忘記對象的初始化和對象中資源的清理工作了。
1.一般如果有動態(tài)申請資源,就需要顯式寫析構(gòu)函數(shù)釋放資源。(最典型的就是棧了)
2.如果沒有動態(tài)申請的資源,不需要寫析構(gòu)函數(shù)。
3.需要釋放資源的成員都是自定義類型,不需要寫析構(gòu)函數(shù)。
4.靈活一點,特殊情況特殊對待。??
下面來看一下不需要寫析構(gòu)函數(shù)的:
//根本不需要我們寫析構(gòu),因為沒有動態(tài)申請的空間需要我們銷毀
class Date
{
private:
int _year;
int _month;
int _day;
};
//下面也不需要我們寫析構(gòu)函數(shù),對于自定義類型,編譯器會去自動調(diào)用它的構(gòu)造和析構(gòu)函數(shù)
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
};
好了,以上就是對C++中構(gòu)造函數(shù)和析構(gòu)函數(shù)的解釋,大家一定一定要學(xué)好這里,因為這里對于C++而言真的是太太太重要了。
就到這里啦,下次見各位。文章來源:http://www.zghlxwxcb.cn/news/detail-449617.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-449617.html
到了這里,關(guān)于【C++】類和對象(中)---構(gòu)造函數(shù)和析構(gòu)函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!