普通型別物件之間的復制很簡單,而類物件與普通物件不同,類物件內部結構一般較為復雜,存在各種成員變數,這篇文章將幫你理清C++類物件的拷貝方式
拷貝建構式,拷貝賦值運算子
首先我們簡單了解下默認的拷貝建構式和拷貝賦值運算子
拷貝建構式
第一個引數是自身型別別參考,其他引數都有默認值的建構式就是拷貝建構式
class Sales_data {
public:
Sales_data(); //默認建構式
Sales_data(const Foo&); //默認拷貝建構式
//...
};
拷貝建構式用來初始化非參考型別別引數,所以拷貝建構式自己的引數必須是參考型別(如果不是參考:為了呼叫拷貝建構式,必須拷貝它的實參,為了拷貝實參,又需要呼叫拷貝建構式,無限回圈)
合成拷貝建構式(默認)
和默認建構式一樣,編譯器會幫你定義一個默認拷貝建構式(如果你沒有手動定義的話),不同的是,如果你定義了其他建構式,編譯器還是會給你合成一個拷貝建構式
舉個例子:Sales_data的合成拷貝建構式等價于
class Sales_data {
public:
Sales_data();
Sales_data(const Sales_data&);
private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0;
};
Sales_data::Sales_data(const Sales_data& origin) :
bookNo(origin.bookNo), //使用string的拷貝建構式
units_sold(origin.units_sold), //拷貝
revenue(origin.revenue) { //拷貝
//空函式體
}
直接初始化,拷貝初始化
通過以下幾行代碼不難理解
string dots(10,'.'); //直接初始化
string s(dots); //直接初始化
string s2 = dots; //拷貝初始化
string null_book = "9-999-9999-9" //拷貝初始化
string nines = strings(100,'9'); //拷貝初始化
使用直接初始化時,我們是在要求編譯器使用普通的函式匹配,來選擇與我們提供的引數最匹配的建構式
使用拷貝初始化時,我們要求編譯器將右側運算子物件拷貝到正在創建的物件中(需要的話還進行型別轉換
拷貝賦值運算子
賦值運算子本質也是函式,它由operator關鍵字后面接要定義的運算子的符號組成,賦值運算子就是一個名為operator=的函式,和其他函式一樣,它也有一個回傳型別和一個引數串列
引數表示運算子的運算物件,某些運算子(包括賦值運算子)必須定義為成員函式,如果一個運算子是成員函式,則其左側運算物件就能系結到隱式的this引數上,對于一個二元運算子(例如賦值運算子),右側運算物件就會作為顯示引數傳遞
拷貝賦值運算子接受一個與其所在類相同型別的引數
class Sales_data {
public:
Sales_data& operator=(const Sales_data&);
};
為了與內置型別的賦值保持一直,賦值運算子通常回傳一個指向其左側運算物件的參考
合成拷貝賦值運算子(默認)
和拷貝建構式一樣,如果一個類未定義自己的拷貝賦值運算子,編譯器會生成一個合成拷貝賦值運算子,類似拷貝建構式,對于某些類,合成拷貝賦值運算子用來禁止該型別物件的賦值
拷貝賦值運算子會將右側運算物件的每個非static成員賦予左側運算物件的對應成員,對于陣列型別的成員,逐個賦值陣列元素合成拷貝賦值運算子回傳一個指向其左側運算物件的參考
Sales_data& Sales_data::operator=(const Sales_data& rhs) {
bookNo = rhs.bookNo;
units_sold = rhs.units_sold;
revenue = rhs.revenue;
return *this;
}
淺拷貝
回頭看看我們最初的Sales_data類
class Sales_data {
public:
Sales_data();
Sales_data(const Sales_data&);
private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0;
};
以下這樣的初始化看似沒有什么問題
int main()
{
Sales_data data1;
Sales_data data2 = data1;
}
下面給出一個和Sales_data不太一樣的Array類
class Array
{
public:
Array() //建構式
{
m_iCount = 5;
m_pArr = new int[m_iCount];
}
Array(const Array& rhs) //拷貝建構式(相當于默認拷貝建構式)
{
m_iCount = rhs.m_iCount;
m_pArr = rhs.m_pArr;
}
private:
int m_iCount;
int* m_pArr;
};
(這里的拷貝建構式其實相當于編譯器合成的默認拷貝建構式)
我們用同樣的方式初始化的時候:
int main()
{
Array array1;
Array array2 = array1;
}
默認拷貝建構式可以完成物件的資料成員簡單的復制,但是由于我們這里有一個指標型別的成員變數m_pArr,直接使用默認拷貝就會出現一個問題,兩個物件的m_pArr指標指向了同一塊區域

當物件arr2通過物件arr1初始化,物件arr1已經申請了記憶體,那么物件arr2就會指向物件arr1所申請的記憶體,如果物件arr1釋放掉記憶體,那么物件A中的指標就是野指標了,這就是淺拷貝
深拷貝
為了避免這樣的記憶體泄露,擁有指標成員的物件進行拷貝的時候,需要自己定義拷貝建構式,使拷貝后的物件指標成員擁有自己的記憶體地址
class Array {
public:
Array() {
m_iCount = 5;
m_pArr = new int[m_iCount];
}
Array(const Array& rhs) {
m_iCount = rhs.m_iCount;
m_pArr = new int[m_iCount];
for (int i = 0; i < m_iCount; i++)
{
m_pArr[i] = rhs.m_pArr[i];
}
}
private:
int m_iCount;
int* m_pArr;
};

對比一下
- 淺拷貝也叫位拷貝,拷貝的是地址
- 深拷貝也叫值拷貝,拷貝的是內容
深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的物件發生復制程序的時候,資源重新分配,這個程序就是深拷貝,反之,沒有重新分配資源,就是淺拷貝
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/103673.html
標籤:C++
上一篇:c++ const問題小記
下一篇:運算子多載(三)
