這篇文章將對C++中復制構造函數和重載賦值操作符進行總結,包括以下內容:
1.復制構造函數和重載賦值操作符的定義;
2.復制構造函數和重載賦值操作符的調用時機;
3.復制構造函數和重載賦值操作符的實現要點;
4.復制構造函數的一些細節。
復制構造函數和重載賦值操作符的定義
我們都知道,在C++中建立一個類,這個類中肯定會包括構造函數、析構函數、復制構造函數和重載賦值操作;即使在你沒有明確定義的情況下,編譯器也會給你生成這樣的四個函數。例如以下類:
class CTest
{
public:
CTest();
~CTest();
CTest(const CTest &);
CTest& operator=(const CTest &);
};
對於構造函數和析構函數不是今天總結的重點,今天的重點是復制構造函數和重載賦值操作。類的復制構造函數原型如下:
class_name(const class_name &src);
一般來說,如果我們沒有編寫復制構造函數,那麼編譯器會自動地替每一個類創建一個復制構造函數(也叫隱式復制構造函數);相反的,如果我們編寫了一個復制構造函數(顯式的復制構造函數),那麼編譯器就不會創建它。
類的重載賦值操作符的原型如下:
class_name& operator=(const class_name &);
重載賦值操作符是一個特別的賦值運算符,通常是用來把已存在的對象指定給其它相同類型的對象。它是一個特別的成員函數,如果我們沒有定義這個成員函數,那麼編譯器會自動地產生這個成員函數。編譯器產生的代碼是以單一成員進行對象復制的動作。
總結了復制構造函數和重載賦值操作符的定義,只是讓我們了解了它們,而沒有真正的深入它們。接下來,再仔細的總結一下它們的調用時機。關於它們的調用時機,我一直都沒有真正的明白過,所以這裡一定要好好的總結明白了。
復制構造函數和重載賦值操作符的調用時機
對復制構造函數和重載賦值操作符的調用總是發生在不經意間,它們不是經過我們顯式的去調用就被執行了。對於這種隱式調用的地方一定要多注意了,這也一般是有陷阱的地方。現在我就用實際的例子來進行驗證;例子如下:
#include <iostream>
using namespace std;
class CTest
{
public:
CTest(){}
~CTest(){}
CTest(const CTest &test)
{
cout<<"copy constructor."<<endl;
}
void operator=(const CTest &test)
{
cout<<"operator="<<endl;
}
void Test(CTest test)
{}
CTest Test2()
{
CTest a;
return a;
}
void Test3(CTest &test)
{}
CTest &Test4()
{
CTest *pA = new CTest;
return *pA;
}
};
int main()
{
CTest obj;
CTest obj1(obj); // 調用復制構造函數
obj1 = obj; // 調用重載賦值操作符
/* 傳參的過程中,要調用一次復制構造函數
* obj1入棧時會調用復制構造函數創建一個臨時對象,與函數內的局部變量具有相同的作用域
*/
obj.Test(obj1);
/* 函數返回值時,調用復制構造函數;將返回值賦值給obj2時,調用重載賦值操作符
* 函數返回值時,也會構造一個臨時對象;調用復制構造函數將返回值復制到臨時對象上
*/
CTest obj2;
obj2 = obj.Test2();
obj2.Test3(obj); // 參數是引用,沒有調用復制構造函數
CTest obj3;
obj2.Test4(); // 返回值是引用,沒有調用復制構造函數
return 0;
}
在代碼中都加入了注釋,這裡就不再做詳細的說明了。再次總結一下,如果對象在聲明的同時將另一個已存在的對象賦給它,就會調用復制構造函數;如果對象已經存在了,然後再將另一個已存在的對象賦給它,調用的就是重載賦值運算符了。這條規則很適用,希望大家能記住。
復制構造函數和重載賦值操作符的實現要點
在一般的情況下,編譯器給我們生成的默認的復制構造函數和重載賦值操作符就已經夠用了;但是在一些特別的時候,需要我們手動去實現自己的復制構造函數。
我們都知道,默認的復制構造函數和賦值運算符進行的都是”shallow copy”,只是簡單地復制字段,因此如果對象中含有動態分配的內存,就需要我們自己重寫復制構造函數或者重載賦值運算符來實現”deep copy”,確保數據的完整性和安全性。這也就是大家常常說的深拷貝與淺拷貝的問題。下面我就提供一個比較簡單的例子來說明一下:
#include <iostream>
using namespace std;
const int MAXSIZE = 260;
class CTest
{
public:
CTest(wchar_t *pInitValue)
{
// Here, I malloc the memory
pValue = new wchar_t[MAXSIZE];
memset(pValue, 0, sizeof(wchar_t) * MAXSIZE);
wcscpy_s(pValue, MAXSIZE, pInitValue);
}
~CTest()
{
if (pValue)
{
delete[] pValue; //finalseabiscuit指出,謝謝。2014.7.24
pValue = NULL;
}
}
CTest(const CTest &test)
{
// Malloc the new memory for the pValue
pValue = new wchar_t[MAXSIZE];
memset(pValue, 0, sizeof(wchar_t) * MAXSIZE);
wcscpy_s(pValue, MAXSIZE, test.pValue);
}
CTest& operator=(const CTest &test)
{
// This is very important, please remember
if (this == &test)
{
return *this;
}
// Please delete the memory, this maybe cause the memory leak
if (pValue)
{
delete[] pValue; // 方恆剛指出的問題。非常感謝 2014.3.15
}
// Malloc the new memory for the pValue
pValue = new wchar_t[MAXSIZE];
memset(pValue, 0, sizeof(wchar_t) * MAXSIZE);
wcscpy_s(pValue, MAXSIZE, test.pValue);
return *this;
}
void Print()
{
wcout<<pValue<<endl;
}
private:
wchar_t *pValue; // The pointer points the memory
};
int main()
{
CTest obj(L"obj");
obj.Print();
CTest obj2(L"obj2");
obj2.Print();
obj2 = obj;
obj2.Print();
obj2 = obj2;
obj2.Print();
return 0;
}
特別是在實現重載賦值構造函數時需要多多的注意,在代碼中我也添加了注釋,大家可以認真的閱讀一下代碼,然後就懂了,如果不懂的就可以留言問我;當然了,如果我哪裡理解錯了,也希望大家能給我提出,我們共同進步。
復制構造函數的一些細節
1.以下哪些是復制構造函數 X::X(const X&);
X::X(X);
X::X(X&, int a=1);
X::X(X&, int a=1, int b=2);
這些細節問題在這裡也說一說,我也是從別人的博客裡看到的,這裡自己也總結一下。對於一個類X, 如果一個構造函數的第一個參數是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且沒有其他參數或其他參數都有默認值,那麼這個函數是拷貝構造函數。
X::X(const X&); //是拷貝構造函數
X::X(X&, int=1); //是拷貝構造函數
X::X(X&, int a=1, int b=2); //當然也是拷貝構造函數
2.類中可以存在超過一個拷貝構造函數 class X
{
public:
X(const X&); // const 的拷貝構造
X(X&); // 非const的拷貝構造
};
注意,如果一個類中只存在一個參數為 X& 的拷貝構造函數,那麼就不能使用const X或volatile X的對象實行拷貝初始化。如果一個類中沒有定義拷貝構造函數,那麼編譯器會自動產生一個默認的拷貝構造函數。這個默認的參數可能為 X::X(const X&)或 X::X(X&),由編譯器根據上下文決定選擇哪一個。在我的Visual Studio 2012中,當定義了多個復制構造函數以後,編譯器就會有warning,但是程序還能正確運行。
總結
這篇文章對復制構造函數和重載賦值操作符進行了一些總結,重點是在復制構造函數與重載賦值操作符的調用時機上;對於大家喜歡總結的深拷貝與淺拷貝問題,我沒有用過多的文字進行說明,我認為上面的代碼就足以說明問題了。最後自己糾結已久的問題也就這樣總結了,自己也徹底的明白了。