1. 程式人生 > >深度探索C++物件模型——物件(5)——程式轉化語義

深度探索C++物件模型——物件(5)——程式轉化語義

我們寫的程式碼,編譯器會對程式碼進行拆分,拆分成編譯器更容易理解和實現的程式碼,接下來將從我們程式設計師寫程式碼角度和編譯器理解角度去分析一些情況

1.定義時初始化物件

(1)程式設計師角度

#include <iostream>
using namespace std;


class A
{
public:
	int A_i;
	A(const A& a1)   //拷貝建構函式
	{
		A_i = a1.A_i;
		cout << "拷貝建構函式被呼叫" << endl;
	}

	A()
	{
		A_i = 0;
		cout << "預設建構函式被呼叫" << endl;
	}
};
int main()
{
	A a0;
	a0.A_i = 5;
	A a1=a0;
	A a2(a0);
	A a3 = (a0);
}

執行結果:

(2)編譯器角度

//這3種等價
	A a1=a0;
	A a2(a0);
	A a3 = (a0);

它會將這幾句呼叫拷貝建構函式的程式碼理解為如下:

A a;        //步驟一:定義一個物件,為物件分配記憶體。從編譯器視角來看,這句是不呼叫X類的建構函式
a.A::A(a0); //步驟二:直接呼叫物件的拷貝建構函式

2.引數的初始化

(1)程式設計師角度

#include <iostream>
using namespace std;


class A
{
public:
	int A_i;
	A(const A& a1)   //拷貝建構函式
	{
		A_i = a1.A_i;
		cout << "拷貝建構函式被呼叫" << endl;
	}

	A()
	{
		A_i = 0;
		cout << "預設建構函式被呼叫" << endl;
	}
	~A()
	{
		cout << "解構函式被呼叫" << endl;
	}
};

void test(A a)
{
	return;
}

int main()
{
	A a;
	test(a);
}

執行結果:

解釋:

1)A a;語句呼叫了預設建構函式;

2)在向函式test傳遞引數時,將a進行了複製出了一個臨時物件,呼叫了拷貝建構函式

3)在test執行完之後,臨時物件遇到“}”,它的作用域結束,呼叫析了構函式

4)最後,main函式執行完畢,物件a遇到“}”,呼叫了解構函式

(2)編譯器角度

在向函式test傳遞引數時,類似於上述的拷貝賦值語句,對原物件進行復制,理解為:

1.先定義一個物件,分配記憶體
2.再呼叫它的拷貝建構函式
3.將複製的臨時物件在函式內部使用

3.返回值初始化

<一>函式返回值作為右值

(1)程式設計師角度

#include <iostream>
using namespace std;


class A
{
public:
	int A_i;
	A(const A& a1)   //拷貝建構函式
	{
		A_i = a1.A_i;
		cout << "拷貝建構函式被呼叫" << endl;
	}

	A()
	{
		A_i = 0;
		cout << "預設建構函式被呼叫" << endl;
	}
	~A()
	{
		cout << "解構函式被呼叫" << endl;
	}
};

A test()
{
	A a;
	return a;
}

int main()
{
	A a1=test();
	
}

執行結果:

解釋:

1)test函式中區域性物件定義時進行呼叫預設建構函式

2)在return語句執行時,呼叫一個拷貝建構函式拷貝區域性物件a給臨時物件

3)在test函式執行完時,呼叫解構函式析構區域性物件a

4)然後臨時物件就來到了main函式的作用域,將它賦給main函式中的區域性物件a1,此時並不呼叫拷貝建構函式

5)最後main結束,呼叫解構函式析構區域性物件a1

(2)編譯器角度

//為了理解A a1=test();編譯器將test函式理解成這樣
void test(A &extra)
{
    A a0;
    //...
    extra.A::A(a0);
}


A a1;     //定義一個物件,分配記憶體
test(a1); //傳入物件a1,以引用方式修改了a1,這就是為什麼A a1=test();語句中有賦值,而不呼叫拷貝建構函式的原因

<二>函式返回值作為左值

(1)程式設計師角度

#include <iostream>
using namespace std;


class A
{
public:
	int A_i;
	A(const A& a1)   //拷貝建構函式
	{
		A_i = a1.A_i;
		cout << "拷貝建構函式被呼叫" << endl;
	}

	A()
	{
		A_i = 0;
		cout << "預設建構函式被呼叫" << endl;
	}
	~A()
	{
		cout << "解構函式被呼叫" << endl;
	}

	void A_test()
	{
		cout << "A_test()被呼叫" << endl;
	}
};

A test()
{
	A a;
	//...
	return a;
}

int main()
{
	test().A_test();
	
}

執行結果:

(2)編譯器角度

//編譯器對於有返回值的test()函式理解與<一>中的test()一樣
//將函式理解為穿入引用的函式
void test(A &extra)
{
    A a0;
    //...
    extra.A::A(a0);
}


A a1;     //定義一個物件,分配記憶體
//test(a1); 傳入物件a1,以引用方式修改了a1,此句合併到了後面
//對後面呼叫成員函式A_test()理解如下
(test(a1), my).A_test(); //逗號表示式:先計算表示式1,再計算表示式2,整個逗號表示式的結果是表示式2的值;

<三>函式指標

(1)程式設計師角度

#include <iostream>
using namespace std;


class A
{
public:
	int A_i;
	A(const A& a1)   //拷貝建構函式
	{
		A_i = a1.A_i;
		cout << "拷貝建構函式被呼叫" << endl;
	}

	A()
	{
		A_i = 0;
		cout << "預設建構函式被呼叫" << endl;
	}
	~A()
	{
		cout << "解構函式被呼叫" << endl;
	}

	void A_test()
	{
		cout << "A_test()被呼叫" << endl;
	}
};

A test()
{
	A a;
	//...
	return a;
}

int main()
{
	A(*pfun)(); //定義個函式指標
	pfun = test;
	pfun().A_test();
	
}

執行結果:

(2)編譯器角度

//將有返回值函式理解為穿入引用的函式
void test(A &extra)
{
    A a0;
    //...
    extra.A::A(a0);
}


A a1;     //定義一個物件,分配記憶體
void (*pfun)(A &);  //定義一個指向返回值為空,傳入引用的函式指標
pfun = test;
(pfun(a1), a1).A_test();