當定義一個類時,我們顯式地或隱式地指定了此類型的對象在拷貝、賦值和銷燬時做什麼。一個類通過定義五種種特殊的成員函數來控制這些操作:拷貝構造函數、拷貝賦值運算符、移動構造函數、移動賦值運算符和析構函數。
在一個類中,如果類 沒有指針成員,一切方便,因爲默認合成的析構函數會自動處理所有的內存。但是如果一個類帶了指針成員,那麼需要我們自己來寫 析構函數來管理內存。
對於含有指針成員的類的對象調用默認拷貝構造函數、拷貝賦值運算符時,指針成員進行是淺拷貝,兩個對象的指針成員所指內存相同,這就導致指針被分配一次內存,但是程序結束時該內存卻被析構了兩次,第一次析構後,指針變爲野指針 ,第二次析構導致錯誤。
淺拷貝與深拷貝:
(1)淺拷貝只是複製了對象的引用地址,兩個對象指向同一個內存地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝;
(2)深拷貝是將對象及值複製過來,兩個對象修改其中任意的值另一個值不會改變,這就是深拷貝;
野指針與空指針:
(1)野指針:不是NULL指針,是指向"垃圾"內存(不可用內存)的指針,野指針是非常危險的。指針變量沒有被初始化,它缺省值是隨機的,它會亂指一氣;指針p被free或者delete之後,只是把指針所指的內存釋放掉了,沒有改變指針的值,此時,p淪落爲野指針。
(2)空指針:沒有存儲任何內存地址的指針就稱爲空指針(NULL指針)
總結:
(1)如果一個類帶了指針成員,它也需要自定義析構函數。
(2)如果一個類需要自定義析構函數,幾乎可以肯定它也需要自定義拷貝賦值運算符和拷貝構造函數。
(3)如果一個類需要一個拷貝構造函數,幾乎可以肯定它也需要一個拷貝賦值運算符。反之亦然,如果一個類需要一個拷貝賦值運算符,幾乎可以肯定它也需要一個拷貝構造函數。
demo:
#include "iostream"
using namespace std;
class Human
{
public:
Human()
{
name = new char[4];
string a = "123";
strcpy_s(name, 4, a.c_str());
cout << "Human Construct be called Successful!" << endl;
}
#if 1
Human(Human &hm)
{
if (NULL != hm.name)
{
name = new char[4];
strcpy_s(name, 4, hm.name);
}
cout << "Human copy Construct be called Successful!" << endl;
}
Human& operator=(const Human& hm)
{
if (this != &hm)
{
// 避免內存泄露
if (name != NULL)
{
delete name;
name = NULL;
}
if (NULL != hm.name)
{
name = new char[4];
strcpy_s(name, 4, hm.name);
}
}
cout << "Human operator be called Successful!" << endl;
return *this;
}
#endif
~Human()
{
delete[] name; //不需要判斷是否爲空
cout << "Human Deconstrute be called Successful!" << endl;
}
int init()
{
return 0;
}
int age;
char *name;
};
int main()
{
{
Human hm1, hm3; //調用構造函數
Human hm2 = hm1; //調用拷貝構造函數
hm3 = hm1; //調用拷貝賦值運算符
}
system("pause");
return 0;
}
參考:
https://blog.csdn.net/caoshangpa/article/details/79226270
https://blog.csdn.net/fanx021/article/details/80207692
https://blog.csdn.net/liitdar/article/details/80656156
https://www.cnblogs.com/zpcdbky/p/5027481.html
https://www.cnblogs.com/codingmengmeng/p/9110608.html