C++class默认生成4个函数
生活随笔
收集整理的這篇文章主要介紹了
C++class默认生成4个函数
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
【C/C++和指針】類默認(rèn)生成的四個(gè)函數(shù)
序:對(duì)于一個(gè)空類,編譯器默認(rèn)生成四個(gè)成員函數(shù):默認(rèn)構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、賦值函數(shù)一,默認(rèn)構(gòu)造函數(shù)
? ? ? ?默認(rèn)構(gòu)造函數(shù)(default constructor)就是在沒(méi)有顯式提供初始化式時(shí)調(diào)用的構(gòu)造函數(shù)。它由不帶參數(shù)的構(gòu)造函數(shù),或者為所有的形參提供默認(rèn)實(shí)參的構(gòu)造函數(shù)定義。如果定義某個(gè)類的變量時(shí)沒(méi)有提供初始化式就會(huì)使用默認(rèn)構(gòu)造函數(shù)。
如果用戶定義的類中沒(méi)有顯式的定義任何構(gòu)造函數(shù),編譯器就會(huì)自動(dòng)為該類型生成默認(rèn)構(gòu)造函數(shù),稱為合成的構(gòu)造函數(shù)(synthesized default constructor)。
?
? ? ? ? C++語(yǔ)言為類提供的構(gòu)造函數(shù)可自動(dòng)完成對(duì)象的初始化任務(wù)
? ? ? ? 全局對(duì)象和靜態(tài)對(duì)象的構(gòu)造函數(shù)在main()函數(shù)執(zhí)行之前就被調(diào)用,局部靜態(tài)對(duì)象的構(gòu)造函數(shù)是當(dāng)程序第一次執(zhí)行到相應(yīng)語(yǔ)句時(shí)才被調(diào)用。然而給出一個(gè)外部對(duì)象的引用性聲明時(shí),并不調(diào)用相應(yīng)的構(gòu)造函數(shù),因?yàn)檫@個(gè)外部對(duì)象只是引用在其他地方聲明的對(duì)象,并沒(méi)有真正地創(chuàng)建一個(gè)對(duì)象。
? ? ? ?C++的構(gòu)造函數(shù)定義格式為:
? ? ? ? ? ? ? ?class <類名>
? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? public: <類名>(參數(shù)表) //...(還可以聲明其它成員函數(shù))
? ? ? ? ? ? ?};
? ? ? ? ? ? ? ?<類名>::<函數(shù)名>(參數(shù)表)
? ? ? ? ? ? ? ? { //函數(shù)體 }
? ? ? 如以下定義是合法的:
? ? ? ? ? ? ?class T
? ? ? ? ? ?{
? ? ? ? ? ? ? ?public: T(int a=0){i=a;}//構(gòu)造函數(shù)允許直接寫在類定義內(nèi),也允許有參數(shù)表。
? ? ? ? ? ? ? ?private:int i;
? ? ? ? ? ?};
? ? ? ? ?
二,析構(gòu)函數(shù)
? ? ? ?當(dāng)程序員沒(méi)有給類創(chuàng)建析構(gòu)函數(shù),那么系統(tǒng)會(huì)在類中自動(dòng)創(chuàng)建一個(gè)析構(gòu)函數(shù),形式為:~A(){},為類A創(chuàng)建的析構(gòu)函數(shù)。當(dāng)程序執(zhí)行完后,系統(tǒng)自動(dòng)調(diào)用自動(dòng)創(chuàng)建的析構(gòu)函數(shù),將對(duì)象釋放。
? ? ? ?默認(rèn)的析構(gòu)函數(shù)不能刪除new運(yùn)算符在自由存儲(chǔ)器中分配的對(duì)象或?qū)ο蟪蓡T。如果類成員占用的空間是在構(gòu)造函數(shù)中動(dòng)態(tài)分配的,我們就必須自定義析構(gòu)函數(shù),然后顯式使用delete運(yùn)算符來(lái)釋放構(gòu)造函數(shù)使用new運(yùn)算符分配的內(nèi)存,就像銷毀普通變量一樣
#include ? <iostream>?
using ? namespace ? std;?
class ? Pig?
{?
public:?
Pig()?
{?
cout < < "Pig ? constructed " < <endl;?
}?
~Pig()?
{?
cout < < "Pig ? destructed " < <endl;?
}?
};?
class ? Japanese:Pig?
{?
};?
int ? main()?
{?
Japanese ? dog;?
return ? 0;?
}?
輸出:?
Pig constructed?
Pig destructed
如果改成一下new 生成的對(duì)象則不調(diào)用默認(rèn)析構(gòu)函數(shù)
int main()?
{?
Japanese *dog=new Japanese;?
return 0;?
}?
輸出就只有:?
Pig constructed
?
?
三,拷貝構(gòu)造函數(shù)
? ? ? ? CExample(const CExample&); //參數(shù)是const ?對(duì)象的引用&
【注意】如果不主動(dòng)編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),編譯器將以“位拷貝”的方式自動(dòng)生成缺省的函數(shù)。倘若類中含有指針變量,那么這兩個(gè)缺省的函數(shù)就隱含了錯(cuò)誤。
? ? ? 以類String的兩個(gè)對(duì)象a,b為例,假設(shè)a.m_data的內(nèi)容為“hello”,b.m_data的內(nèi)容為“world”。
? ? ? 現(xiàn)將a賦給b,缺省賦值函數(shù)的“位拷貝”意味著執(zhí)行b.m_data = a.m_data。這將造成三個(gè)錯(cuò)誤:一是b.m_data原有的內(nèi)存沒(méi)被釋放,造成內(nèi)存泄露;二是b.m_data和a.m_data指向同一塊內(nèi)存,a或b任何一方變動(dòng)都會(huì)影響另一方;三是在對(duì)象被析構(gòu)時(shí),m_data被釋放了兩次。
?1)默認(rèn)拷貝構(gòu)造函數(shù) ? ? ?
? ? ? 對(duì)于普通類型的對(duì)象來(lái)說(shuō),它們之間的復(fù)制是很簡(jiǎn)單的,例如:
? ? ? ? ? ? ?int a=88;
? ? ? ? ? ? ?int b=a; //復(fù)制
? ? ? 而類對(duì)象與普通對(duì)象不同,類對(duì)象內(nèi)部結(jié)構(gòu)一般較為復(fù)雜,存在各種成員變量。
? 下面看一個(gè)類對(duì)象拷貝的簡(jiǎn)單例子。
#include <iostream>
using namespace std;
class CExample {
private:
? int a;
public:
? CExample(int b)
? { a=b;}
? void Show ()
? {
? ? ? cout<<a<<endl;
? ?}
};
int main()
{
? CExample A(100);
? CExample B=A;
? B.Show ();
? return 0;
}?
?
? ? ?運(yùn)行程序,屏幕輸出100。
? ? 系統(tǒng)為對(duì)象B分配了內(nèi)存并完成了與對(duì)象A的復(fù)制過(guò)程。就類對(duì)象而言,相同類型的類對(duì)象是通過(guò)拷貝構(gòu)造函數(shù)來(lái)完成整個(gè)復(fù)制過(guò)程的。下面舉例說(shuō)明拷貝構(gòu)造函數(shù)的工作過(guò)程。
2)顯式拷貝構(gòu)造函數(shù)
#include <iostream>
using namespace std;
class CExample {
private:
?int a;
public:
?CExample(int b)
?{ a=b;}
?
?CExample(const CExample& C)//拷貝構(gòu)造函數(shù)
?{
? ? ?a=C.a;
?}
?void Show ()
?{
? ? ?cout<<a<<endl;
?}
};
int main()
{
? ?CExample A(100);
? ?CExample B=A;
? ?B.Show ();
? ?return 0;
}?
?
? ? ? ?CExample(constCExample& C)就是我們自定義的拷貝構(gòu)造函數(shù)。可見(jiàn),拷貝構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù),函數(shù)的名稱必須和類名稱一致,它的唯一的一個(gè)參數(shù)是本類型的一個(gè)引用變量,該參數(shù)是const類型,不可變的。例如:類X的拷貝構(gòu)造函數(shù)的形式為X(X& x)。
? ? ? ? ? 當(dāng)用一個(gè)已初始化過(guò)了的對(duì)象去初始化另一個(gè)新構(gòu)造的對(duì)象的時(shí)候,拷貝構(gòu)造函數(shù)就會(huì)被自動(dòng)調(diào)用。也就是說(shuō),當(dāng)類的對(duì)象需要拷貝時(shí),拷貝構(gòu)造函數(shù)將會(huì)被調(diào)用。以下情況都會(huì)調(diào)用拷貝構(gòu)造函數(shù):
? ? ? ? ? 一個(gè)對(duì)象以值傳遞的方式傳入函數(shù)體
? ? ? ? ? 一個(gè)對(duì)象以值傳遞的方式從函數(shù)返回
? ? ? ? ? 一個(gè)對(duì)象需要通過(guò)另外一個(gè)對(duì)象進(jìn)行初始化。
? ? ? ? 如果在類中沒(méi)有顯式地聲明一個(gè)拷貝構(gòu)造函數(shù),那么,編譯器將會(huì)自動(dòng)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù),該構(gòu)造函數(shù)完成對(duì)象之間的位拷貝。位拷貝又稱淺拷貝,后面將進(jìn)行說(shuō)明。?
? ? ?淺拷貝和深拷貝
在某些狀況下,類內(nèi)成員變量需要?jiǎng)討B(tài)開(kāi)辟堆內(nèi)存,如果實(shí)行位拷貝,也就是把對(duì)象里的值完全復(fù)制給另一個(gè)對(duì)象,如A=B。這時(shí),如果B中有一個(gè)成員變量指針已經(jīng)申請(qǐng)了內(nèi)存,那A中的那個(gè)成員變量也指向同一塊內(nèi)存。這就出現(xiàn)了問(wèn)題:當(dāng)B把內(nèi)存釋放了(如:析構(gòu)),這時(shí)A內(nèi)的指針就是野指針了,出現(xiàn)運(yùn)行錯(cuò)誤。
深拷貝和淺拷貝可以簡(jiǎn)單理解為:如果一個(gè)類擁有資源,當(dāng)這個(gè)類的對(duì)象發(fā)生復(fù)制過(guò)程的時(shí)候,資源重新分配,這個(gè)過(guò)程就是深拷貝,反之,沒(méi)有重新分配資源,就是淺拷貝。
3)深拷貝 ?(主要應(yīng)對(duì)類中有指針變量的情況)?
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& C)
{
a=C.a;
str=new char[a]; //深拷貝
if(str!=0)
strcpy(str,C.str);
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}?
???????深拷貝:類擁有資源(堆,或者是其它系統(tǒng)資源),當(dāng)這個(gè)類的對(duì)象發(fā)生復(fù)制過(guò)程的時(shí)候
? ? ? ?淺拷貝:對(duì)象存在資源,但復(fù)制過(guò)程并未復(fù)制資源的情況視為淺拷貝。
? ? ? ? ? ? ? ? ?淺拷貝缺點(diǎn):淺拷貝資源后在釋放資源的時(shí)候會(huì)產(chǎn)生資源歸屬不清的情況導(dǎo)致程序運(yùn)行出錯(cuò)。
? ? ? Test(Test &c_t)是自定義的拷貝構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)的名稱必須與類名稱一致,函數(shù)的形式參數(shù)是本類型的一個(gè)引用變量,且必須是引用。
? ? ? 當(dāng)用一個(gè)已經(jīng)初始化過(guò)了的自定義類類型對(duì)象去初始化另一個(gè)新構(gòu)造的對(duì)象的時(shí)候,拷貝構(gòu)造函數(shù)就會(huì)被自動(dòng)調(diào)用,如果你沒(méi)有自定義拷貝構(gòu)造函數(shù)的時(shí)候,系統(tǒng)將會(huì)提供給一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)來(lái)完成這個(gè)過(guò)程,上面代碼的復(fù)制核心語(yǔ)句就是通過(guò)Test(Test &c_t)拷貝構(gòu)造函數(shù)內(nèi)的p1=c_t.p1;語(yǔ)句完成的。
?
四,賦值函數(shù)
? ? ? ? 每個(gè)類只有一個(gè)賦值函數(shù)
? ? ? ? 由于并非所有的對(duì)象都會(huì)使用拷貝構(gòu)造函數(shù)和賦值函數(shù),程序員可能對(duì)這兩個(gè)函數(shù)有些輕視。
? 1,如果不主動(dòng)編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),編譯器將以“位拷貝”的方式自動(dòng)生成缺省的函數(shù)。倘若類中含有指針變量,那么這兩個(gè)缺省的函數(shù)就隱含了錯(cuò)誤。
? ? ? ? ? ? ? ?以類String的兩個(gè)對(duì)象a,b為例,假設(shè)a.m_data的內(nèi)容為“hello”,b.m_data的內(nèi)容為“world”。
? ? ? ? ? ? ? ?現(xiàn)將a賦給b,缺省賦值函數(shù)的“位拷貝”意味著執(zhí)行b.m_data = a.m_data。
? ? ? ? ? ? ? ?這將造成三個(gè)錯(cuò)誤: ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? 一是b.m_data原有的內(nèi)存沒(méi)被釋放,造成內(nèi)存泄露;
? ? ? ? ? ? ? ? ? ? ? ? 二是b.m_data和a.m_data指向同一塊內(nèi)存,a或b任何一方變動(dòng)都會(huì)影響另一方;
? ? ? ? ? ? ? ? ? ? ? ? 三是在對(duì)象被析構(gòu)時(shí),m_data被釋放了兩次。
? ? ? ? 2,拷貝構(gòu)造函數(shù)和賦值函數(shù)非常容易混淆,常導(dǎo)致錯(cuò)寫、錯(cuò)用。拷貝構(gòu)造函數(shù)是在對(duì)象被創(chuàng)建時(shí)調(diào)用的,而賦值函數(shù)只能被已經(jīng)存在了的對(duì)象調(diào)用。以下程序中,第三個(gè)語(yǔ)句和第四個(gè)語(yǔ)句很相似,你分得清楚哪個(gè)調(diào)用了拷貝構(gòu)造函數(shù),哪個(gè)調(diào)用了賦值函數(shù)嗎?
? ? ? ? ?String a(“hello”);
? ? ? ? ? ? ?String b(“world”);?
? ? ? ? ? ? ?String c = a; // 調(diào)用了拷貝構(gòu)造函數(shù),最好寫成 c(a);?
? ? ? c = b; // 調(diào)用了賦值函數(shù)
? ? ? ? ? ? ? ?本例中第三個(gè)語(yǔ)句的風(fēng)格較差,宜改寫成String c(a) 以區(qū)別于第四個(gè)語(yǔ)句。
? ? ? ? 類String的拷貝構(gòu)造函數(shù)與賦值函數(shù)
? ? ? ? ? ?// 拷貝構(gòu)造函數(shù) ? ? ? ??
? ? ? ? ?String::String(const String &other)
? ? ? ? ?{
? ? ? ? ? ? ? // 允許操作other的私有成員m_data
? ? ? ? ? ? ? ?int length = strlen(other.m_data);
? ? ? ? ? ? ? ?m_data = new char[length+1];
? ? ? ? ? ?strcpy(m_data, other.m_data);
? ? }
? ? ? // 賦值函數(shù)
? ? ? String & String::operator =(const String &other)
? ? ? ?{ // (1) 檢查自賦值
? ? ? ? ? ? ? ? if(this == &other)
? ? ? ? ? ? ? ? ? ? ? ? ? return *this;
? ? ? ? ? ? ? ?// (2) 釋放原有的內(nèi)存資源?
? ? ? ?delete [] m_data;
? ? ? ? ? ? ? // (3)分配新的內(nèi)存資源,并復(fù)制內(nèi)容
? ? ? ? ? int length = strlen(other.m_data);
? ? ? ? ? m_data = new char[length+1];
? ? ? ? ? ? ? strcpy(m_data, other.m_data);
? ? ? ? ? ? ?// (4)返回本對(duì)象的引用
? ? ? ? ? ? ? return *this;
? }
? ? ? 類String拷貝構(gòu)造函數(shù)與普通構(gòu)造函數(shù)的區(qū)別是:在函數(shù)入口處無(wú)需與NULL進(jìn)行比較,這是因?yàn)椤耙谩辈豢赡苁荖ULL,而“指針”可以為NULL。?
? 類String的賦值函數(shù)比構(gòu)造函數(shù)復(fù)雜得多,分四步實(shí)現(xiàn):
? ? ? ? ? ? ? ? (1)第一步,檢查自賦值。你可能會(huì)認(rèn)為多此一舉,難道有人會(huì)愚蠢到寫出 a = a 這樣的自賦值語(yǔ)句!的確不會(huì)。但是間接的自賦值仍有可能出現(xiàn),例如 // 內(nèi)容自賦值 b = a; … c = b; … a = c; // 地址自賦值 b = &a; … a = *b; 也許有人會(huì)說(shuō):“即使出現(xiàn)自賦值,我也可以不理睬,大不了化點(diǎn)時(shí)間讓對(duì)象復(fù)制自己而已,反正不會(huì)出錯(cuò)!” 他真的說(shuō)錯(cuò)了??纯吹诙降膁elete,自殺后還能復(fù)制自己?jiǎn)?#xff1f;所以,如果發(fā)現(xiàn)自賦值,應(yīng)該馬上終止函數(shù)。注意不要將檢查自賦值的if語(yǔ)句 if(this == &other) 錯(cuò)寫成為 if( *this == other)
? ? ? ? ? ? ? ?(2)第二步,用delete釋放原有的內(nèi)存資源。如果現(xiàn)在不釋放,以后就沒(méi)機(jī)會(huì)了,將造成內(nèi)存泄露。
? ? ? ? ? ? ? ?(3)第三步,分配新的內(nèi)存資源,并復(fù)制字符串。注意函數(shù)strlen返回的是有效字符串長(zhǎng)度,不包含結(jié)束符‘\0’。函數(shù)strcpy則連‘\0’一起復(fù)制。
? ? ? ? ? ? ? ?(4)第四步,返回本對(duì)象的引用,目的是為了實(shí)現(xiàn)象 a = b = c 這樣的鏈?zhǔn)奖磉_(dá)。注意不要將 return *this 錯(cuò)寫成 return this 。那么能否寫成return other 呢?效果不是一樣嗎? ? ?不可以!因?yàn)槲覀儾恢绤?shù)other的生命期。有可能other是個(gè)臨時(shí)對(duì)象,在賦值結(jié)束后它馬上消失,那么return other返回的將是垃圾。 偷懶的辦法處理拷貝構(gòu)造函數(shù)與賦值函數(shù) 如果我們實(shí)在不想編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),又不允許別人使用編譯器生成的缺省函數(shù),怎么辦?
? ? ? ? ? ? 偷懶的辦法是:只需將拷貝構(gòu)造函數(shù)和賦值函數(shù)聲明為私有函數(shù),不用編寫代碼。
? ? ? ?例如:
? ? ? ? ?class A { …
? ? ? ? ? ? ? private: A(const A &a); // 私有的拷貝構(gòu)造函數(shù)?
? ? ? ? ? ? A & operate =(const A &a); // 私有的賦值函數(shù)
? ? ? ? ? ? ? ?};
? ? ? ? ? ? 如果有人試圖編寫如下程序:
? ? ? ? ? ? ? ? ? A b(a); // 調(diào)用了私有的拷貝構(gòu)造函數(shù)
? ? ? ? ? ? ? b = a; // 調(diào)用了私有的賦值函數(shù)
編譯器將指出錯(cuò)誤,因?yàn)橥饨绮豢梢圆僮鰽的私有函數(shù)。 注意:以上例子在vc中可能編譯不過(guò),因關(guān)鍵字不是operate ,而是operator
?3.在編寫派生類的賦值函數(shù)時(shí),注意不要忘記對(duì)基類的數(shù)據(jù)成員重新賦值.
?
總結(jié)
以上是生活随笔為你收集整理的C++class默认生成4个函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Dead Lock
- 下一篇: C++ Public, Protecte