电竞比分网-中国电竞赛事及体育赛事平台

分享

數(shù)據(jù)結(jié)構(gòu)中的copy構(gòu)造函數(shù)和賦值重載函數(shù)-堅(jiān)強(qiáng)的心-搜狐博客

 曾經(jīng)艱難走過(guò) 2010-04-05

    前段時(shí)間瘋狂的在調(diào)試著數(shù)據(jù)結(jié)構(gòu)中的各個(gè)程序。但是有兩個(gè)東西我一直是當(dāng)注釋放在那的,這就是鏈接結(jié)構(gòu)中的copy構(gòu)造函數(shù)和賦值重載函數(shù);這也是我學(xué)習(xí)中的一個(gè)毛病——對(duì)于不懂的,只想著能拖就拖。不過(guò),由于作業(yè)的要求,只能硬著頭皮把書(shū)啃了兩個(gè)小時(shí)。下面就是我對(duì)這兩個(gè)東西的理解。

    在數(shù)據(jù)結(jié)構(gòu)中,對(duì)于程序的實(shí)現(xiàn)一般有兩種方式:順序?qū)崿F(xiàn)和鏈接實(shí)現(xiàn),即一個(gè)是由數(shù)組作為基本的數(shù)據(jù)成員,而另一個(gè)則是由節(jié)點(diǎn)Node作為基本的數(shù)據(jù)成員。其中,鏈接實(shí)現(xiàn)時(shí),如果用戶(hù)的不經(jīng)意,就會(huì)造成很多不必要的garbage,而正是出于這種考慮,在C++中有了三個(gè)保護(hù)函數(shù):析構(gòu)函數(shù)、copy構(gòu)造函數(shù)和賦值重載函數(shù)。

    析構(gòu)函數(shù)類(lèi)似于構(gòu)造函數(shù)的“反函數(shù)”,目的是:“在所聲明的對(duì)象在銷(xiāo)毀時(shí),釋放其所占用的內(nèi)存空間?!比缦旅媸莝tack的析構(gòu)函數(shù):

Stack::~Stack()
{
  while(!empty())
    pop();
  delete head;
}

    而對(duì)于copy構(gòu)造函數(shù)和賦值重載函數(shù),都是為了避免用戶(hù)在使用時(shí),輕易對(duì)兩個(gè)對(duì)象之間賦值所設(shè)立的保護(hù)函數(shù)。他們的區(qū)別可以簡(jiǎn)單通過(guò)下面這個(gè)例子看出來(lái):

void main()
{
  Stack stack1; // 聲明了一個(gè)Stack對(duì)象:Stack1;
  for(int i=0; i<5; i++)
    stack.push(i);
  Stack stack2;
  stack2=stack1;  // 這里是調(diào)用賦值重載函數(shù);
  Stack stack3=stack1; // 這里調(diào)用copy構(gòu)造函數(shù);
}

而對(duì)于更加詳細(xì)的區(qū)別,這里我轉(zhuǎn)載了一片我感覺(jué)很不錯(cuò)的文章:   

"   在學(xué)習(xí)這一章內(nèi)容前我們已經(jīng)學(xué)習(xí)過(guò)了類(lèi)的構(gòu)造函數(shù)和析構(gòu)函數(shù)的相關(guān)知識(shí),對(duì)于普通類(lèi)型的對(duì)象來(lái)說(shuō),他們之間的復(fù)制是很簡(jiǎn)單的,例如:

int a = 10;
int b =a;

  自己定義的類(lèi)的對(duì)象同樣是對(duì)象,誰(shuí)也不能阻止我們用以下的方式進(jìn)行復(fù)制,例如:

#include <iostream
using namespace std; 
 
class Test 

public
    Test(int temp) 
    { 
        p1=temp; 
    } 
protected
    int p1; 
 
}; 
 
void main() 

    Test a(99); 
    Test b=a; 
}

  普通對(duì)象和類(lèi)對(duì)象同為對(duì)象,他們之間的特性有相似之處也有不同之處,類(lèi)對(duì)象內(nèi)部存在成員變量,而普通對(duì)象是沒(méi)有的,當(dāng)同樣的復(fù)制方法發(fā)生在不同的對(duì)象上的時(shí)候,那么系統(tǒng)對(duì)他們進(jìn)行的操作也是不一樣的,就類(lèi)對(duì)象而言,相同類(lèi)型的類(lèi)對(duì)象是通過(guò)拷貝構(gòu)造函數(shù)來(lái)完成整個(gè)復(fù)制過(guò)程的,在上面的代碼中,我們并沒(méi)有看到拷貝構(gòu)造函數(shù),同樣完成了復(fù)制工作,這又是為什么呢?因?yàn)楫?dāng)一個(gè)類(lèi)沒(méi)有自定義的拷貝構(gòu)造函數(shù)的時(shí)候系統(tǒng)會(huì)自動(dòng)提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù),來(lái)完成復(fù)制工作。

  下面,我們?yōu)榱苏f(shuō)明情況,就普通情況而言(以上面的代碼為例),我們來(lái)自己定義一個(gè)與系統(tǒng)默認(rèn)拷貝構(gòu)造函數(shù)一樣的拷貝構(gòu)造函數(shù),看看它的內(nèi)部是如何工作的!

  代碼如下:

#include <iostream
using namespace std; 
 
class Test 

public
    Test(int temp) 
    { 
        p1=temp; 
    } 
    Test(Test &c_t)//這里就是自定義的拷貝構(gòu)造函數(shù) 
    { 
        cout<<"進(jìn)入copy構(gòu)造函數(shù)"<<endl; 
        p1=c_t.p1;//這句如果去掉就不能完成復(fù)制工作了,此句復(fù)制過(guò)程的核心語(yǔ)句 
    } 
public
    int p1; 
}; 
 
void main() 

    Test a(99); 
    Test b=a; 
    cout<<b.p1; 
    cin.get(); 
}

  上面代碼中的Test(Test &c_t)就是我們自定義的拷貝構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)的名稱(chēng)必須與類(lèi)名稱(chēng)一致,函數(shù)的形式參數(shù)是本類(lèi)型的一個(gè)引用變量,必須是引用。

  當(dāng)用一個(gè)已經(jīng)初始化過(guò)了的自定義類(lèi)類(lèi)型對(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ǔ)句完成的。如果取掉這句代碼,那么b對(duì)象的p1屬性將得到一個(gè)未知的隨機(jī)值;

下面我們來(lái)討論一下關(guān)于淺拷貝和深拷貝的問(wèn)題。

  就上面的代碼情況而言,很多人會(huì)問(wèn)到,既然系統(tǒng)會(huì)自動(dòng)提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)來(lái)處理復(fù)制,那么我們沒(méi)有意義要去自定義拷貝構(gòu)造函數(shù)呀,對(duì),就普通情況而言這的確是沒(méi)有必要的,但在某寫(xiě)狀況下,類(lèi)體內(nèi)的成員是需要開(kāi)辟動(dòng)態(tài)開(kāi)辟堆內(nèi)存的,如果我們不自定義拷貝構(gòu)造函數(shù)而讓系統(tǒng)自己處理,那么就會(huì)導(dǎo)致堆內(nèi)存的所屬權(quán)產(chǎn)生混亂,試想一下,已經(jīng)開(kāi)辟的一端堆地址原來(lái)是屬于對(duì)象a的,由于復(fù)制過(guò)程發(fā)生,b對(duì)象取得是a已經(jīng)開(kāi)辟的堆地址,一旦程序產(chǎn)生析構(gòu),釋放堆的時(shí)候,計(jì)算機(jī)是不可能清楚這段地址是真正屬于誰(shuí)的,當(dāng)連續(xù)發(fā)生兩次析構(gòu)的時(shí)候就出現(xiàn)了運(yùn)行錯(cuò)誤。

  為了更詳細(xì)的說(shuō)明問(wèn)題,請(qǐng)看如下的代碼。

#include <iostream
using namespace std; 
 
class Internet 

public
    Internet(char *name,char *address) 
    { 
        cout<<"載入構(gòu)造函數(shù)"<<endl; 
        strcpy(Internet::name,name); 
        strcpy(Internet::address,address); 
        cname=new char[strlen(name)+1]; 
        if(cname!=NULL) 
        { 
            strcpy(Internet::cname,name); 
        } 
    } 
    Internet(Internet &temp) 
    { 
        cout<<"載入COPY構(gòu)造函數(shù)"<<endl; 
        strcpy(Internet::name,temp.name); 
        strcpy(Internet::address,temp.address); 
        cname=new char[strlen(name)+1];//這里注意,深拷貝的體現(xiàn)! 
        if(cname!=NULL) 
        { 
            strcpy(Internet::cname,name); 
        } 
    } 
    ~Internet() 
    { 
        cout<<"載入析構(gòu)函數(shù)!"; 
        delete[] cname; 
        cin.get(); 
    } 
    void show(); 
protected
    char name[20]; 
    char address[30]; 
    char *cname; 
}; 
void Internet::show() 

    cout<<name<<":"<<address<<cname<<endl; 

void test(Internet ts) 

    cout<<"載入test函數(shù)"<<endl; 

void main() 

    Internet a("中國(guó)軟件開(kāi)發(fā)實(shí)驗(yàn)室","www.cndev-lab.com"); 
    Internet b = a; 
    b.show(); 
    test(b); 
}

  上面代碼就演示了深拷貝的問(wèn)題,對(duì)對(duì)象b的cname屬性采取了新開(kāi)辟內(nèi)存的方式避免了內(nèi)存歸屬不清所導(dǎo)致析構(gòu)釋放空間時(shí)候的錯(cuò)誤,最后我必須提一下,對(duì)于上面的程序我的解釋并不多,就是希望讀者本身運(yùn)行程序觀察變化,進(jìn)而深刻理解。

  拷貝和淺拷貝的定義可以簡(jiǎn)單理解成:如果一個(gè)類(lèi)擁有資源(堆,或者是其它系統(tǒng)資源),當(dāng)這個(gè)類(lèi)的對(duì)象發(fā)生復(fù)制過(guò)程的時(shí)候,這個(gè)過(guò)程就可以叫做深拷貝,反之對(duì)象存在資源但復(fù)制過(guò)程并未復(fù)制資源的情況視為淺拷貝。


  淺拷貝資源后在釋放資源的時(shí)候會(huì)產(chǎn)生資源歸屬不清的情況導(dǎo)致程序運(yùn)行出錯(cuò),這點(diǎn)尤其需要注意!

 

另一篇:

 

關(guān)于拷貝構(gòu)造函數(shù)和賦值運(yùn)算符
作者:馮明德


重點(diǎn):包含動(dòng)態(tài)分配成員的類(lèi) 應(yīng)提供拷貝構(gòu)造函數(shù),并重載"="賦值操作符。

 

以下討論中將用到的例子:

class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete pBuffer;}
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //類(lèi)的對(duì)象中包含指針,指向動(dòng)態(tài)分配的內(nèi)存資源
int nSize;
};

 

 

 

 

這個(gè)類(lèi)的主要特點(diǎn)是包含指向其他資源的指針。

pBuffer指向堆中分配的一段內(nèi)存空間。

 

 

一、拷貝構(gòu)造函數(shù)

int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init40);
//現(xiàn)在需要另一個(gè)對(duì)象,需要將他初始化稱(chēng)對(duì)象一的狀態(tài)
CExample theObjtwo=theObjone;
...
}

 

 

 

 

語(yǔ)句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。

其完成方式是內(nèi)存拷貝,復(fù)制所有成員的值。

完成后,theObjtwo.pBuffer==theObjone.pBuffer。

即它們將指向同樣的地方,指針雖然復(fù)制了,但所指向的空間并沒(méi)有復(fù)制,而是由兩個(gè)對(duì)象共用了。這樣不符合要求,對(duì)象之間不獨(dú)立了,并為空間的刪除帶來(lái)隱患。

所以需要采用必要的手段來(lái)避免此類(lèi)情況。

回顧以下此語(yǔ)句的具體過(guò)程:首先建立對(duì)象theObjtwo,并調(diào)用其構(gòu)造函數(shù),然后成員被拷貝。

可以在構(gòu)造函數(shù)中添加操作來(lái)解決指針成員的問(wèn)題。

所以C++語(yǔ)法中除了提供缺省形式的構(gòu)造函數(shù)外,還規(guī)范了另一種特殊的構(gòu)造函數(shù):拷貝構(gòu)造函數(shù),上面的語(yǔ)句中,如果類(lèi)中定義了拷貝構(gòu)造函數(shù),這對(duì)象建立時(shí),調(diào)用的將是拷貝構(gòu)造函數(shù),在拷貝構(gòu)造函數(shù)中,可以根據(jù)傳入的變量,復(fù)制指針?biāo)赶虻馁Y源。

 

拷貝構(gòu)造函數(shù)的格式為:構(gòu)造函數(shù)名(對(duì)象的引用)

提供了拷貝構(gòu)造函數(shù)后的CExample類(lèi)定義為:

class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete pBuffer;}
CExample(const CExample&); //拷貝構(gòu)造函數(shù)
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //類(lèi)的對(duì)象中包含指針,指向動(dòng)態(tài)分配的內(nèi)存資源
int nSize;
};
CExample::CExample(const CExample& RightSides) //拷貝構(gòu)造函數(shù)的定義
{
nSize=RightSides.nSize; //復(fù)制常規(guī)成員
pBuffer=new char[nSize]; //復(fù)制指針指向的內(nèi)容
memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}

 

 

 

 

這樣,定義新對(duì)象,并用已有對(duì)象初始化新對(duì)象時(shí),CExample(const CExample& RightSides)將被調(diào)用,而已有對(duì)象用別名RightSides傳給構(gòu)造函數(shù),以用來(lái)作復(fù)制。

 

原則上,應(yīng)該為所有包含動(dòng)態(tài)分配成員的類(lèi)都提供拷貝構(gòu)造函數(shù)。

 

拷貝構(gòu)造函數(shù)的另一種調(diào)用。

 

當(dāng)對(duì)象直接作為參數(shù)傳給函數(shù)時(shí),函數(shù)將建立對(duì)象的臨時(shí)拷貝,這個(gè)拷貝過(guò)程也將調(diào)同拷貝構(gòu)造函數(shù)。

例如

BOOL testfunc(CExample obj);
testfunc(theObjone); //對(duì)象直接作為參數(shù)。
BOOL testfunc(CExample obj)
{
//針對(duì)obj的操作實(shí)際上是針對(duì)復(fù)制后的臨時(shí)拷貝進(jìn)行的
}

 

 

 

 

還有一種情況,也是與臨時(shí)對(duì)象有關(guān)的

當(dāng)函數(shù)中的局部對(duì)象被被返回給函數(shù)調(diào)者時(shí),也將建立此局部對(duì)象的一個(gè)臨時(shí)拷貝,拷貝構(gòu)造函數(shù)也將被調(diào)用

CTest func()
{
CTest theTest;
return theTest
}

 

 

 

 

二、賦值符的重載

下面的代碼與上例相似

int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init(40);
CExample theObjthree;
theObjthree.Init(60);
//現(xiàn)在需要一個(gè)對(duì)象賦值操作,被賦值對(duì)象的原內(nèi)容被清除,并用右邊對(duì)象的內(nèi)容填充。
theObjthree=theObjone;
return 0;
}

 

 

 

也用到了"="號(hào),但與"一、"中的例子并不同,"一、"的例子中,"="在對(duì)象聲明語(yǔ)句中,表示初始化。更多時(shí)候,這種初始化也可用括號(hào)表示。

例如 CExample theObjone(theObjtwo);

而本例子中,"="表示賦值操作。將對(duì)象theObjone的內(nèi)容復(fù)制到對(duì)象theObjthree;,這其中涉及到對(duì)象theObjthree原有內(nèi)容的丟棄,新內(nèi)容的復(fù)制。

但"="的缺省操作只是將成員變量的值相應(yīng)復(fù)制。舊的值被自然丟棄。

由于對(duì)象內(nèi)包含指針,將造成不良后果:指針的值被丟棄了,但指針指向的內(nèi)容并未釋放。指針的值被復(fù)制了,但指針?biāo)竷?nèi)容并未復(fù)制。

 

因此,包含動(dòng)態(tài)分配成員的類(lèi)除提供拷貝構(gòu)造函數(shù)外,還應(yīng)該考慮重載"="賦值操作符號(hào)。

類(lèi)定義變?yōu)?

class CExample
{
...
CExample(const CExample&); //拷貝構(gòu)造函數(shù)
CExample& operator = (const CExample&); //賦值符重載
...
};
//賦值操作符重載
CExample & CExample::operator = (const CExample& RightSides)
{
nSize=RightSides.nSize; //復(fù)制常規(guī)成員
char *temp=new char[nSize]; //復(fù)制指針指向的內(nèi)容
memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
delete []pBuffer; //刪除原指針指向內(nèi)容  (將刪除操作放在后面,避免X=X特殊情況下,內(nèi)容的丟失)
pBuffer=temp;   //建立新指向
return *this
}

 

 

 

 

 

三、拷貝構(gòu)造函數(shù)使用賦值運(yùn)算符重載的代碼。

CExample::CExample(const CExample& RightSides)
{
pBuffer=NULL;
*this=RightSides	 //調(diào)用重載后的"="
}
"

 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多