C++/類與對(duì)象/默認(rèn)成員函數(shù)@構(gòu)造函數(shù)的用法
名詞概念
默認(rèn)構(gòu)造函數(shù):不用傳參就可以調(diào)用的構(gòu)造函數(shù)。有3種默認(rèn)構(gòu)造函數(shù)(但是只能存在一個(gè)):
- 1、構(gòu)造函數(shù)的參數(shù)是全缺省的;
- 2、構(gòu)造函數(shù)是無(wú)參的;
- 3、編譯器自動(dòng)生成的(當(dāng)我們沒(méi)有編寫(xiě)構(gòu)造函數(shù)時(shí))
顯式構(gòu)造函數(shù):用戶自己編寫(xiě)的構(gòu)造函數(shù)。
隱式構(gòu)造函數(shù):編譯器主動(dòng)生成的。注意:當(dāng)用戶自己編寫(xiě)了構(gòu)造函數(shù)(包括拷貝構(gòu)造函數(shù)),也就是出現(xiàn)顯示構(gòu)造函數(shù)時(shí),便不會(huì)有隱式構(gòu)造函數(shù),即編譯器不會(huì)主動(dòng)生成了。
默認(rèn)成員函數(shù)
在C++的類中,有6個(gè)默認(rèn)的成員函數(shù)。所謂的默認(rèn)成員函數(shù),就是用戶自己沒(méi)在類中編寫(xiě)的“特殊”成員函數(shù),但是編譯器會(huì)自動(dòng)生成的成員函數(shù)(也叫“隱式成員函數(shù)”)。
(ps:不要跟上面的 “ 默認(rèn)成員函數(shù) ” 概念混淆了,說(shuō)不清道不明的,靠自己領(lǐng)會(huì)了)
如下所示
#include <iostream> using namespace std; class Date { }; int main() { Date d; // cout<<sizeof(d)<<endl; // 對(duì)象d的內(nèi)存大小是1 return 0; }
我們定義了一個(gè)類Date,而在Date這個(gè)類中既沒(méi)有成員變量,也沒(méi)有成員函數(shù),是一個(gè)空類。
然而實(shí)際上,編譯器自己默默地生成6個(gè)成員函數(shù)。分別是 默認(rèn)構(gòu)造函數(shù) 、 默認(rèn)析構(gòu)函數(shù) 、 默認(rèn)拷貝構(gòu)造函數(shù) 、 默認(rèn)賦值運(yùn)算符重載函數(shù) 、默認(rèn)取地址操作符重載函數(shù) 、 默認(rèn)const取地址操作符重載函數(shù)。
題外話,我們?cè)趍ain函數(shù)里面,創(chuàng)建了一個(gè)空類Date的對(duì)象(也叫實(shí)例),請(qǐng)問(wèn)對(duì)象d的大小是多少呢?驗(yàn)證發(fā)現(xiàn)對(duì)象d的內(nèi)存大小是1。面對(duì)這種情況我會(huì)產(chǎn)生兩個(gè)疑問(wèn):
1、即然Date是空類,那由空類產(chǎn)生的對(duì)象不應(yīng)該內(nèi)存是0嗎,怎么會(huì)是1?
2、即然有默認(rèn)的成員函數(shù),那成員函數(shù)不是也有內(nèi)存大小嗎?6個(gè)成員函數(shù)內(nèi)存大小所占空間大小怎么會(huì)是1?
文章末尾對(duì)這兩個(gè)疑問(wèn)進(jìn)行解答。
下面我們對(duì)默認(rèn)構(gòu)造函數(shù),展開(kāi)講解
構(gòu)造函數(shù)
概念
在C++中,構(gòu)造函數(shù)是一種特殊類型的成員函數(shù),用于在對(duì)象被創(chuàng)建時(shí)執(zhí)行初始化操作。構(gòu)造函數(shù)的名稱與類名相同,沒(méi)有返回類型,甚至不使用 void。
函數(shù)特征
- ① 函數(shù)名 與 類名相同
- ② 沒(méi)有返回類型
如下面所示,類中的成員函數(shù) Date 便是該類的構(gòu)造函數(shù)
#include <iostream> using namespace std; class Date { public: // 構(gòu)造函數(shù) Date() { } };
在c語(yǔ)言中,如果我們創(chuàng)建了一個(gè)變量,沒(méi)有初始化,那么變量將是隨機(jī)值。因此良好的編程習(xí)慣是,創(chuàng)建一個(gè)變量的同時(shí),順便給變量初始化,讓變量的值是可知的,可預(yù)測(cè)的。
在C++中同樣的道理,當(dāng)我們定義了一個(gè)類,用類創(chuàng)建對(duì)象時(shí),順便的就給對(duì)象初始化了。
與C語(yǔ)言的區(qū)別是,初始化的這個(gè)動(dòng)作,不用我們自己做,編譯器自動(dòng)幫我們做了 (當(dāng)我們編寫(xiě)定義了構(gòu)造函數(shù)時(shí),編譯器自動(dòng)去調(diào)用構(gòu)造函數(shù);當(dāng)我們沒(méi)有編寫(xiě)構(gòu)造函數(shù)時(shí),編譯器自動(dòng)調(diào)用自己生成的構(gòu)造函數(shù)) 。
可以這么理解:在C++的類中,構(gòu)造函數(shù)便是給對(duì)象初始化的函數(shù)(即 構(gòu)造函數(shù) 就是 初始化函數(shù))。
為了方便理解,先從顯示構(gòu)造函數(shù)說(shuō)起,也就是自己編寫(xiě)構(gòu)造函數(shù)。
顯示構(gòu)造函數(shù)
#include <iostream> using namespace std; class Date { public: // 成員函數(shù)1 Date() { // 默認(rèn)構(gòu)造函數(shù) _year=2023; _month=11; _day=29; } // 成員函數(shù)2 //Date(int year, int month, int day) //{ // _year = year; // _month = month; // _day = day; //} // 成員函數(shù)3 void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; // 會(huì)被編譯器轉(zhuǎn)換為 --> d1.Date(&d1) d1.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對(duì)象1中的日期 //Date d2(1976,9,9) // 對(duì)象2 //d2.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對(duì)象2中的日期 return 0; }
如上代碼所示,我們用類Date實(shí)例出對(duì)象d1,然后調(diào)用類中的成員函數(shù)3打印對(duì)象d1的所有成員變量(也就是日期),運(yùn)行程序,結(jié)果如下
驗(yàn)證結(jié)論是,對(duì)象d1在創(chuàng)建時(shí),編譯器自動(dòng)的去類里面尋找默認(rèn)構(gòu)造函數(shù)(也就是成員函數(shù)1),而我們?cè)诔蓡T函數(shù)1里面完成了所有成員變量的初始化??偟男Ч麃?lái)說(shuō)編譯器主動(dòng)的幫我們對(duì)對(duì)象d1初始化了。
到此構(gòu)造函數(shù)就可以結(jié)束了嗎?你覺(jué)得該程序還有什么不好的點(diǎn),可以有更友善靈活的點(diǎn)嗎?
我們發(fā)現(xiàn),不管我們用Date創(chuàng)建多少的對(duì)象,對(duì)象的初始化都是同一個(gè)日期2023-11-29。那么如果我要?jiǎng)?chuàng)建一個(gè)對(duì)象,但是初始化的日期是自己指定的呢?如下面創(chuàng)建一個(gè)對(duì)象d2,指定日期是1976-9-9。
Date d2(1976,9,9);
這時(shí),編譯器報(bào)錯(cuò)提示 “沒(méi)有與參數(shù)列表匹配的構(gòu)造函數(shù)”。
這是因?yàn)榍懊嫖覀儎?chuàng)建對(duì)象d1時(shí),編譯器找的是無(wú)參的、默認(rèn)構(gòu)造函數(shù),而類中的成員函數(shù)1正好就與之匹配。而現(xiàn)在我們創(chuàng)建了對(duì)象d2,但是初始化時(shí)傳入了我們指定的日期參數(shù),這時(shí)編譯器去類中找的是帶有參數(shù)的構(gòu)造函數(shù)(也就是成員函數(shù)2,代碼中我注釋掉了),而我們還沒(méi)有編寫(xiě)帶參的構(gòu)造函數(shù),所以編譯器找不到匹配的構(gòu)造函數(shù),便報(bào)錯(cuò)編譯不通過(guò)了。
解決方法是,沒(méi)有條件就給創(chuàng)建條件,我們?cè)僭陬愔卸x一個(gè)帶參的構(gòu)造函數(shù),也就成員函數(shù)2,如下:
// 成員函數(shù)2 Date(int year, int month, int day) { _year = year; _month = month; _day = day; }
這時(shí),我們創(chuàng)建對(duì)象2
Date d2(1976,9,9); d2.Print();
時(shí),編譯器便會(huì)找到成員函數(shù)2,完成對(duì)象2的成員變量的初始化。(clue:成員函數(shù)2不是默認(rèn)構(gòu)造函數(shù))
運(yùn)行結(jié)果如下:
到此,我們實(shí)現(xiàn)了構(gòu)造函數(shù)的全部功能,
- 1、當(dāng)創(chuàng)建對(duì)象不給定參數(shù)時(shí),調(diào)用默認(rèn)構(gòu)造函數(shù)初始化;
- 2、當(dāng)創(chuàng)建對(duì)象給指定參數(shù)時(shí),調(diào)用帶參數(shù)的構(gòu)造函數(shù)初始化。
但是,我們發(fā)現(xiàn)要實(shí)現(xiàn)這兩個(gè)功能,要寫(xiě)兩個(gè)構(gòu)造函數(shù),有點(diǎn)繁瑣,而且也很怪怪的,兩個(gè)構(gòu)造函數(shù)…那么有沒(méi)有方法用一個(gè)構(gòu)造函數(shù),實(shí)現(xiàn)上面兩個(gè)構(gòu)造函數(shù)的功能呢?如下:
實(shí)現(xiàn)方法:使用帶有全缺省參數(shù)的構(gòu)造函數(shù)(為了方便,記為成員函數(shù)4)。
class Date { public: // 成員函數(shù)4 // 更好的實(shí)現(xiàn)(默認(rèn)構(gòu)造函數(shù):不傳參就可以調(diào)用的函數(shù)) -> 全缺省 Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; cout << "Date()" << this << endl; // 用來(lái)查看構(gòu)造函數(shù)和析構(gòu)函數(shù) 創(chuàng)建和銷毀的順序 } // 成員函數(shù)3 void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; // 會(huì)被編譯器轉(zhuǎn)換為 --> d1.Date(&d1) d1.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對(duì)象1中的日期 Date d2(1976,9,9) // 對(duì)象2 d2.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對(duì)象2中的日期 }
當(dāng)創(chuàng)建對(duì)象不帶參時(shí),構(gòu)造函數(shù)使用缺省值初始化;
當(dāng)創(chuàng)建對(duì)象帶參時(shí),構(gòu)造函數(shù)使用接收到的參數(shù)初始化。
最后,我們對(duì)以上,成員函數(shù)1、2、4進(jìn)行總結(jié)分析:
1、當(dāng)在類中只定義了成員函數(shù)1時(shí),可以創(chuàng)建不帶參數(shù)的對(duì)象,但是卻無(wú)法創(chuàng)建指定初始值(帶參數(shù))的對(duì)象,如
Date d1(2023,11,29);
編譯器會(huì)報(bào)錯(cuò),錯(cuò)誤類型是“沒(méi)有與參數(shù)列表匹配的構(gòu)造函數(shù)”
2、當(dāng)在類中只定義了成員函數(shù)2時(shí),可以創(chuàng)建指定初始值(帶參數(shù))的對(duì)象,但是卻不能創(chuàng)建不帶參數(shù)的對(duì)象,如
Date d1;
編譯器會(huì)報(bào)錯(cuò),錯(cuò)誤類型是“類Date中沒(méi)有默認(rèn)構(gòu)造函數(shù)”。
3、當(dāng)在類定義了成員函數(shù)1和成員函數(shù)2,就可以解決以上兩種問(wèn)題。
4、可以只在類中只定義成員函數(shù)4,便能實(shí)現(xiàn)成員函數(shù)1、2的功能。需要注意的是:當(dāng)定義了成員函數(shù)4時(shí),不能同時(shí)存在成員函數(shù)1 或 成員函數(shù)2,否則將導(dǎo)致編譯器報(bào)錯(cuò),因?yàn)榫幾g器不知道該調(diào)用哪一個(gè)構(gòu)造函數(shù)。
至此,顯示構(gòu)造函數(shù)學(xué)習(xí)完畢!下面繼續(xù)來(lái)看看隱式構(gòu)造函數(shù)。
隱式構(gòu)造函數(shù)
通過(guò)前面的介紹,我們能夠得知,隱式構(gòu)造函數(shù)就是,當(dāng)用戶自己沒(méi)有編寫(xiě)定義構(gòu)造函數(shù)時(shí),編譯器自動(dòng)生成的默認(rèn)構(gòu)造函數(shù)。
下面我們驗(yàn)證一下編譯器自動(dòng)生成的默認(rèn)構(gòu)造函數(shù),在背后干了個(gè)啥。
class Time { public: // 構(gòu)造函數(shù) Time { _hour = 0; _minute = 0; _second = 0; cout<<"Time()"<<" :"<<" "; cout<<_hour<<_minute<<second<<endl; // 將初始化后的結(jié)果打印輸出 } private: int _hour; int _minute; int _second; }; class Date { public: // 成員函數(shù)1 void Print() { cout<<"Date:\t"; cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; Time _t; // 類Time 實(shí)例出的對(duì)象 _t }; int main() { Date d1; d1.Print(); }
運(yùn)行程序,結(jié)果如下:
運(yùn)行結(jié)果表明,編譯器自動(dòng)生成的,隱式構(gòu)造函數(shù)做了以下的事情:
1、對(duì)于內(nèi)置類型的成員變量,沒(méi)有做任何處理;
2、對(duì)于自定義類型的成員變量,會(huì)去調(diào)用自定義類型對(duì)象的構(gòu)造函數(shù)。
下面分析代碼:
1、首先我們定義了兩個(gè)類,分別是類Time 和 類Date;
2、在類Time中定義了無(wú)參的構(gòu)造函數(shù)Time()。在函數(shù)內(nèi),對(duì)所有成員變量都初始化為0,同時(shí)打印輸出初始化的結(jié)果;
3、在類Date中,我們沒(méi)有定義構(gòu)造函數(shù),由編譯器自主去生成。只定義了成員函數(shù)Print(),在函數(shù)內(nèi),打印出經(jīng)過(guò)編譯器生成的構(gòu)造函數(shù)初始化后的成員變量。注意:在類Date的成員變量中,存在著一個(gè)由類Time實(shí)例出的對(duì)象 _t;
4、在main函數(shù)中,我們用類Date實(shí)例出一個(gè)對(duì)象d1,當(dāng)我們創(chuàng)建出對(duì)象時(shí),編譯器便會(huì)主動(dòng)去調(diào)用自主產(chǎn)生的構(gòu)造函數(shù),對(duì)對(duì)象d1初始化。然后我們調(diào)用對(duì)象d1中的成員函數(shù)Print(),打印出初始化后的成員變量。根據(jù)最終的結(jié)果,我們可以總結(jié)出以下的結(jié)論:
編譯器自主生成的隱式構(gòu)造函數(shù),是一個(gè)大型的雙標(biāo)現(xiàn)場(chǎng),因?yàn)殡[式構(gòu)造函數(shù)針對(duì)內(nèi)置類型的成員變量并沒(méi)有做任何處理,
而對(duì)于自定義類型的成員變量,會(huì)去調(diào)用自定義類型創(chuàng)建的對(duì)象的構(gòu)造函數(shù)進(jìn)行初始化。(如果自定義類型中也沒(méi)有顯示構(gòu)造函數(shù),則調(diào)用自定義類型中的隱式構(gòu)造函數(shù))
ps:
內(nèi)置類型:char 、 int 、 double 、 float …
自定義類型 : 自己定義的類型,如類、結(jié)構(gòu)體、枚舉…
以上,便是對(duì)于C++中,類中6個(gè)默認(rèn)成員函數(shù)之一的構(gòu)造函數(shù)的學(xué)習(xí)記錄。
結(jié)局彩蛋:
問(wèn)題1:
給空類Date創(chuàng)建的對(duì)象d分配1個(gè)字節(jié)的空間,是因?yàn)?strong>存在。
雖然Date是空類,但是用Date創(chuàng)建出來(lái)的對(duì)象d是客觀上存在的,不要主觀的認(rèn)為Date是空類,Date創(chuàng)建的對(duì)象d也就不存在了。
即然對(duì)象d是客觀上實(shí)實(shí)在在存在的,而每個(gè)對(duì)象又是獨(dú)一無(wú)二的,因此每個(gè)對(duì)象在內(nèi)存中都有自己獨(dú)一無(wú)二的內(nèi)存地址。所以編譯器會(huì)為空類Date創(chuàng)建的對(duì)象d分配一個(gè)字節(jié)的內(nèi)存,確保對(duì)象d也有自己獨(dú)特的地址,保證了“確保每個(gè)對(duì)象都有獨(dú)特的地址”的原則。
ps:這個(gè)字節(jié)通常被稱為 “空對(duì)象占用的內(nèi)存” 或者 “對(duì)象的內(nèi)存對(duì)齊” 。它不包含任何用戶定義的成員變量,而是用于區(qū)分不同對(duì)象在內(nèi)存中的位置。
問(wèn)題2:
計(jì)算對(duì)象的大小時(shí),是不考慮成員函數(shù)的大小的,只考慮成員變量的大小。思考一下,這樣設(shè)計(jì)的用意。
當(dāng)我們用類創(chuàng)建很多不同的對(duì)象時(shí),對(duì)于對(duì)象而言,類中的什么成員是必須的,而什么成員不是硬需的呢?
對(duì)于每個(gè)對(duì)象而言,擁有各自的成員變量是必須的,比如日期類Date(成員變量為年、月、日),對(duì)象之間擁有自己特定的記錄的年、月、日,才有意義,如果所有對(duì)象都是統(tǒng)一固定的,那將沒(méi)有意義了,因?yàn)椴还軇?chuàng)建多少對(duì)象,表示的都是同一個(gè)信息。
而成員函數(shù)是實(shí)現(xiàn)一些功能,比如日期的加減等等,而這些功能函數(shù)對(duì)于所有的對(duì)象來(lái)說(shuō),都是通用的,因此如果每個(gè)對(duì)象都存入這些通用的函數(shù),是不是對(duì)于內(nèi)存很不友善,于是在類中的成員函數(shù)的內(nèi)存是不計(jì)入對(duì)象中的內(nèi)存中的,成員函數(shù)是被存到代碼區(qū),供所有類的對(duì)象使用。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++面試八股文之如何實(shí)現(xiàn)strncpy函數(shù)
strncpy函數(shù),主要用做字符串復(fù)制,將于字符從一個(gè)位置復(fù)制到另一個(gè)位置,那么如何實(shí)現(xiàn)一個(gè)strncpy函數(shù),下面小編就來(lái)和大家簡(jiǎn)單講講吧2023-07-07講解C++的do while循環(huán)和循環(huán)語(yǔ)句的嵌套使用方法
這篇文章主要介紹了講解C++的do while循環(huán)和循環(huán)語(yǔ)句的嵌套使用方法,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09C++編程模板匹配超詳細(xì)的識(shí)別手寫(xiě)數(shù)字實(shí)現(xiàn)示例
大家好!本篇文章是關(guān)于手寫(xiě)數(shù)字識(shí)別的,接下來(lái)我將在這里記錄我的手寫(xiě)數(shù)字識(shí)別的從零到有,我在這里把我自己的寫(xiě)代碼過(guò)程發(fā)出來(lái),希望能幫到和我一樣努力求知的人2021-10-10C語(yǔ)言中0、‘\0‘、‘0‘、NULL以及類型轉(zhuǎn)化
在C語(yǔ)言中, NULL和0的值都是一樣的,但是為了目的和用途及容易識(shí)別的原因,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中0、‘\0‘、‘0‘、NULL以及類型轉(zhuǎn)化的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08C語(yǔ)言 數(shù)據(jù)結(jié)構(gòu)鏈表的實(shí)例(十九種操作)
這篇文章主要介紹了C語(yǔ)言 數(shù)據(jù)結(jié)構(gòu)鏈表的實(shí)例(十九種操作)的相關(guān)資料,需要的朋友可以參考下2017-07-07C++實(shí)現(xiàn)病人就醫(yī)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++語(yǔ)言實(shí)現(xiàn)病人就醫(yī)管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01