1. 程式人生 > >C++中建構函式,拷貝建構函式,解構函式

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;
}
輸出:

注意:不能返回區域性物件的引用,所以如果引數不為引用是不能返回一個引用的物件的。



到這裡應該對建構函式,拷貝建構函式和賦值操作符在何時會呼叫應該有一個清晰的認識了。