C++中建構函式,拷貝建構函式,解構函式
C++中預設建構函式就是沒有形參的建構函式。準確的說法,按照《C++ Primer》中定義:只要定義一個物件時沒有提供初始化式,就是用預設建構函式。為所有 的形參提供預設實參的建構函式也定義了預設建構函式。
合成的預設建構函式,即編譯器自動生成的預設建構函式。《C++ Primer》中的說明:一個類哪怕只定義了一個建構函式,編譯器也不會再生成預設建構函式。這條規則的根據是,如果一個類再某種情況下需要控制物件初始化,則該類很可能在所有情況下都需要控制。只有當一個類沒有定義建構函式時,編譯器才會自動生成一個預設建構函式。
需要注意的是自定義的建構函式包括拷貝建構函式。就是說在下面這種情況下,編譯器也不會自動生成預設建構函式,程式碼會出錯。把拷貝建構函式刪除掉後就沒有問題了。
編譯器為類自動生成的函式包括預設建構函式,拷貝建構函式,解構函式,賦值函式。但是如果自己定義了拷貝建構函式,那麼預設建構函式編譯器就不會自動生成了。但是自己定義了預設建構函式時其他函式都會自動生成。
注意拷貝建構函式的引數型別為const Base &型別。&是必須的很容易理解,需要傳遞引用才行。至於const引數在g++中不加在編譯時會報錯,在vs中能通過編譯。不過建議還是加上,因為傳遞的是引用,根據原物件構造新的物件時當然不希望將原物件改變。
首先使用下面的程式碼測試,預設建構函式,建構函式,拷貝建構函式,賦值操作符的呼叫:#include <iostream> using namespace std; class Base{ public: //建構函式 //Base(){ // cout<<"default constructor invoke!"<<endl; //} //Base(int d){ // this->data=d; // cout<<"constructor invoke!"<<endl; //} //拷貝建構函式 Base(const Base &b){ cout<<"copy constructor invoke!"<<endl; this->data=b.data; } //賦值操作符 Base & operator=(const Base &b){ cout<<"operator constructor invoke!"<<endl; this->data=b.data; } //7構函式 ~Base(){ cout<<"deconstructor invoke!"<<endl; } private: int data; }; int main(){ Base b; return 0; }
執行結果如下:#include <iostream> using namespace std; class Base{ public: //建構函式 Base(){ cout<<"default constructor invoke!"<<endl; } Base(int d){ this->data=d; cout<<"constructor invoke!"<<endl; } //拷貝建構函式 Base(const Base &b){ cout<<"copy constructor invoke!"<<endl; this->data=b.data; } //賦值操作符 void operator=(const Base &b){ cout<<"operator constructor invoke!"<<endl; this->data=b.data; } //解構函式 ~Base(){ cout<<"deconstructor invoke!"<<endl; } private: int data; }; int main(){ Base b(1);//建構函式呼叫 Base b2=b;//拷貝建構函式呼叫 Base b3;//預設建構函式呼叫 b3=b;//賦值操作符呼叫 return 0; }
在g++和vs中執行的結果都是一樣的。
然後新增一個函式呼叫來測試:
#include <iostream>
using namespace std;
class Base{
public:
//建構函式
Base(){
cout<<"default constructor invoke!"<<endl;
}
Base(int d){
this->data=d;
cout<<"constructor invoke!"<<endl;
}
//拷貝建構函式
Base(const Base &b){
cout<<"copy constructor invoke!"<<endl;
this->data=b.data;
}
//賦值操作符
void operator=(const Base &b){
cout<<"operator constructor invoke!"<<endl;
this->data=b.data;
}
//7構函式
~Base(){
cout<<"deconstructor invoke!"<<endl;
}
Base play(Base b){
return b;
}
private:
int data;
};
int main(){
Base b(1);//建構函式呼叫
Base b2=b;//拷貝建構函式呼叫
Base b3;//預設建構函式呼叫
b3=b;//賦值操作符呼叫
b.play(2);//建構函式呼叫,拷貝建構函式呼叫(由於函式返回引起的)
return 0;
}
結果如下:函式b.play(2)執行時,首先會使用建構函式(即接受一個int型變數的建構函式)進行隱式的型別轉換,即呼叫一次建構函式。注意,如果play的形參是B&型別的話,就不可以這樣做。否則會出錯。
然後使用直接呼叫時:
#include <iostream>
using namespace std;
class Base{
public:
//建構函式
Base(){
cout<<"default constructor invoke!"<<endl;
}
Base(int d){
this->data=d;
cout<<"constructor invoke!"<<endl;
}
//拷貝建構函式
Base(const Base &b){
cout<<"copy constructor invoke!"<<endl;
this->data=b.data;
}
//賦值操作符
void operator=(const Base &b){
cout<<"operator constructor invoke!"<<endl;
this->data=b.data;
}
//7構函式
~Base(){
cout<<"deconstructor invoke!"<<endl;
}
Base play(Base b){
return b;
}
private:
int data;
};
int main(){
Base b(1);//建構函式呼叫
Base b2=b;//拷貝建構函式呼叫
Base b3;//預設建構函式呼叫
b3=b;//賦值操作符呼叫
b.play(2);//建構函式呼叫,拷貝建構函式呼叫(由於函式返回引起的)
b.play(b);//<span style="font-size: 11.8181819915771px; font-family: Arial, Helvetica, sans-serif;">第一次拷貝建構函式呼叫是構造形參,第二次拷貝建構函式呼叫是函式返回值</span>
return 0;
}
輸出結構如下:
最開始也不明白為什麼是兩次拷貝建構函式呼叫。那麼打印出每個物件的地址吧。
vs中的結果:
g++中的結果,可以發現g++中的結果引數進棧的順序還是有規律的,但是vs好像就沒有規律了。
這樣還是看不明白,但是和下面程式碼的這種情況進行比較就比較容易理解了:
#include <iostream>
using namespace std;
class Base{
public:
//建構函式
Base(){
cout<<"default constructor invoke!"<<(int *)this<<endl;
}
Base(int d){
this->data=d;
cout<<"constructor invoke!"<<(int *)this<<endl;
}
//拷貝建構函式
Base(const Base &b){
cout<<"copy constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//賦值操作符
void operator=(const Base &b){
cout<<"operator constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//7構函式
~Base(){
cout<<"deconstructor invoke!"<<(int *)this<<endl;
}
Base play(Base b){
return b;
}
private:
int data;
};
int main(){
Base b(1);//建構函式呼叫
Base b2=b;//拷貝建構函式呼叫
Base b3;//預設建構函式呼叫
b3=b;//賦值操作符呼叫
b.play(2);//建構函式呼叫,拷貝建構函式呼叫(由於函式返回引起的)
b.play(b);//第一次拷貝建構函式呼叫是構造形參,第二次拷貝建構函式呼叫是函式返回值
cout<<"---------------"<<endl;
Base b4=b.play(2);
cout<<"---------------"<<endl;
Base b5=b.play(b);
cout<<"---------------"<<endl;
return 0;
}
g++中執行結果為:
可以發現Base b4=b.play(2);和b.play(2);這行程式碼相比,解構函式的位置發生了變化,之前是連續兩次析構,現在換成了先析構一次,然後函式結束後又析構一次。並且是第二次拷貝建構函式建立的物件在函式結束後析構的(根據物件的地址可以得知)。那麼情況可能是這樣的,即第二次拷貝建構函式是由於函式返回物件時引起的。因為在函式play呼叫結束後形參b會被析構掉,所以需要將b複製到一個新的記憶體區域。呼叫b.play(2)時的記憶體佈局可能是這樣的(根據上面的地址繪製):
為了驗證這個測試,可以讓play函式不返回B的物件,程式碼如下:
#include <iostream>
using namespace std;
class Base{
public:
//建構函式
Base(){
cout<<"default constructor invoke!"<<(int *)this<<endl;
}
Base(int d){
this->data=d;
cout<<"constructor invoke!"<<(int *)this<<endl;
}
//拷貝建構函式
Base(const Base &b){
cout<<"copy constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//賦值操作符
void operator=(const Base &b){
cout<<"operator constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//7構函式
~Base(){
cout<<"deconstructor invoke!"<<(int *)this<<endl;
}
void play(Base b){
}
private:
int data;
};
int main(){
Base b(1);//建構函式呼叫
Base b2=b;//拷貝建構函式呼叫
Base b3;//預設建構函式呼叫
b3=b;//賦值操作符呼叫
b.play(2);//建構函式呼叫
b.play(b);//呼叫一次拷貝建構函式
return 0;
}
可以發現少了一次拷貝建構函式的呼叫:
最後再來看看函式引數為引用的時候的情況,這種情況下不會再建立形參,函式是直接對實參進行操作:
#include <iostream>
using namespace std;
class Base{
public:
//建構函式
Base(){
cout<<"default constructor invoke!"<<(int *)this<<endl;
}
Base(int d){
this->data=d;
cout<<"constructor invoke!"<<(int *)this<<endl;
}
//拷貝建構函式
Base(const Base &b){
cout<<"copy constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//賦值操作符
void operator=(const Base &b){
cout<<"operator constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//7構函式
~Base(){
cout<<"deconstructor invoke!"<<(int *)this<<endl;
}
void play(Base b){
}
void refer(Base b){
cout<<"refer address:"<<(int *)&b<<endl;
}
private:
int data;
};
int main(){
Base b(1);//建構函式呼叫
Base b2=b;//拷貝建構函式呼叫
Base b3;//預設建構函式呼叫
b3=b;//賦值操作符呼叫
b.play(b);//只會呼叫拷貝建構函式
b.play(2);//建構函式呼叫
cout<<"-----------------"<<endl;
b.refer(b);
cout<<"-----------------"<<endl;
return 0;
}
執行結果為:
refer函式引數為引用返回物件時:
#include <iostream>
using namespace std;
class Base{
public:
//建構函式
Base(){
cout<<"default constructor invoke!"<<(int *)this<<endl;
}
Base(int d){
this->data=d;
cout<<"constructor invoke!"<<(int *)this<<endl;
}
//拷貝建構函式
Base(const Base &b){
cout<<"copy constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//賦值操作符
void operator=(const Base &b){
cout<<"operator constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//7構函式
~Base(){
cout<<"deconstructor invoke!"<<(int *)this<<endl;
}
void play(Base b){
}
Base refer(Base & b){
cout<<"refer address:"<<(int *)&b<<endl;
return b;
}
private:
int data;
};
int main(){
Base b(1);//建構函式呼叫
Base b2=b;//拷貝建構函式呼叫
Base b3;//預設建構函式呼叫
b3=b;//賦值操作符呼叫
b.play(b);//只會呼叫拷貝建構函式
b.play(2);//建構函式呼叫
cout<<"-----------------"<<endl;
b.refer(b);
cout<<"-----------------"<<endl;
return 0;
}
依然呼叫了拷貝建構函式來複制返回值:
refer函式引數為引用並且返回引用時,才不會呼叫拷貝建構函式:
#include <iostream>
using namespace std;
class Base{
public:
//建構函式
Base(){
cout<<"default constructor invoke!"<<(int *)this<<endl;
}
Base(int d){
this->data=d;
cout<<"constructor invoke!"<<(int *)this<<endl;
}
//拷貝建構函式
Base(const Base &b){
cout<<"copy constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//賦值操作符
void operator=(const Base &b){
cout<<"operator constructor invoke!"<<(int *)this<<endl;
this->data=b.data;
}
//7構函式
~Base(){
cout<<"deconstructor invoke!"<<(int *)this<<endl;
}
void play(Base b){
}
Base& refer(Base & b){
cout<<"refer address:"<<(int *)&b<<endl;
return b;
}
private:
int data;
};
int main(){
Base b(1);//建構函式呼叫
Base b2=b;//拷貝建構函式呼叫
Base b3;//預設建構函式呼叫
b3=b;//賦值操作符呼叫
b.play(b);//只會呼叫拷貝建構函式
b.play(2);//建構函式呼叫
cout<<"-----------------"<<endl;
b.refer(b);
cout<<"-----------------"<<endl;
return 0;
}
輸出:
注意:不能返回區域性物件的引用,所以如果引數不為引用是不能返回一個引用的物件的。
到這裡應該對建構函式,拷貝建構函式和賦值操作符在何時會呼叫應該有一個清晰的認識了。