C++ 再識(shí)類和對(duì)象
類的6個(gè)默認(rèn)成員函數(shù)
一個(gè)類中如果什么成員都沒有,那么這個(gè)類稱為空類??疹愔惺鞘裁炊紱]有嗎?其實(shí)不然,任何一個(gè)類,再我們不寫的情況下,都會(huì)自動(dòng)生成下面6個(gè)默認(rèn)成員函數(shù):
本篇文章將對(duì)這幾個(gè)默認(rèn)成員函數(shù)進(jìn)行簡(jiǎn)單介紹。
構(gòu)造函數(shù)
1.概念
我們先來(lái)看一下下面這個(gè)日期類:
class Date { public: void SetDate(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.SetDate(); d1.Print(); return 0; }
對(duì)于Date類,每次創(chuàng)建對(duì)象時(shí)可以調(diào)用SetData函數(shù)來(lái)設(shè)置對(duì)象的日期,但是如果每次創(chuàng)建對(duì)象時(shí)都需要調(diào)用該函數(shù)來(lái)設(shè)置日期信息,未免有些麻煩,那么能否再對(duì)象創(chuàng)建的同時(shí)就進(jìn)行初始化呢?
這里就需要用到類的默認(rèn)成員函數(shù)–構(gòu)造函數(shù)了。
構(gòu)造函數(shù)是一個(gè)特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對(duì)象時(shí)由編譯器自動(dòng)調(diào)用,保證每個(gè)數(shù)據(jù)成員都有 一個(gè)合適的初始值,并且在對(duì)象的生命周期內(nèi)只調(diào)用一次。
2.特性
需要注意,構(gòu)造函數(shù)雖然名為構(gòu)造函數(shù),但是其作用并非為成員變量開辟空間,而是初始化對(duì)象。其特征如下:
函數(shù)名與類名相同。
沒有返回值。
編譯器會(huì)再對(duì)象實(shí)例化時(shí)自動(dòng)調(diào)用構(gòu)造函數(shù)。
構(gòu)造函數(shù)可以重載。
需要注意的是在類實(shí)例化對(duì)象的時(shí)候,如果變量后面帶上了(),而括號(hào)內(nèi)沒有參數(shù),那么這就成了函數(shù)聲明,該函數(shù)無(wú)參,且返回值為類名。
class Date { public: Date()//無(wú)參的構(gòu)造函數(shù) { _year = 0; _month = 1; _day = 1; } //帶參的構(gòu)造函數(shù) Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1;//調(diào)用無(wú)參的構(gòu)造函數(shù) Date d2(0, 1, 1);//調(diào)用帶參的構(gòu)造函數(shù) Date d3();//無(wú)參,返回值為Date的函數(shù)聲明 return 0; }
隱式構(gòu)造函數(shù)
如果類中沒有顯式定義構(gòu)造函數(shù),那么c++編譯器將會(huì)自動(dòng)生成一個(gè)無(wú)參的默認(rèn)構(gòu)造函數(shù),而如果用戶顯式定義了構(gòu)造函數(shù),那么編譯器將不再生成構(gòu)造函數(shù)。
需要注意的是編譯器自己生成的構(gòu)造函數(shù)在初始化對(duì)象時(shí)做了一個(gè)偏心的處理:即對(duì)于內(nèi)置類型,編譯器不會(huì)處理;而對(duì)于自定義類型,編譯器會(huì)自定義類型調(diào)用它自己的默認(rèn)構(gòu)造函數(shù)。內(nèi)置類型指的是語(yǔ)法已經(jīng)定義好的類型,如:int,double,long等等;自定義類型是使用struct/class/union定義的類型。
這是什么意思呢?我們通過(guò)下面這個(gè)代碼來(lái)理解:
class C { public: C() { cout << "C()" << endl; } private: int _c; }; class Date { public: //若用戶顯式定義了構(gòu)造函數(shù),那么編譯器將不再生成 /*Date() { _year = 0; _month = 1; _day = 1; } Date(int year, int month, int day) { _year = year; _month = month; _day = day; }*/ private: //內(nèi)置類型 int _year; int _month; int _day; //自定義類型 C c1; }; int main() { Date d1;//調(diào)用無(wú)參的構(gòu)造函數(shù) return 0; }
通過(guò)調(diào)試可以發(fā)現(xiàn),d1自身的內(nèi)置類型變量仍為隨機(jī)值,編譯器調(diào)用的構(gòu)造函數(shù)并沒有處理,而對(duì)于自定義類型,可以看到編譯器調(diào)用了自定義類型中的默認(rèn)函數(shù),但是實(shí)際上如果調(diào)用編譯器自己生成的默認(rèn)構(gòu)造函數(shù),最終的結(jié)果就是所有的內(nèi)置類型變量仍然為隨機(jī)值,這么看下來(lái)好像編譯器自己生成的構(gòu)造函數(shù)好像沒什么用?
實(shí)則不然,比如我們?cè)鲞^(guò)用棧實(shí)現(xiàn)隊(duì)列的題,這道題的思路是用兩個(gè)棧來(lái)回倒保證隊(duì)列的先進(jìn)先出,而這里面的兩個(gè)結(jié)構(gòu)棧和用棧實(shí)現(xiàn)的隊(duì)列的代碼為:
class Stack//棧 { public: Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); if (_a == nullptr) { cout << "malloc fail" << endl; exit(-1); } _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; struct MyQueue//用兩個(gè)棧實(shí)現(xiàn)隊(duì)列 { Stack s1; Stack s2; };
可以看到在用MyQueue這個(gè)類實(shí)例化對(duì)象時(shí),編譯器調(diào)用Stack中的構(gòu)造函數(shù)分別對(duì)成員變量s1和s2初始化,因此,我們無(wú)需再對(duì)其進(jìn)行初始化了,這相對(duì)來(lái)說(shuō)方便了許多。
無(wú)參和全缺省的函數(shù)均為默認(rèn)構(gòu)造函數(shù)
無(wú)參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都被稱為默認(rèn)構(gòu)造函數(shù),但是需要注意的是:無(wú)參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)二者只能存在一個(gè),這是因?yàn)椋绻叨即嬖诘脑?,那么在?shí)例化對(duì)象不帶參數(shù)時(shí),編譯器無(wú)法區(qū)分是調(diào)用哪一個(gè)函數(shù)。
class Date { public: Date() { _year = 0; _month = 1; _day = 1; } Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1;//錯(cuò)誤,編譯器無(wú)法識(shí)別要調(diào)用哪一個(gè)構(gòu)造函數(shù) return 0; }
在實(shí)際過(guò)程中,我們更傾向于使用全缺省的構(gòu)造函數(shù),因?yàn)樗藷o(wú)參的構(gòu)造函數(shù)的情況。
成員變量的命名風(fēng)格
可以注意到的是在定義類的時(shí)候成員變量前都加了一個(gè)_,這是為了防止下面這種情況:
class Date { public: Date(int year = 0, int month = 1, int day = 1) { year = year; month = month; day = day; } void Print() { cout << year << "/" << month << "/" << day << endl; } private: int year; int month; int day; }; int main() { Date d1; d1.Print(); return 0; }
可以看到,d1調(diào)用了構(gòu)造函數(shù)后,其成員變量認(rèn)為隨機(jī)值。這是因?yàn)樵趛ear = year這句代碼中,兩個(gè)year變量均為函數(shù)形參,實(shí)際上編譯器在處理這種變量時(shí),會(huì)遵循局部?jī)?yōu)先原則,即編譯器在函數(shù)形參中找到了year變量,就不會(huì)繼續(xù)擴(kuò)大搜索范圍去尋找成員變量中的year變量,而在Print函數(shù)中,編譯器由于在形參中未找到y(tǒng)ear變量,因此繼續(xù)擴(kuò)大搜索范圍,在成員變量中找到了year并使用之。
因此,在聲明成員變量的命名時(shí)需要遵循一定的規(guī)范,常見的有:(1)在變量名前加_,如_year (2)在變量名后加_,如year_ (3)駝峰法,如mYear,m表示member。
另外,上述情況可以通過(guò)使用this指針進(jìn)行解決,即將代碼改為this->year = year;但在實(shí)際使用過(guò)程中,最好還是注重成員變量的命名
補(bǔ)充
由于早期c++語(yǔ)法設(shè)計(jì)的缺陷,編譯器默認(rèn)生成的構(gòu)造函數(shù)并不會(huì)對(duì)內(nèi)置類型變量初始化,因此在c++11后,語(yǔ)法委員會(huì)在成員變量聲明處打了一個(gè)補(bǔ)丁,運(yùn)行,變量聲明的同時(shí)加上缺省值,比如:
class Date { public: Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } private: //注意,此處僅為缺省值,仍為變量聲明,而非初始化(定義) int _year = 0; int _month = 1; int _day = 1; };
析構(gòu)函數(shù)
1.概念
與構(gòu)造函數(shù)相比,析構(gòu)函數(shù)相對(duì)簡(jiǎn)單一些。析構(gòu)函數(shù)的作用與構(gòu)造函數(shù)的相反,析構(gòu)函數(shù)并不是完成對(duì)象的銷毀,因?yàn)榫植繉?duì)象的銷毀工作是由編譯器來(lái)完成的。一個(gè)詞來(lái)概括析構(gòu)函數(shù)的作用就是清理,即對(duì)象在銷毀的時(shí)候會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),完成類當(dāng)中的一些資源清理工作。
2.特性
析構(gòu)函數(shù)是一種特殊的成員函數(shù),其特征如下:
析構(gòu)函數(shù)名是類名前加上~號(hào)
析構(gòu)函數(shù)無(wú)參數(shù)無(wú)返回值
一個(gè)類有且只有一個(gè)析構(gòu)函數(shù)
若析構(gòu)函數(shù)為顯式定義,那么系統(tǒng)會(huì)自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)。
與構(gòu)造函數(shù)一樣,系統(tǒng)的默認(rèn)析構(gòu)函數(shù)對(duì)于內(nèi)置類型變量不會(huì)處理,對(duì)于自定義變量會(huì)調(diào)用其自身的析構(gòu)函數(shù)。
其次,對(duì)于Date類這樣的類,由于其內(nèi)部沒有什么資源需要處理,因此不需要析構(gòu)函數(shù);對(duì)于Stack這樣的類,其內(nèi)部由資源需要處理,比如對(duì)malloc出來(lái)的空間進(jìn)行釋放,因此需要實(shí)現(xiàn)析構(gòu)函數(shù)。
還是之前的代碼,在用兩個(gè)棧實(shí)現(xiàn)隊(duì)列中,在Stack類中實(shí)現(xiàn)了構(gòu)造函數(shù)和析構(gòu)函數(shù),那么用MyQueue實(shí)例化my變量后無(wú)法自己實(shí)現(xiàn)初始化和空間的釋放:
class Stack { public: Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); if (_a == nullptr) { cout << "malloc fail" << endl; exit(-1); } _top = 0; _capacity = capacity; } ~Stack() { free(_a); _a = NULL; _top = _capacity = 0; } private: int* _a; int _top; int _capacity; }; struct MyQueue { Stack s1; Stack s2; }; int main() { //我們無(wú)需自己對(duì)mq進(jìn)行初始化和清理空間 //編譯會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù) MyQueue mq; return 0; }
c++編譯器在對(duì)象生命周期結(jié)束時(shí)自動(dòng)調(diào)用析構(gòu)函數(shù)
class Date { public: Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; return 0;//編譯器在執(zhí)行這句代碼的同時(shí)會(huì)調(diào)用類中的析構(gòu)函數(shù) }
拷貝構(gòu)造函數(shù)
1.概念
拷貝構(gòu)造函數(shù),顧名思義,其作用就是創(chuàng)建一個(gè)和被拷貝對(duì)象一模一樣的對(duì)象。
拷貝構(gòu)造函數(shù)只有單個(gè)形參,該形參是對(duì)本類類型對(duì)象的引用(一般常用const修飾),在用已存在的類類型對(duì)象創(chuàng)建新對(duì)象時(shí)由編譯器自動(dòng)調(diào)用。
2.特性
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特征是:
拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式
參數(shù)只有一個(gè)且為引用傳參
拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須為引用傳參,使用傳值方式會(huì)引發(fā)無(wú)窮遞歸調(diào)用。
class Date { public: Date() { _year = 0; _month = 1; _day = 1; } Date(int year, int month, int day) { _year = year; _month = month; _day = day; } Date(Date& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(d1); return 0; }
那么為什么說(shuō)傳值會(huì)導(dǎo)致無(wú)窮遞歸調(diào)用呢?首先我們需要理解到調(diào)用函數(shù)傳值給形參也是一種拷貝,比如說(shuō):
同樣的,對(duì)于拷貝構(gòu)造函數(shù),若形參為傳值調(diào)用,那么在上述代碼中將d2賦值給形參d時(shí)也會(huì)調(diào)用拷貝構(gòu)造函數(shù),而每一次調(diào)用拷貝構(gòu)造函數(shù)都會(huì)經(jīng)過(guò)依次賦值操作,從而導(dǎo)致無(wú)窮遞歸調(diào)用:
而傳引用就能夠很好的解決這個(gè)問題,其次,傳指針也可以達(dá)到目的,不過(guò)一般傳引用的話可以增強(qiáng)代碼可讀性。
若未顯式定義,系統(tǒng)會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)
與構(gòu)造函數(shù)一樣,如果我們自己沒有實(shí)現(xiàn)拷貝構(gòu)造函數(shù),那么編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù);但是與構(gòu)造函數(shù)不同的是,默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)于內(nèi)置類型和自定義類型變量都會(huì)處理:
(1)對(duì)于內(nèi)置類型,默認(rèn)的拷貝構(gòu)造函數(shù)會(huì)對(duì)對(duì)象進(jìn)行淺拷貝,即按照內(nèi)存存儲(chǔ)中的字節(jié)序?qū)?duì)象進(jìn)行拷貝,也叫值拷貝。
(2)對(duì)于自定義類型,默認(rèn)的拷貝構(gòu)造函數(shù)會(huì)調(diào)用自定義類型中自己的拷貝構(gòu)造函數(shù)。
class A { public: A() { _a = 0; } A(const A& a) { cout << "A(const A& a)" << endl; } private: int _a; }; class Date { public: Date() { _year = 0; _month = 1; _day = 1; } Date(int year, int month, int day) { _year = year; _month = month; _day = day; } //調(diào)用默認(rèn)的拷貝構(gòu)造函數(shù) /*Date(Date& d) { _year = d._year; _month = d._month; _day = d._day; }*/ private: int _year; int _month; int _day; A aa; }; int main() { Date d1; Date d2(d1); return 0; }
淺拷貝的注意事項(xiàng)
通過(guò)上面我們知道了默認(rèn)的拷貝構(gòu)造函數(shù)能夠?qū)崿F(xiàn)淺拷貝,也就是說(shuō),對(duì)于Date這樣的類,我們無(wú)需自己實(shí)現(xiàn)拷貝構(gòu)造函數(shù)只用默認(rèn)的拷貝構(gòu)造函數(shù)就能夠?qū)崿F(xiàn)拷貝目的,那么是否用編譯器自己的函數(shù)就夠了呢?
其實(shí)不然,比如我們熟知的Stack類,如果直接調(diào)用系統(tǒng)默認(rèn)的拷貝構(gòu)造函數(shù):
class Stack { public: Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); if (_a == nullptr) { cout << "malloc fail" << endl; exit(-1); } _top = 0; _capacity = capacity; } ~Stack() { free(_a); _a = NULL; _top = _capacity = 0; } private: int* _a; int _top; int _capacity; }; int main() { Stack s1(8); Stack s2(s1); return 0; }
上述代碼,我們運(yùn)行后發(fā)現(xiàn),程序崩潰了,這是為什么呢?這是因?yàn)橄到y(tǒng)默認(rèn)的拷貝構(gòu)造函數(shù)拷貝出了一份與s1一模一樣的s2:
而我們知道當(dāng)對(duì)象的生命周期結(jié)束時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)對(duì)類空間進(jìn)行清理,由于s2是后壓棧的,因此會(huì)先清理,這時(shí)s2._a所指的空間已經(jīng)free還給操作系統(tǒng)了,但是s1還會(huì)再次調(diào)用析構(gòu)函數(shù),將已經(jīng)釋放的s1._a所指向的空間再一次釋放(注意,s2._a釋放完后s1._a仍指向原空間,此時(shí)s1._a為野指針),這個(gè)操作最終會(huì)導(dǎo)致程序崩潰。
可見編譯器默認(rèn)的拷貝構(gòu)造函數(shù)并不能解決所有的問題,淺拷貝會(huì)導(dǎo)致一些錯(cuò)誤,那么要如何解決淺拷貝的帶來(lái)的問題呢?這就要我們之后介紹的深拷貝來(lái)解決了。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)酒店管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03C/C++自主分配出現(xiàn)double free or corruption問題解決
這篇文章主要為大家介紹了C/C++出現(xiàn)double free or corruption問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04C語(yǔ)言新手初階教程之三子棋實(shí)現(xiàn)
相信大家在小時(shí)候都用紙和筆與小伙伴們玩過(guò)一個(gè)經(jīng)典的游戲之井字棋,即三子棋,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言新手初階教程之三子棋實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2022-01-01C語(yǔ)言的分支和循環(huán)語(yǔ)句你真的了解嗎
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言的分支和循環(huán)語(yǔ)句,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02基于C語(yǔ)言實(shí)現(xiàn)鉆石棋游戲的示例代碼
獨(dú)立鉆石是源于18世紀(jì)法國(guó)的宮廷貴族的自我挑戰(zhàn)類單人棋游戲,可以鍛煉邏輯思維能力。本文將用C語(yǔ)言實(shí)現(xiàn)這一簡(jiǎn)單的游戲,感興趣的小伙伴可以了解一下2023-02-02C++ 讀寫文件安全又簡(jiǎn)潔的簡(jiǎn)單實(shí)例
這篇文章主要介紹了C++ 讀寫文件安全又簡(jiǎn)潔的簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06