關於構造函數,咱們耳熟能詳,彷佛都沒有必要成爲一個知識點,或者說是重要的知識點拿出來特殊說明,畢竟C++的編譯器都能幫咱們完成這個工做,只是,事情真的如想象的那麼簡單麼;ios
可能不是。c++
本文試圖挖掘關於構造函數,可能不是那麼簡單的一面,固然也不會很全面,權當一塊兒學習了。ide
構造函數的概念:提供類的對象的初始化的方式,類經過一個或幾個特殊的成員函數來控制對象的初始化過程。函數
有這個概念出發,咱們能夠知道,全部的構造函數都是在類的對象初始化時由系統調用的,具體調用哪一個是按重載函數的調用規則來的。工具
備註:構造函數不能被聲明爲const。能夠想一想爲什麼?學習
構造函數也不能是虛函數,這個應該好解釋。測試
這個最簡單,在面向對象的世界裏,萬物皆是對象,由於萬物皆須要構造函數,若是咱們沒有定義一個構造函數,那麼就由C++的編譯器幫咱們完成,在《c++ primer》裏叫作合成的默認構造函數。this
下面開始咱們的編碼求學之旅:編碼
首先,定義一個類設計者工具類:spa
#include <iostream> using namespace std; class ClassDesignTool { public: void printSp(){ cout << sp_ << "\n"; } private: string *sp_; };
在這樣一個什麼沒有寫構造函數的類裏,默認構造函數依然會在編譯階段生成,測試代碼以下:
ClassDesignTool tool; tool.printSp();
在VS2010的編譯環境下的結果是CCCCCCCC
,看到這個你應該很熟悉,這是Windows環境下對全部未顯式賦值變量的默認賦值,這也就能證實,Windows系統在編譯後使用默認合成構造函數,將成員變量sp_賦值爲CCCCCCCC
了。
若是你不放心,能夠把默認構造函數加上去,
ClassDesignTool(){};
測試的結果是同樣的。
這說明,若是你不許備在類的對象初始化時作點什麼,徹底能夠把這件事交給編譯器。反之,咱們須要作點別的工做了。
可能,你認爲默認的合成構造函數什麼事也沒作,對它心有怨恨,因此你決定出馬把它改寫(覆蓋之)。
ClassDesignTool():sp_(new string("lcksfa")){ cout << "use override default constructor " << "\n"; } //打印函數同時修改 void printSp(){ cout << "sp_ is " << sp_->c_str() << "\n"; }
測試結果:
use override default constructor sp_ is lcksfa
如今,咱們覆蓋(override)了默認構造函數,合成的默認構造函數不會被調用,而調用咱們本身的構造函數。
函數重載(overload)的概念,我相信你們都不會陌生,對於構造函數,一樣的也能將其重載。和調用普通的重載函數同樣,系統會在初始化對象時,根據不一樣的參數類型去調用不一樣的重載構造函數:
在上面的代碼裏添加以下代碼:
//overload constructor ClassDesignTool(const string& str) :sp_(new string(str)){ std::cout << "use overload constructor " << "\n"; }
以上,咱們重載了一個構造函數,其參數爲一個const string&類型。
ClassDesignTool tool4(string("4")); tool4.printSp();
測試結果以下:
use overload constructor sp_ is 4
這說明,當咱們添加了構造函數的重載函數後,使用string("4")參數構造對象時,調用了咱們的string參數的構造函數。
上面的東西都很簡單,下面,咱們說下稍微複雜的。
從函數重載層面,拷貝構造函數也是構造函數的重載,只是其參數爲本類的const引用,以下:
//copy constructor ClassDesignTool(const ClassDesignTool&);
ClassDesignTool::ClassDesignTool(const ClassDesignTool& rhs) { std::cout << "use copy constructor from " << rhs.sp_->c_str() << "\n"; sp_ = new string(*(rhs.sp_)); }
何時調用?
ClassDesignTool tool("lcksfa"); ClassDesignTool tool2(tool); tool2.printSp();
測試輸出:
use overload constructor use copy constructor from lcksfa sp_ is lcksfa
以上代碼說明,tool是使用的構造函數初始化,其參數爲"lcksfa",而tool2是使用拷貝構造函數初始化,其參數爲tool。
說完構造函數,說下析構函數。咱們知道對象在建立時調用了構造函數,而在銷燬時則會調用析構函數。
//destructor ~ClassDesignTool(){ std::cout <<"use destructor "<<sp_->c_str()<<"\n"; delete sp_; }
以上是析構函數,事實上,我已經把默認的析構函數給覆蓋了,緣由在於sp_的內存釋放,若是使用合成的默認析構函數,系統將不會釋放sp__的內存,從而致使內存泄漏。
和構造函數不一樣,析構函數沒有重載函數。這一點和人生很像啊。
每個構造函數都是 由兩部分組成的,一個是初始化部分,另外一個纔是函數體,成員的初始化是在函數體執行以前完成的,因此你的代碼裏也須要作這兩個部分的區分,不要把成員的初始化和函數體混爲一體,由於,可能會影響析構函數的執行(只是,沒有你想的那麼嚴重)。由於一個析構函數,其也是由函數體和其析構部分組成的,析構時,先執行函數體,再執行銷燬操做,成員按構造的初始化列表的逆序銷燬。
若是你須要覆蓋重寫析構函數體,那麼幾乎能夠確定你還須要拷貝構造函數和拷貝賦值運算符。
舉例子,我在上面的程序中重寫了析構函數,由於我須要顯示釋放sp_的內存,按上面的程序看,還可能出現什麼問題呢?畢竟我沒有拷貝賦值運算符函數。在測試函數中添加如下代碼:
ClassDesignTool tool ; { ClassDesignTool tool2("not me"); tool2 = tool; // tool.printSp(); tool2.printSp(); }
測試輸出:
use override default constructor use overload constructor sp_ is lcksfa use destructor lcksfa use destructor ///奔潰了!!!
使用大括號{}將tool2的賦值部分封起來,確保tool2先析構。
程序輸出後,到tool析構處就奔潰了!
緣由何在?
由於這裏的系統默認的賦值運算是直接將sp_ 的值進行賦值,而沒有去拷貝sp_ 指向的內存,tool2離開做用域時調用析構將sp_ delete掉了,等到tool離開做用域時,嘗試delete的仍是同一塊內存,因而就出現了double delete的問題!
這種狀況的解決方案之一就是咱們本身定義一個賦值操做運算符:
ClassDesignTool& ClassDesignTool::operator=(const ClassDesignTool& rhs) { std::cout << "use copy-assignment operaotr"<<"\n"; auto spNew = new string(*(rhs.sp_)); delete sp_; sp_ = spNew; return *this; }
本函數的寫法頗爲模式化:
總結起來就是 綜合了析構和構造函數的操做。銷燬了左值運算對象的資源,而從右值運算對象中拷貝資源。
小結:本文初略的說明了構造函數、析構函數和拷貝賦值運算符的重載,能夠做爲入門者的參考。