C++超詳細(xì)講解構(gòu)造函數(shù)
類的6個(gè)默認(rèn)成員函數(shù)
如果我們寫了一個(gè)類,這個(gè)類我們只寫了成員變量沒有定義成員函數(shù),那么這個(gè)類中就沒有函數(shù)了嗎?并不是的,在我們定義類時(shí)即使我們沒有寫任何成員函數(shù),編譯器會(huì)自動(dòng)生成下面6個(gè)默認(rèn)成員函數(shù)。
class S { public: int _a; };
這里就來詳細(xì)介紹一下構(gòu)造函數(shù)。
構(gòu)造函數(shù)
使用C語言,我們用結(jié)構(gòu)體創(chuàng)建一個(gè)變量時(shí),變量的內(nèi)容都是隨機(jī)值,要想要能正確的操作變量中存儲(chǔ)的數(shù)據(jù),我們還需要調(diào)用對(duì)應(yīng)的初始化函數(shù),給成員變量賦一個(gè)合適的初值。那么C++呢,我們?nèi)匀皇褂眠@個(gè)方法來試試。
class Date { public: void SetDate(int year, int month, int day) { _year = year; _month = month; _day = day; } void Display() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1, d2; d1.SetDate(2018, 5, 1);//初始化 d1.Display(); d2.SetDate(2018, 7, 1);//初始化 d2.Display(); return 0; }
完全沒問題的,畢竟C++完全兼容C嘛,不過這樣子做未免有點(diǎn)麻煩,能不能做到我一創(chuàng)建好對(duì)象,對(duì)象的成員變量就是已經(jīng)被初始化了而不是我們主動(dòng)去調(diào)用呢?C++提供了一個(gè)特殊的函數(shù):構(gòu)造函數(shù)。
構(gòu)造函數(shù)是一個(gè)特殊的成員函數(shù),名字與類名相同,無返回值,每次使用類實(shí)例化對(duì)象時(shí)會(huì)自動(dòng)調(diào)用,保證每個(gè)數(shù)據(jù)成員都有一個(gè)合適的初值,方便我們的后續(xù)使用,構(gòu)造函數(shù)在對(duì)象的生命周期內(nèi)只會(huì)被調(diào)用一次。
特性
雖然叫構(gòu)造函數(shù),但它的作用并不是構(gòu)造一個(gè)對(duì)象(申請(qǐng)空間創(chuàng)建對(duì)象),而是初始化對(duì)象。
特征如下
- 函數(shù)名與類名相同;
- 無返回值;
- 對(duì)象實(shí)例化時(shí)編譯器自動(dòng)調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù);
- 構(gòu)造函數(shù)可以重載;
class Date { public: Date()//無參的構(gòu)造函數(shù) { } Date(int year, int month, int day)//帶參的構(gòu)造函數(shù) { _year = year; _month = month; _day = day; } void Display() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1;//調(diào)用無參的構(gòu)造函數(shù) Date d2(2001, 7, 28);//調(diào)用帶參的構(gòu)造函數(shù) d1.Display(); d2.Display(); Date d3();//這種寫法要不得,會(huì)被當(dāng)做函數(shù)名為d3,無參、返回類型為Date的函數(shù)聲明。 //調(diào)用無參的構(gòu)造函數(shù),一定不要加()否則編譯器無法識(shí)別這是一個(gè)函數(shù)聲明還是調(diào)用的無參構(gòu)造。 return 0; }
如果編譯器沒有顯式的定義構(gòu)造函數(shù),編譯器會(huì)自動(dòng)生成一個(gè)無參的默認(rèn)構(gòu)造函數(shù),一旦我們顯式定義,編譯器就不再生成;
class Date { public: void Display() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1;//同樣能創(chuàng)建一個(gè)對(duì)象 d1.Display(); return 0; }
輸出:
可以看到使用編譯器生成的默認(rèn)構(gòu)造函數(shù)我們的日期仍然是隨機(jī)值。
無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都稱為默認(rèn)構(gòu)造函數(shù),并且默認(rèn)構(gòu)造函數(shù)只能有一個(gè)。注意:無參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、我們沒寫編譯器默認(rèn)生成的構(gòu)造函數(shù),都可以認(rèn)為是默認(rèn)構(gòu)造函數(shù)。
class Date { public: Date()//無參的默認(rèn)構(gòu)造函數(shù) { } Date(int year = 2001, int month = 7, int day = 28)//全缺省的默認(rèn)構(gòu)造函數(shù) { _year = year; _month = month; _day = day; } void Display() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1;//這里無法編譯通過,因?yàn)檎{(diào)用不明確。 d1.Display(); return 0; }
一般我們使用全缺省的構(gòu)造函數(shù),既可以不傳參用缺省值去初始化對(duì)象,也可以顯式地去調(diào)用并用實(shí)參初始化對(duì)象。
編譯器生成的默認(rèn)構(gòu)造函數(shù)
欸好像這貨沒什么用啊,我剛剛使用這個(gè)自動(dòng)生成的,我的對(duì)象的初值還是隨機(jī)值啊,看起來就像這構(gòu)造函數(shù)什么事都沒有做。
其實(shí)C++把類型分成內(nèi)置類型(基本類型)和自定義類型。
內(nèi)置類型就是語法已經(jīng)定義好的類型:如int/char…,自定義類型就是我們使用class/struct/union自己定義的類型,看看下面的程序,就會(huì)發(fā)現(xiàn)編譯器生成默認(rèn)的構(gòu)造函數(shù)會(huì)對(duì)自定類型成員_t調(diào)用的它的默認(rèn)成員函數(shù)。
不過這涉及到我們還沒學(xué)過的知識(shí)——初始化列表,后面會(huì)講。
class Time { public: Time(int hour = 0, int minute = 0, int second = 0) { cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl; _hour = hour; _minute = minute; _second = second; } private: int _hour; int _minute; int _second; }; class Date { public: //使用編譯器默認(rèn)的構(gòu)造函數(shù) void Display() { cout << _year << "-" << _month << "-" << _day << endl; } private: //內(nèi)置類型 int _year; int _month; int _day; //自定義類型 Time _t; }; int main() { Date d1; d1.Display(); return 0; }
輸出:
Time(int hour = 0,int minute = 0,int second = 0)
-858993460–858993460–858993460
事實(shí)上編譯器生成的默認(rèn)構(gòu)造函數(shù)并不是什么都沒有做,而是只處理了成員變量中的自定義類型,而沒有去初始化內(nèi)置類型,調(diào)用自定義類型成員的構(gòu)造函數(shù)就是在初始化列表做的,下面會(huì)詳細(xì)講。
成員變量的命名風(fēng)格
看看下面這種成員命名方式有什么缺陷?
class Time { public: Time(int hour = 0, int minute = 0, int second = 0) { cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl; hour = hour; minute = minute; second = second; } private: int hour; int minute; int second; };
簡(jiǎn)直太難看了,hour = hour這是什么操作???到底哪個(gè)hour是成員變量,讓人去猜嗎,代碼實(shí)在丑陋,因此我們?cè)趯?duì)成員變量命名時(shí),為了初始化對(duì)象不會(huì)因?yàn)榘l(fā)生命名沖突,又能一眼看出來哪個(gè)形參是對(duì)應(yīng)初始化哪個(gè)成員變量的,我們通常在對(duì)成員變量命名方式統(tǒng)一成成員變量名前加上下劃線_。
class Time { public: Time(int hour = 0, int minute = 0, int second = 0) { cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl; _hour = hour; _minute = minute; _second = second; } private: int _hour; int _minute; int _second; };
當(dāng)然不止是這樣,適合自己的才是最好的,不過一般都是采用加一個(gè)前綴或者加上一個(gè)后綴的方式來命名成員變量。
這里很容易弄混兩個(gè)概念,在此強(qiáng)調(diào)一下:
默認(rèn)構(gòu)造函數(shù)是不需要參數(shù)的構(gòu)造函數(shù),有以下三種:
- 編譯器生成的;
- 顯式定義的無參的構(gòu)造函數(shù);
- 顯式定義的全缺省的構(gòu)造函數(shù);
默認(rèn)成員函數(shù)是我們?nèi)绻粚懀幾g器會(huì)自動(dòng)生成的函數(shù);
構(gòu)造函數(shù)的初始化列表
前面說了,在實(shí)例化一個(gè)類的對(duì)象時(shí)會(huì)自動(dòng)去調(diào)用類的構(gòu)造函數(shù)進(jìn)行對(duì)象的初始化操作,那在C++中一個(gè)自定義類型的過程可分為兩個(gè)過程:
- 為對(duì)象分配內(nèi)存;
- 對(duì)成員變量賦值;
- 函數(shù)體內(nèi)賦值;
那我們想想如果成員變量是具有常屬性的,那么是不是2過程就無法生效了?那么對(duì)于那些具有常性的變量我們以前是怎么定義的呢?初始化操作可以完成這個(gè)問題。
初始化是什么?變量在定義的同時(shí)為它設(shè)定一個(gè)初值,例如:引用必須初始化
int& a = 10;
,這就是一個(gè)典型的初始化操作,而我們要談的初始化列表也是相似的,那么這種初始化操作有什么與眾不同的呢?
答案是:對(duì)于那些一旦有初值就不能再被賦值的變量,初始化列表的作用就體現(xiàn)出來了,例如被const修飾的變量,或者是引用,這些都是具有常屬性的,因此就需要在它們創(chuàng)建的過程中就給它們一個(gè)初值,保證其可以被正常初始化。
class S { public: S(int i = 0, int j = 0) { _i = i; _j = j; } private: const int _i;//const修飾i具有常屬性 int _j; }; int main() { S s; return 0; }
報(bào)錯(cuò):
“S::_i”: 必須初始化常量限定類型的對(duì)象
error C2166: 左值指定 const 對(duì)象
即在編譯器看來,這個(gè)對(duì)象包括對(duì)象中的成員變量在進(jìn)入構(gòu)造函數(shù)的函數(shù)體后,就都已經(jīng)初始化完成了,因此const修飾的變量i就無法再被賦值了。
既然初始化列表是在創(chuàng)建變量階段對(duì)變量進(jìn)行的初始化,因此就可以使用初始化列表處理給那些無法修改的變量。
初始化列表格式如下:
class S { public: S(int i = 0, int j = 0) :_i(i), _j(j) {} private: const int _i; int _j; }; int main() { S s; return 0; }
程序正常運(yùn)行
同時(shí)呢,建議能使用初始化列表就使用,盡量不在構(gòu)造函數(shù)的函數(shù)體中為成員變量再賦值,養(yǎng)成好的習(xí)慣。
下面分析編譯器生成的默認(rèn)構(gòu)造函數(shù)到底做了什么事情
class Time { public: Time(int hour = 0, int minute = 0, int second = 0) :_hour(hour), _minute(minute), _second(second) { cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl; } void DisPlay() { cout << _hour << "-" << _minute << "-" << _second << endl; } private: int _hour; int _minute; int _second; }; class Date { public: void Display() { cout << "Date:"; cout << _year << "-" << _month << "-" << _day << endl; cout << "Time:"; _t.DisPlay(); } private: int _year; int _month; int _day; Time _t; }; int main() { Date d1; d1.Display(); return 0; }
輸出:
可以看到,Date類中的內(nèi)置類型都未被初始化,而對(duì)于自定義類型是去調(diào)用了其默認(rèn)構(gòu)造函數(shù)并且初始化成功了的。
這里要記住一定是調(diào)用的自定義成員的默認(rèn)構(gòu)造函數(shù),因?yàn)榫幾g器生成的Date的默認(rèn)構(gòu)造函數(shù)調(diào)用Time的構(gòu)造時(shí)默認(rèn)是不傳參的,畢竟它也不知道傳什么嘛。
如果我們把Time構(gòu)造函數(shù)的缺省值去掉,那么Time就沒有默認(rèn)構(gòu)造函數(shù),那么創(chuàng)建Date對(duì)象時(shí)就無法調(diào)用Time的默認(rèn)構(gòu)造函數(shù),就出錯(cuò)了。如下
class Time { public: Time(int hour, int minute, int second) :_hour(hour), _minute(minute), _second(second) { cout << "Time(int hour = 1, int minute = 1, int second = 1)" << endl; } void DisPlay() { cout << _hour << "-" << _minute << "-" << _second << endl; } private: int _hour; int _minute; int _second; }; class Date { public: void Display() { cout << "Date:"; cout << _year << "-" << _month << "-" << _day << endl; cout << "Time:"; _t.DisPlay(); } private: int _year; int _month; int _day; Time _t; }; int main() { Date d1; d1.Display(); return 0; }
報(bào)錯(cuò):
message : “Date::Date(void)”: 由于 數(shù)據(jù)成員“Date::_t”不具備相應(yīng)的 默認(rèn)構(gòu)造函數(shù)
那我們現(xiàn)在已經(jīng)知道了編譯器生成的默認(rèn)構(gòu)造函數(shù)它能做什么了,接下來我們顯式地去定義一個(gè)構(gòu)造函數(shù)。
class Time { public: Time(int hour = 0, int minute = 0, int second = 0) :_hour(hour), _minute(minute), _second(second) { cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl; } void DisPlay() { cout << _hour << "-" << _minute << "-" << _second << endl; } private: int _hour; int _minute; int _second; }; class Date { public: Date() : _year(),//調(diào)用了int的默認(rèn)構(gòu)造函數(shù),并且會(huì)給初始化為0 _month(), _day(), _t()//調(diào)用了Time的默認(rèn)構(gòu)造函數(shù),這里不寫也會(huì)自動(dòng)調(diào)用 { cout << "Date()" << endl; } void Display() { cout << "Date:"; cout << _year << "-" << _month << "-" << _day << endl; cout << "Time:"; _t.DisPlay(); } private: int _year; int _month; int _day; Time _t; }; int main() { Date d1; d1.Display(); return 0; }
我們?yōu)镈ate定義一個(gè)無參的默認(rèn)構(gòu)造函數(shù),在初始化列表我不止處理了內(nèi)置類型還處理了自定義類型。
在C++中支持這樣一種定義變量的方法:
int a;//a是隨機(jī)值 int b(1); int();//創(chuàng)建匿名變量,調(diào)用默認(rèn)構(gòu)造
這和自定義類型的定義是一樣的格式,這是為了讓內(nèi)置類型也能按照自定義類型的方式去定義和初始化,看起來是去調(diào)用了int的構(gòu)造函數(shù)。
這樣,我們?cè)诔跏蓟斜碇姓{(diào)用了初始化了內(nèi)置類型和自定義類型,Date的內(nèi)置類型也都被初始化為0了,這也印證了編譯器生成的默認(rèn)構(gòu)造函數(shù)并沒有去調(diào)用int的默認(rèn)構(gòu)造,而只調(diào)用了自定義的默認(rèn)構(gòu)造。
注意:就算我們顯式的定義了構(gòu)造函數(shù),如果在初始化列表中不顯式的調(diào)用Time的構(gòu)造函數(shù),那么編譯器也會(huì)默認(rèn)去調(diào)用它的默認(rèn)構(gòu)造(創(chuàng)建自定義類型成員變量時(shí)就一定會(huì)調(diào)用),而我們一旦顯式的去調(diào)用了,那么走我們的調(diào)用。
C++中有這樣一個(gè)特性,編譯器能幫你做的,就算你不做它會(huì)自動(dòng)幫你完成,而你一旦做了他就會(huì)按照你的方式去完成。
具體什么意思呢?看代碼:
class Time { public: Time(int hour = 0, int minute = 0, int second = 0) :_hour(hour), _minute(minute), _second(second) { cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl; } void DisPlay() { cout << _hour << "-" << _minute << "-" << _second << endl; } private: int _hour; int _minute; int _second; }; class Date { public: Date() : _t()//這里即使不寫,編譯器會(huì)自動(dòng)去調(diào) { cout << "Date()" << endl; } void Display() { cout << "Date:"; cout << _year << "-" << _month << "-" << _day << endl; cout << "Time:"; _t.DisPlay(); } private: int _year; int _month; int _day; Time _t; }; int main() { Date d1; d1.Display(); cout << int() << endl; return 0; }
其實(shí)這個(gè)Date類的構(gòu)造函數(shù)的初始化過程可以說就是編譯器默認(rèn)生成的構(gòu)造相同了。
總之就是編譯器生成的默認(rèn)構(gòu)造函數(shù)會(huì)在初始化列表中調(diào)用成員中自定義類型的默認(rèn)構(gòu)造,而不會(huì)處理內(nèi)置類型,不論是我們顯式定義的還是編譯器生成的,編譯器都會(huì)默認(rèn)去調(diào)用自定義類的構(gòu)造函數(shù),除非我們?cè)诔跏蓟斜碇酗@式的去調(diào)用了成員的構(gòu)造函數(shù)。
再言簡(jiǎn)意駭,就是無論什么構(gòu)造函數(shù)(自動(dòng)生成,顯式定義)編譯器都會(huì)在初始化列表調(diào)用自定義類型成員的構(gòu)造函數(shù),而如果我們自己顯式調(diào)用了成員的構(gòu)造,就執(zhí)行我們所寫的。
可以不顯式定義構(gòu)造函數(shù)的情況
如果成員變量都是自定義類型并且不需要顯式調(diào)用構(gòu)造函數(shù),那么編譯器生成的默認(rèn)構(gòu)造函數(shù)就足以處理這種情況
? 其他情況都需要我們自己去定義構(gòu)造函數(shù)。
到此這篇關(guān)于C++超詳細(xì)講解構(gòu)造函數(shù)的文章就介紹到這了,更多相關(guān)C++構(gòu)造函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)順序表的基本操作指南(注釋很詳細(xì))
線性表是最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),而順序表又是最簡(jiǎn)單的線性表,其基本思想是用一段地址連續(xù)的儲(chǔ)存單元依次存儲(chǔ)線性表的數(shù)據(jù)元素,下面這篇文章主要給大家介紹了關(guān)于C語言實(shí)現(xiàn)順序表的基本操作,需要的朋友可以參考下2021-10-10深入了解C語言的動(dòng)態(tài)內(nèi)存管理
所謂動(dòng)態(tài)和靜態(tài)就是指內(nèi)存的分配方式。動(dòng)態(tài)內(nèi)存是指在堆上分配的內(nèi)存,而靜態(tài)內(nèi)存是指在棧上分配的內(nèi)存,本文將用5600字帶你深入了解動(dòng)態(tài)內(nèi)存管理,感興趣的可以學(xué)習(xí)一下2022-07-07深入分析C語言存儲(chǔ)類型與用戶空間內(nèi)部分布
這篇文章主要介紹了C語言存儲(chǔ)類型與用戶空間內(nèi)部分布,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12C語言的sleep、usleep、nanosleep等休眠函數(shù)的使用
本文主要介紹了C語言的sleep、usleep、nanosleep等休眠函數(shù)的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03