深度探索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
編譯器合成了子類的建構函式,並向其中添加了呼叫基類建構函式,虛基類表的程式碼