C++中的構(gòu)造函數(shù)詳解
普通變量的初始化
當(dāng)我們在定義一個變量不給它指定一個初始值時,這對于全局變量和局部變量來說結(jié)果會不一樣。全局變量在程序裝入內(nèi)存時 就已經(jīng)分配好空間,程序運行期間其地址不變,它會被初始化為全0(變量的每一位都為0)。但是局部變量定義在函數(shù)內(nèi)部,存儲在棧上,當(dāng)函數(shù)被調(diào)用時,棧會分配一部分空間來存儲該局部變量(也就是只分配空間,而不管賦值),這時此空間的值是已經(jīng)有的,它是一個隨機的值,如果程序員不對它進行初始化,將會產(chǎn)生不好的后果(如果將棧空間內(nèi)存的變量初始化工作交給編譯器,則每次函數(shù)調(diào)用都會被重新賦值,則會大大增加開銷)。
示例全局變量,這些對象會被賦一個默認的初始值。如圖:
構(gòu)造函數(shù)
對象和我們前面提到的基本類型的變量一樣,定義的時候也能夠被初始化。而這些初始化操作會比基本數(shù)據(jù)類型初始化更復(fù)雜一些,不僅涉及對類內(nèi)基本數(shù)據(jù)類型的初始化,還包括進行動態(tài)內(nèi)存分配,打開文件等操作。這時就需要為類設(shè)計一個構(gòu)造函數(shù)來專門負責(zé)這些操作,對象一旦創(chuàng)建就立即調(diào)用它,對其進行強制初始化(這是強制執(zhí)行的)。
構(gòu)造函數(shù)屬于成員函數(shù)比較特殊的一種(與其他成員函數(shù)不同,它不能被顯式的調(diào)用,所以也沒有必要為它設(shè)置返回值),它在對象的初始化時起作用(設(shè)計類時往往將初始化功能在構(gòu)造函數(shù)中實現(xiàn)),我們希望創(chuàng)建出的所有變量都能被賦予有意義的能力,而不是未知的,可以在創(chuàng)建之后再次改變他,但在此之前若要使用它,將會發(fā)生意外,所以在制度上我們就要對其進行嚴(yán)防死守(使其被創(chuàng)建后一定處于良好狀態(tài))。
構(gòu)造函數(shù)可以重載,可以編寫多個構(gòu)造函數(shù),參數(shù)列表不同。生成對象時,將會根據(jù)參數(shù)列表的參數(shù)信息決定該調(diào)用哪個構(gòu)造函數(shù)。如果沒有對參數(shù)有任何交代,編譯器就認為應(yīng)該調(diào)用無參構(gòu)造函數(shù)。
當(dāng)在局部空間或全局區(qū)創(chuàng)建自定義類型的對象時,構(gòu)造函數(shù)會被自動調(diào)用(設(shè)計好初始化后,每當(dāng)創(chuàng)建對象就會自動調(diào)用構(gòu)造函數(shù)會使每一個對象都處于良好的隨時可用的狀態(tài))。如圖:
以上演示是在類的外部對構(gòu)造函數(shù)進行了定義,實際開發(fā)中,一般將類的定義部分放到頭文件中(.h),而類內(nèi)成員函數(shù)的實現(xiàn)放到實現(xiàn)文件中(.cc)中。這對我們在不改變類的定義的情況下修改他的成員函數(shù)提供了方便。這比在類內(nèi)直接實現(xiàn)有一個好處,就是不需要每次改變函數(shù)體都要重新編譯(前題是不改變函數(shù)聲明)。
明確一點,如果在類內(nèi)對成員函數(shù)進行了實現(xiàn),它將會被隱式地聲明為內(nèi)聯(lián)函數(shù)(由編譯器決定是否最終把它變?yōu)閮?nèi)聯(lián)函數(shù))。
一般超過5行代碼的函數(shù)就沒有必要設(shè)為內(nèi)聯(lián)函數(shù)了(函數(shù)內(nèi)限制在兩個表達式最好)。
在我們只定義了有參的構(gòu)造函數(shù)后,就不會有無參的構(gòu)造函數(shù)了,需要我們自己定義一個無參的構(gòu)造函數(shù)。
class MyArray { char array_; public: MyArray(char a_new_array='x'):array_(a_new_array);//給出默認參數(shù) MyArray(); void PrintMyArray(); }; //類實現(xiàn)省略 void test() { MyArray a_long_array('c');//調(diào)用有參的構(gòu)造方法 MyArray a_short_array();//調(diào)用無參的構(gòu)造方法 }
初始化方法一般給出形參列表,在這里對類內(nèi)成員賦初值(此時構(gòu)造函數(shù)的函數(shù)體還未開始執(zhí)行),我們這里是對其輸入了一個字符。構(gòu)造函數(shù)還可以調(diào)用其他類的構(gòu)造函數(shù)來初始化對象中的成員變量(這是編譯器自動調(diào)用的,如在隱式的數(shù)據(jù)類型轉(zhuǎn)換時)。當(dāng)然也可以在構(gòu)造函數(shù)函數(shù)體內(nèi)為成員變量賦值(這并不是初始化,執(zhí)行函數(shù)體后賦值之前,成員變量已經(jīng)有一個無意義的值了)。
MyArray::MyArray(char init_array) {//進入構(gòu)造函數(shù)體后成員變量已經(jīng)有值了! array_=init_array;//在函數(shù)體內(nèi)進行賦值 cout << "MyArray的構(gòu)造方法執(zhí)行了!\n"; }
形參列表具有立即性(在成員變量被創(chuàng)建時形參列表的值就立即為其初始化),而構(gòu)造函數(shù)體內(nèi)需要執(zhí)行賦值語句。對于一些自定義類型的成員變量進行初始化,形參列表初始化往往比賦值語句效率更高。此外對于一些特殊的成員只能采用形參列表的方式進行初始化。
一定會生成默認構(gòu)造函數(shù)嗎?
前面我們說過,當(dāng)我們沒有編寫構(gòu)造函數(shù)時,編譯器會為我們生成一個默認的構(gòu)造函數(shù),對于初學(xué)者來說,這就夠了,但現(xiàn)在我們要強調(diào)幾點。
1.如果類中我們沒有定義構(gòu)造函數(shù)并且類中成員變量含有自定義類型成員變量時,編譯器會生成默認構(gòu)造函數(shù),并且調(diào)用自定義類型的構(gòu)造函數(shù)。
2.當(dāng)父類具有默認構(gòu)造函數(shù)而子類沒有任何構(gòu)造函數(shù),當(dāng)生成子類對象時,會生成子類的默認構(gòu)造函數(shù),在這個函數(shù)中調(diào)用父類默認構(gòu)造函數(shù)。
3.當(dāng)一個含有虛函數(shù)并且沒有構(gòu)造函數(shù)時編譯器會生成默認的構(gòu)造函數(shù)。
4.出現(xiàn)菱形繼承時,兩個父類分別進行虛繼承,當(dāng)出現(xiàn)子類對象時,子類會生成一個自己的默認構(gòu)造函數(shù),在構(gòu)造函數(shù)內(nèi)調(diào)用兩個父類的構(gòu)造函數(shù)。
5.在定義類時,直接為成員變量賦值,當(dāng)生成對象時,會調(diào)用默認構(gòu)造函數(shù)。
防止隱式類型轉(zhuǎn)換
如果構(gòu)造函數(shù)只有一個參數(shù)的話最好將此構(gòu)造函數(shù)聲明為ecplicit,他能防止編譯器將explicit的構(gòu)造函數(shù)進行隱式類型轉(zhuǎn)換。
class MyArray { char array_; double array_length_; public: MyArray(double init); MyArray(char init); void PrintMyArray(MyArray a_array); }; void MyArray::PrintMyArray(MyArray a_array) {//理應(yīng)接受一個對象,有時會接受一個基本類型 //此時會進行隱式類型轉(zhuǎn)換 cout << "MyArray為:"<< a_array.array_length_ << "\n"; } void test() { MyArray first_array(3.0); first_array.PrintMyArray(5.0);//隱式的將5.0轉(zhuǎn)化為了MyArray對象 }
修改如下:
class MyArray { char array_; double array_length_; public: explicit MyArray(double init); explicit MyArray(char init); void PrintMyArray(MyArray a_array); };
此時調(diào)用first_array.PrintMyArray(5.0)就會報錯了。
賦值與初始化的區(qū)別
必須明確初始化和賦值的概念,當(dāng)一個對象剛被創(chuàng)建時,對它的賦值操作為初始化,而賦值是修改一個已經(jīng)存在并且有值的對象。
int a_int=1;//初始化,a_int被創(chuàng)建出來并被立即初始化 a_int=2;//賦值,a_int已經(jīng)存在,沒有新的變量被創(chuàng)建出來
初始化出現(xiàn)在構(gòu)造函數(shù)中,而賦值出現(xiàn)在operator=操作符中。
一個對象只能被初始化一次,而賦值卻可以不限制次數(shù),在對象的聲明期內(nèi)都可以進行。而且進行賦值時可能會進行類型轉(zhuǎn)換(此時會產(chǎn)生臨時對象)。
一個構(gòu)造函數(shù)的初始化可以借用另一個構(gòu)造函數(shù),他被稱為委托構(gòu)造函數(shù)。
class MyArray { char array_; double array_length_{ 1.0 }; public: MyArray(double init, double in, double it); MyArray(double init); MyArray(); double GetMyArrayLength(); bool CompareMyArrayLength(MyArray a_array); void PrintMyArray(MyArray a_array); }; MyArray::MyArray(double init, double in, double it) { cout << "構(gòu)造函數(shù)1執(zhí)行了!" << endl; } MyArray::MyArray(double init) :MyArray{ init,init,init } { cout << "構(gòu)造函數(shù)2執(zhí)行了!" << endl; } //其他函數(shù)實現(xiàn)略 void test() { MyArray first_array{ 1.0,1.1,1.2 }; MyArray second_array{ 2.0 }; }
輸出如下:
在執(zhí)行構(gòu)造函數(shù)2時先利用構(gòu)造函數(shù)1進行初始化操作。
對象的計數(shù)
有時我們希望知道我們定義的一個類有多少個對象,就可以這樣操作:
class MyArray { static int sum_; public: MyArray(); ~MyArray(); static int GetMyArraySum() { return sum_; } }; int MyArray::sum_ = 0; MyArray::MyArray() { sum_++; } MyArray::~MyArray() { sum_--; } void test() { MyArray first_array; MyArray second_array; MyArray forth_array; cout << "MyArray有" << MyArray::GetMyArraySum() << "個對象" << endl; }
輸出如下:
因為創(chuàng)建對象的時候構(gòu)造函數(shù)一定會被調(diào)用,所以它能準(zhǔn)確的記錄當(dāng)前的對象數(shù)。
成員初始化的順序
一個類中成員的初始化順序和它們在類中被聲明的順序是一致的。編譯器會忽略構(gòu)造函數(shù)中的順序,這是因為對象的析構(gòu)需要和對象的成員構(gòu)造順序相反的要求 。
所以在編程中,成員變量的初始化順序應(yīng)與類定義中的聲明順序保持一致。
class Employee {//錯誤示范 string email_,first_name_,last_name_; public: Employee(const char* first_name,const char* last_name):first_name_(first_name),last_name_(last_name),email_(first_name_+last_name_+"@163.com"){}
類中定義的email_是在first_和last_之前的,但程序卻用其他未初始化的成員變量來為它初始化。
類的引用成員
如果類中的某個費靜態(tài)數(shù)據(jù)成員是一個引用的話,所有的引用必須被明確地初始化,所以在構(gòu)造函數(shù)中都要顯示初始化。
class MyArray { YourArray& a_array_; public: MyArray(YourArray&); } MyArray::MyArray(YourArray& init)//會被報錯 { }
這個成員引用有兩個特點,一,在創(chuàng)建a_array_時就要為它綁定一個對象,二,一旦綁定后,就不能再綁定其他變量了。
構(gòu)造函數(shù)使用注意事項
1.除非必要不要在構(gòu)造函數(shù)內(nèi)做與初始化對象無關(guān)的事情,減少它的功能可以使每個函數(shù)功能更加明確,增加效率。
2.類的非靜態(tài)const成員和引用成員只能在構(gòu)造函數(shù)的初始化列表中進行初始化,這是由const和引用自身特點決定的。
3.不能同時定義一個無參的構(gòu)造函數(shù)和一個參數(shù)全部為默認值得構(gòu)造函數(shù)。
4.拷貝構(gòu)造函數(shù)的參數(shù)應(yīng)為引用傳遞,如果為傳值則會與有一個參數(shù)的構(gòu)造函數(shù)沖突。
參考
The C++ Programming Language (美) Bjarne Stroustrup
cpp參考:https://zh.cppreference.com
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C語言中的結(jié)構(gòu)體內(nèi)嵌函數(shù)用法
這篇文章主要介紹了C語言中的結(jié)構(gòu)體內(nèi)嵌函數(shù)用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02Linux搭建C++開發(fā)調(diào)試環(huán)境的方法步驟
這篇文章主要介紹了Linux搭建C++開發(fā)調(diào)試環(huán)境的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10解析c語言中"函數(shù)調(diào)用中缺少哨兵"的情況分析
本篇文章是對c語言中"函數(shù)調(diào)用中缺少哨兵"的情況進行了詳細的分析介紹,需要的朋友參考下2013-05-05C++設(shè)計模式之簡單工廠模式的實現(xiàn)示例
這篇文章主要給大家介紹了關(guān)于C++設(shè)計模式之簡單工廠模式的相關(guān)資料,簡單工廠模式,主要用于創(chuàng)建對象,添加類時,不會影響以前的系統(tǒng)代碼,需要的朋友可以參考下2021-06-06C++中的三種繼承public,protected,private詳細解析
我們已經(jīng)知道,在基類以private方式被繼承時,其public和protected成員在子類中變?yōu)閜rivate成員。然而某些情況下,需要在子類中將一個或多個繼承的成員恢復(fù)其在基類中的訪問權(quán)限2013-09-09