拷貝建構函式程式設計實驗
拷貝建構函式程式設計實驗
本文是本人大一期間在校學習C++課程時所撰寫的實驗報告的摘錄,由於剛上大學,剛接觸計算機程式設計方面的相關知識,故可能會有很多不足甚至錯誤的地方,還請各位看官指出及糾正。
本文所涉及到的“教材”指:
電子工業出版社《C++ Primary中文版(第5版)》
如需轉載或引用請標明出處。
基本知識
拷貝控制函式
當定義一個類時,我們顯示地或隱式地指定在此型別的物件拷貝、移動、賦值和銷燬時做什麼,而負責控制這些操作的函式我們稱為拷貝控制成員,把這些操作稱為拷貝控制操作。
如果一個類沒有定義所有的這些拷貝控制成員,編譯器會自動為它定義缺失的操作。因此,很多類會忽略這些拷貝控制操作。但是,對一些類來說,依賴這些操作的預設定義會導致嚴重的後果。因此,學會判斷什麼時候需要定義它們,如何定義他們是非常重要的。
拷貝建構函式
拷貝建構函式是拷貝控制成員的其中之一。如果一個建構函式的第一個引數是自身類型別的引用,且任何額外引數都有預設值,則此建構函式是拷貝建構函式。例如:
class Foo
{
public:
Foo(); //預設建構函式
Foo(const Foo&); //拷貝建構函式
//...
};
由於拷貝建構函式被用來初始化非引用類型別引數,因此拷貝建構函式的第一個引數必須是一個引用型別。
如果我們沒有為一個類定義拷貝建構函式,編譯器會為我們定義一個。與合成預設建構函式不同,即使我們定義了其他建構函式,編譯器其也會為我們合成一個拷貝建構函式。一般情況下,合成的拷貝建構函式會將其引數的成員逐個拷貝到正在建立的物件中,且每個成員的型別決定了它如何拷貝。
一般而言,在執行拷貝初始化時,會呼叫拷貝建構函式或移動建構函式中的一種。而在下列情況中將會執行拷貝初始化:
- 使用=定義變數時
- 將一個物件作為實參傳遞給一個非引用型別的形參時
- 從一個返回型別為非引用型別的函式返回一個物件時
- 用花括號列表初始化一個數組中的元素或一個聚合類中的成員時
- 某些類型別還會對它們所分配的物件使用拷貝初始化(例如標準庫容器中的insert和push成員函式)
解構函式
解構函式是拷貝控制成員的其中之一。解構函式執行與建構函式相反的操作,解構函式釋放物件使用的資源,並銷燬物件的非static資料成員。
解構函式是類的一個成員函式,名字由波浪號接類名構成。它沒有返回值,也不接受引數。例如:
class Foo
{
public:
~Foo(); //解構函式
//...
};
由於解構函式不接受引數,因此它不能被過載,對於一個給定的類,也只有唯一一個解構函式。
解構函式有一個函式體和一個析構部分,在一個解構函式中,首先執行函式體,再銷燬成員,成員按初始化順序的逆序銷燬。類似的,當一個類未定義自己解構函式時,編譯器會為它定義一個合成解構函式。
一般而言,在下列情況中,解構函式會被呼叫:
- 變數離開其作用域時
- 當一個物件被銷燬時,其成員被銷燬
- 容器被銷燬時,其元素被銷燬
- 對於動態分配的物件,當對指向它的指標應用delete運算子時
- 對於臨時物件,當建立它的完整表示式結束時
需要注意的是,成員是在解構函式體之後隱含的析構階段中被銷燬的,在整個物件的銷燬過程中,解構函式體是作為成員銷燬步驟之外的另一部分而進行的。
關於本實驗
在本次實驗中,共有5個程式,可以分為兩組,前三個一組與後兩個一組。通過分析程式碼,對每組之間的各個程式進行對比,分析執行結果,展示了拷貝建構函式和解構函式的定義和使用方法,以及展示了它們在什麼情況下會被呼叫。具體說明詳見程式碼註釋部分。
示例程式碼
lab12_1.cpp
#include<iostream>
using namespace std;
class CExample //測試型別
{
private:
int a; //唯一成員
public:
CExample(int b) //建構函式:接受一個整型實參,用於初始化成員a
{
a = b;
printf("constructor is called\n");
}
CExample(const CExample & c) //拷貝建構函式:將實參c的成員a拷貝過來
{
a = c.a;
printf("copy constructor is called\n");
}
~CExample() //解構函式:資料銷燬將在下列函式體結束後進行
{
cout << "destructor is called\n";
}
void Show() //指示成員a的值
{
cout << a << endl;
}
};
int main(void)
{
CExample A(100); //執行普通的建構函式,構造A
CExample B = A; //執行拷貝建構函式,構造B
B.Show(); //驗證執行了拷貝建構函式
system("pause");
return 0; //由於此時A和B仍在自己的作用域內,故沒有執行解構函式
}
執行結果:
lab12_2.cpp
#include<iostream>
using namespace std;
class CExample //測試型別
{
private:
int a; //唯一成員
public:
CExample(int b) //建構函式:接受一個整型實參,用於初始化成員a
{
a = b;
printf("constructor is called\n");
}
CExample(const CExample & c) //拷貝建構函式:將實參c的成員a拷貝過來
{
a = c.a;
printf("copy constructor is called\n");
}
~CExample() //解構函式:資料銷燬將在下列函式體結束後進行
{
cout << "destructor is called\n";
}
void Show() //指示成員a的值
{
cout << a << endl;
}
};
void g_fun(CExample c) //非引用型別的形參,呼叫時會執行一次拷貝建構函式,構造c的副本
{
cout << "g_func" << endl;
}
int main(void)
{
CExample A(100); //執行普通的建構函式,構造A
CExample B = A; //執行拷貝建構函式,構造B
B.Show(); //驗證執行了拷貝建構函式
g_fun(A); //下面是這段程式碼的執行過程:
//1. 執行拷貝建構函式,構造A的副本
//2. 執行函式體,即輸出文字
//3. 函式結束,A的副本離開了該函式的作用域,執行一次解構函式
system("pause");
return 0; //由於此時A和B仍在自己的作用域內,故沒有執行解構函式
}
執行結果:
lab12_3.cpp
#include<iostream>
using namespace std;
class CExample //測試型別
{
private:
int a; //唯一成員
public:
CExample(int b) //建構函式:接受一個整型實參,用於初始化成員a
{
a = b;
printf("constructor is called\n");
}
CExample(const CExample & c) //拷貝建構函式:將實參c的成員a拷貝過來
{
a = c.a;
printf("copy constructor is called\n");
}
~CExample() //解構函式:資料銷燬將在下列函式體結束後進行
{
cout << "destructor is called\n";
}
void Show() //指示成員a的值
{
cout << a << endl;
}
};
CExample g_fun() //測試函式
{
CExample temp(0); //呼叫普通的建構函式,構造temp
return temp; //返回的是非引用型別的temp,即temp的拷貝,會執行一次拷貝建構函式
}
int main(void)
{
g_fun(); //下面是這段程式碼的執行過程:
//1. 呼叫普通的建構函式,構造temp
//2. 返回非引用型別的temp,即返回temp的拷貝,執行一次拷貝建構函式
//3. 函式結束,temp離開其作用域,執行一次解構函式
//4. 由於沒有物件承接返回的temp的拷貝,可以認為該拷貝的作用域結束,執行一次解構函式
system("pause");
return 0;
}
執行結果:
lab12_4.cpp
#include<iostream>
using namespace std;
class Rect //測試型別
{
public:
Rect() //預設建構函式:將count的值加一
{
count++;
}
~Rect() //解構函式:先把count的值減一,再銷燬相關資料
{
count--;
}
static int getCount() //指示靜態成員count的值
{
return count;
}
private:
int width;
int height; //用於說明
static int count; //靜態成員count,即使多次執行拷貝建構函式,也只有一個count
//即多個測試型別共享一個count
};
int Rect::count = 0; //先把測試類中的count置為0
int main(void)
{
Rect rect1; //執行預設建構函式,構造rect1,把count的值加1
cout << "The count of Rect:" << Rect::getCount() << endl; //驗證預設建構函式執行成功
Rect rect2(rect1); //由於沒有自己定義拷貝建構函式,故會使用合成的拷貝建構函式去構造rect2
//合成的拷貝建構函式中沒有定義count加一的操作,故count的值不變
cout << "The count of Rect:" << Rect::getCount() << endl; //驗證執行的是合成的拷貝建構函式
system("pause");
return 0; //由於此時rect1和rect2仍在自己的作用域內,故沒有執行解構函式
}
執行結果:
lab12_5.cpp
#include<iostream>
using namespace std;
class Rect //測試型別
{
public:
Rect() //預設建構函式:將count的值加一
{
count++;
}
Rect(const Rect& r) //拷貝建構函式
{
width = r.width; //將實參r的成員width拷貝過來
height = r.height; //將實參r的成員height拷貝過來
count++; //與合成的拷貝建構函式不同的地方:將count的值加一
}
~Rect() //解構函式:先把count的值減一,再銷燬相關資料
{
count--;
}
static int getCount() //指示靜態成員count的值
{
return count;
}
private:
int width;
int height;
static int count; //靜態成員count,即使多次執行拷貝建構函式,也只有一個count
}; //即多個測試型別共享一個count
int Rect::count = 0; //先把測試類中的count置為0
int main(void)
{
Rect rect1; //執行預設建構函式,構造rect1,把count的值加1
cout << "The count of Rect:" << Rect::getCount() << endl; //驗證預設建構函式執行成功
Rect rect2(rect1); //執行自己定義的拷貝建構函式:構造rect2,把count的值加1
cout << "The count of Rect:" << Rect::getCount() << endl; //驗證執行的是自己定義的拷貝建構函式
system("pause");
return 0; //由於此時rect1和rect2仍在自己的作用域內,故沒有執行解構函式
}
執行結果: