1. 程式人生 > >深度探索C++物件模型(3)——物件(3)——建構函式語義

深度探索C++物件模型(3)——物件(3)——建構函式語義

預設建構函式(預設建構函式)分析

     即沒有引數的建構函式

     傳統認識認為:如果我們自己沒定義任何建構函式,那麼編譯器就會為我們隱式自動定義 一個預設的建構函式,
我們稱這種建構函式為:“合成的預設建構函式”

    結論:“合成的預設建構函式”,只有在 必要的時候,編譯器才會為我們合成出來,而不是必然或者必須為我們合成出來。
    

必要的時候是什麼時候?

示例程式碼

#include <iostream>
using namespace std;

class MATX
{
public:
	MATX() //預設建構函式
	{
		cout << "goodHAHAHAHA" << endl;
	}
};

class MBTX
{
public:
	int m_i;
	int m_j;
	void funct()
	{
		cout << "IAmVeryGood" << endl;
	}
};

int main()
{
	MBTX myb;
	return 1;
}

編譯通過會生成.obj檔案:

每個.cpp原始檔會編譯生成一個.obj(linux下 gcc -c生成.o檔案),最終把很多的.obj(.o)檔案連結到一起生成一個可執行。

Windows下檢視.obj檔案

(1)右鍵單擊正在執行程式的檔案,開啟所在資料夾

(2)進入它的debug資料夾,就可以找到該檔案剛才編譯生成的.obj檔案

(3)找到開始選單中visual studio中的開發人員命令提示符並開啟

(4)輸入命令如下(我的test1檔案在F:盤,所以首先進入F:盤),會在.obj的檔案目錄下生成test1.txt檔案,存放的是.o檔案的分析結果

(5)用visual studio2017檢視test1.txt結果如圖

 

我們使用快速查詢驗證編譯器是否為MBTX生成了預設建構函式MBTX::MBTX()

編譯器並沒有合成,因為此時只有普通型別的變數,編譯器並不會合成預設建構函式

編譯器會在什麼時候幫我們合成預設建構函式呢?

(1)該類MBTX沒有任何建構函式,但包含一個類型別的成員ma,而該物件ma所屬於的類MATX 有一個預設的建構函式

#include <iostream>
using namespace std;

class M0TX
{
public:
	M0TX() //預設建構函式
	{
		cout << "合成了預設建構函式,呼叫了M0TX類" << endl;
	}
};
class MATX
{
public:
	MATX() //預設建構函式
	{
		cout << "合成了預設建構函式,呼叫了MATX類" << endl;
	}
};

class MBTX
{
public:
	int m_i;
	int m_j;

	M0TX m0;  //類型別成員變數
	MATX ma; //類型別成員變數


	void funct()
	{
		cout << "IAmVeryGood" << endl;
	}
};

int main()
{
	MBTX myb;
	return 1;
}

執行結果:

再使用dumpbin命令更新test1.txt,查詢MBTX::MBTX()

此時編譯器幫我們合成了預設建構函式,合成的目的是為了呼叫MATX裡的預設建構函式。

換句話說:編譯器合成了預設的MBTX建構函式,並且在其中 安插程式碼,呼叫M0TX,MATX中的預設建構函式;

並且注意:此時呼叫類型別M0TX,MATX的預設建構函式的順序與在該類MBTX中宣告的順序相同

(2)父類帶預設建構函式,子類沒有任何建構函式,那因為父類這個預設的建構函式要被呼叫,所以編譯器會為這個子類合成出一個預設建構函式。
     合成的目的是為了呼叫這個父類的建構函式。換句話說,編譯器合成了預設的建構函式,並在其中安插程式碼,呼叫其父類的預設建構函式。

#include <iostream>
using namespace std;

class MBTXPARENT
{
public:
	MBTXPARENT()
	{
		cout << "父類建構函式被呼叫了" << endl;
	}
};
class MBTX :public MBTXPARENT
{
public:
	int m_i;
	int m_j;

};


int main()
{
	MBTX myb;
	return 1;
}	

執行結果:父類函式被呼叫了

檢視.obj檔案:

(3)如果一個類含有虛擬函式,但沒有任何建構函式時

          a.編譯器會給我們生成一個基於該類的虛擬函式表vftable

          b.編譯器給我們合成了一個建構函式,並且在其中安插程式碼: 把類的虛擬函式表地址賦給類物件的虛擬函式表指標 (賦值語句/程式碼);

#include <iostream>
using namespace std;

class MBTX 
{
public:
	int m_i;
	int m_j;


	virtual void MBTXfunc()
	{
		cout << "虛擬函式" << endl;
	}

};


int main()
{
	MBTX myb;
	return 1;
}	

檢視.obj檔案:

我們再來看一下我們有自己的建構函式的情況:

#include <iostream>
using namespace std;

class MBTXPARENT
{
public:
	MBTXPARENT()
	{
		cout << "MBTXPARENT()" << endl;
	}
};
class MBTX:public MBTXPARENT
{
public:
	int m_i;
	int m_j;


	virtual void MBTXfunc()
	{
		cout << "虛擬函式" << endl;
	}

	MBTX()
	{
		m_i = 10;
	}
};


int main()
{
	MBTX myb;
	return 1;
}

結論:當我們有自己的預設建構函式時,編譯器會根據需要擴充我們自己寫的建構函式程式碼,比如呼叫父類建構函式,給物件的虛擬函式表指標賦值。

(4)如果一個類帶有虛基類,編譯器也會為它合成一個預設建構函式

虛基類:通過兩個直接基類繼承同一個簡介基類。所以一般是三層結構 ,有爺爺類Grand,有兩個父類A,A2,有子類C,大致如下

示例程式碼:

class Grand //爺爺類
{
public:
};

//兩個父類
class A : virtual public Grand
{
public:
};

class A2 : virtual public Grand
{
public:
};



class C :public A, public A2 //這裡不需要virtual
{
public:
	C()
	{
		int aa;
		aa = 1;
	}
};


int main()
{
	C cc;
	return 1;
}

我們再來看一下我們有自己的建構函式的情況:

編譯器沒有合成爺爺類Grand的建構函式

編譯器合成了父類A和A2的建構函式

A

A2

編譯器合成了子類的建構函式,並向其中添加了呼叫基類建構函式,虛基類表的程式碼