C++的拷貝建構函式、operator=運算子過載,深拷貝和淺拷貝、explicit關鍵字
1、在C++編碼過程中,類的建立十分頻繁。
簡單的功能,當然不用考慮太多,但是從進一步深刻理解C++的內涵,類的結構和用法,編寫更好的程式碼的角度去考慮,我們就需要用到標題所提到的這些內容。
最近,在看單例模式,覺得十分有趣,然而如果想要掌握單例模式,就必須掌握這些內容。下面是我的一些學習總結,參考了很多部落格內容。文末將註明出處。
2、先上程式碼
// testSingleMode.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #include <iostream> using namespace std; class Complex { private: double m_real; double m_imag; public: Complex(void){ m_real = 0.0; m_imag = 0.0; } Complex(double real, double imag){ m_real = real; m_imag = imag; } Complex(const Complex & c){ //這裡就是最經典的拷貝構造函數了 m_real = c.m_real; m_imag = c.m_imag; } Complex &operator = (const Complex &rhs){ //這裡就是最經典的operator=操作符過載了 if (this == &rhs){ return *this; } this->m_real = rhs.m_real; this->m_imag = rhs.m_imag; return *this; } explicit Complex::Complex(double r){ //explicit的用法,只適用於1個引數的情況 m_real = r; m_imag = 0.0; } }; int main() { Complex c1, c2; //呼叫 第15行 預設無引數的建構函式 Complex c3(1.0, 2.5); //呼叫 第20行 具有2個形參的建構函式 //Complex c3 = Complex(1.0, 2.5); //和上一行是一個意思,所以這個註釋了 c1 = c3; //呼叫 第30行 過載operator=運算子 c2 = c3; //呼叫 第30行 過載operator=運算子 //c2 = 5.2; //隱式轉換,需要去掉41行的explicit關鍵字,才可編譯通過 Complex c5(c2); //呼叫 第25行 拷貝建構函式 Complex c4 = c2; //呼叫 第25行 拷貝建構函式 getchar(); return 0; }
【注1】explicit 只適用於建構函式只含有1個引數的情況,加上這個關鍵字,意味著不支援建構函式隱式轉換,可以避免一些誤解。如果去掉這個關鍵字,那麼程式碼裡面的:c2 = 5.2 ; 就是可以執行的了。
【注2】為什麼函式中可以直接訪問物件c的私有成員?答:(網上)因為拷貝建構函式是放在本身這個類裡的,而類中的函式可以訪問這個類的物件的所有成員,當然包括私有成員了。
3、上面程式碼的註釋,已經是非常的清楚了
自定義拷貝建構函式是一種良好的程式設計風格,
它可以阻止編譯器形成預設的拷貝建構函式,防止出錯。
淺拷貝:如果自己不寫拷貝建構函式,系統會預設生成一個,而系統的拷貝建構函式是淺拷貝。
深拷貝:自己寫一個拷貝建構函式,系統就不會產生了預設的構造函數了(來自網上說法)。自己寫的這個拷貝建構函式,當然會有開闢空間的動作,所以是深拷貝。也就是說,如果生成類的例項的時候,呼叫了自己寫的拷貝建構函式,那麼在記憶體空間上,必然是會開闢新的空間,而不用擔心只是一個指標。很多時候,我們希望得到的類的例項是各自獨立的,各有各的空間。如果希望得到指標,那就不用操心這麼多。
在某些狀況下,類內成員變數需要動態開闢堆記憶體,如果實行淺拷貝,也就是把物件裡的值完全複製給另一個物件,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這就出現了問題:當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現執行錯誤。
4、總結
(1)深拷貝:如果一個類擁有資源,當這個類的物件發生複製過程的時候,資源重新分配,這個過程就是深拷貝。反之,沒有重新分配資源,就是淺拷貝。
(2)什麼時候用到拷貝建構函式
a.一個物件以值傳遞的方式傳入函式體;b.一個物件以值傳遞的方式從函式返回; c.一個物件需要通過另外一個物件進行初始化。 (3)深拷貝好還是淺拷貝好? 如果實行錢拷貝,也就是把物件裡的值完全複製給另一個物件,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這就出現了問題:當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現執行錯誤。
(4)程式碼例子2
#include "stdafx.h"
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
class Person
{
public:
// 建構函式
Person(char * pN)
{
cout << "一般建構函式被呼叫 !\n";
m_pName = new char[strlen(pN) + 1];
//在堆中開闢一個記憶體塊存放pN所指的字串
if (m_pName != NULL)
{
//如果m_pName不是空指標,則把形參指標pN所指的字串複製給它
strcpy_s(m_pName, strlen(pN) + 1, pN);
}
}
// 下面自己設計複製建構函式,實現“深拷貝”,即不讓指標指向同一地址,而是重新申請一塊記憶體給新的物件的指標資料成員
Person(Person & chs)
{
cout << "拷貝建構函式被呼叫 !\n";
// 用運算子new為新物件的指標資料成員分配空間
m_pName = new char[strlen(chs.m_pName) + 1];
if (m_pName)
{
// 複製內容
strcpy_s(m_pName, strlen(chs.m_pName) + 1, chs.m_pName);
}
// 則新建立的物件的m_pName與原物件chs的m_pName不再指向同一地址了
}
//// 系統建立的預設複製建構函式,只做位模式拷貝
//Person(Person & p)
//{
// //使兩個字串指標指向同一地址位置
// m_pName = p.m_pName;
//}
~Person()
{
delete m_pName;
}
void getName(){
cout << m_pName << endl;
}
private:
char * m_pName;
};
void main()
{
Person man("lujun");
man.getName();
Person woman(man);
woman.getName();
getchar();
}
程式執行環境: VS2013
輸出結果:
==============
參考文獻:
http://blog.csdn.net/lpp0900320123/article/details/39007047
http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html
http://www.cnblogs.com/raichen/p/4752025.html
http://blog.163.com/haixing_03031102/blog/static/120105509200972855328532/
http://blog.csdn.net/waitforfree/article/details/10137495
https://zhidao.baidu.com/question/1638161405180160020.html