小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

數據結構中的copy構造函數和賦值重載函數-堅強的心-搜狐博客

 曾經艱難走過 2010-04-05

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

    在數據結構中,對于程序的實現一般有兩種方式:順序實現和鏈接實現,即一個是由數組作為基本的數據成員,而另一個則是由節(jié)點Node作為基本的數據成員。其中,鏈接實現時,如果用戶的不經意,就會造成很多不必要的garbage,而正是出于這種考慮,在C++中有了三個保護函數:析構函數、copy構造函數和賦值重載函數。

    析構函數類似于構造函數的“反函數”,目的是:“在所聲明的對象在銷毀時,釋放其所占用的內存空間?!比缦旅媸莝tack的析構函數:

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

    而對于copy構造函數和賦值重載函數,都是為了避免用戶在使用時,輕易對兩個對象之間賦值所設立的保護函數。他們的區(qū)別可以簡單通過下面這個例子看出來:

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

而對于更加詳細的區(qū)別,這里我轉載了一片我感覺很不錯的文章:   

"   在學習這一章內容前我們已經學習過了類的構造函數和析構函數的相關知識,對于普通類型的對象來說,他們之間的復制是很簡單的,例如:

int a = 10;
int b =a;

  自己定義的類的對象同樣是對象,誰也不能阻止我們用以下的方式進行復制,例如:

#include <iostream
using namespace std; 
 
class Test 

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

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

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

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

  代碼如下:

#include <iostream
using namespace std; 
 
class Test 

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

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

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

  當用一個已經初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用,如果你沒有自定義拷貝構造函數的時候系統(tǒng)將會提供給一個默認的拷貝構造函數來完成這個過程,上面代碼的復制核心語句就是通過Test(Test &c_t)拷貝構造函數內的p1=c_t.p1;語句完成的。如果取掉這句代碼,那么b對象的p1屬性將得到一個未知的隨機值;

下面我們來討論一下關于淺拷貝和深拷貝的問題。

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

  為了更詳細的說明問題,請看如下的代碼。

#include <iostream
using namespace std; 
 
class Internet 

public
    Internet(char *name,char *address) 
    { 
        cout<<"載入構造函數"<<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構造函數"<<endl; 
        strcpy(Internet::name,temp.name); 
        strcpy(Internet::address,temp.address); 
        cname=new char[strlen(name)+1];//這里注意,深拷貝的體現! 
        if(cname!=NULL) 
        { 
            strcpy(Internet::cname,name); 
        } 
    } 
    ~Internet() 
    { 
        cout<<"載入析構函數!"; 
        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函數"<<endl; 

void main() 

    Internet a("中國軟件開發(fā)實驗室","www.cndev-lab.com"); 
    Internet b = a; 
    b.show(); 
    test(b); 
}

  上面代碼就演示了深拷貝的問題,對對象b的cname屬性采取了新開辟內存的方式避免了內存歸屬不清所導致析構釋放空間時候的錯誤,最后我必須提一下,對于上面的程序我的解釋并不多,就是希望讀者本身運行程序觀察變化,進而深刻理解。

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


  淺拷貝資源后在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯,這點尤其需要注意!

 

另一篇:

 

關于拷貝構造函數和賦值運算符
作者:馮明德


重點:包含動態(tài)分配成員的類 應提供拷貝構造函數,并重載"="賦值操作符。

 

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

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

 

 

 

 

這個類的主要特點是包含指向其他資源的指針。

pBuffer指向堆中分配的一段內存空間。

 

 

一、拷貝構造函數

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

 

 

 

 

語句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。

其完成方式是內存拷貝,復制所有成員的值。

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

即它們將指向同樣的地方,指針雖然復制了,但所指向的空間并沒有復制,而是由兩個對象共用了。這樣不符合要求,對象之間不獨立了,并為空間的刪除帶來隱患。

所以需要采用必要的手段來避免此類情況。

回顧以下此語句的具體過程:首先建立對象theObjtwo,并調用其構造函數,然后成員被拷貝。

可以在構造函數中添加操作來解決指針成員的問題。

所以C++語法中除了提供缺省形式的構造函數外,還規(guī)范了另一種特殊的構造函數:拷貝構造函數,上面的語句中,如果類中定義了拷貝構造函數,這對象建立時,調用的將是拷貝構造函數,在拷貝構造函數中,可以根據傳入的變量,復制指針所指向的資源。

 

拷貝構造函數的格式為:構造函數名(對象的引用)

提供了拷貝構造函數后的CExample類定義為:

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

 

 

 

 

這樣,定義新對象,并用已有對象初始化新對象時,CExample(const CExample& RightSides)將被調用,而已有對象用別名RightSides傳給構造函數,以用來作復制。

 

原則上,應該為所有包含動態(tài)分配成員的類都提供拷貝構造函數。

 

拷貝構造函數的另一種調用。

 

當對象直接作為參數傳給函數時,函數將建立對象的臨時拷貝,這個拷貝過程也將調同拷貝構造函數。

例如

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

 

 

 

 

還有一種情況,也是與臨時對象有關的

當函數中的局部對象被被返回給函數調者時,也將建立此局部對象的一個臨時拷貝,拷貝構造函數也將被調用

CTest func()
{
CTest theTest;
return theTest
}

 

 

 

 

二、賦值符的重載

下面的代碼與上例相似

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

 

 

 

也用到了"="號,但與"一、"中的例子并不同,"一、"的例子中,"="在對象聲明語句中,表示初始化。更多時候,這種初始化也可用括號表示。

例如 CExample theObjone(theObjtwo);

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

但"="的缺省操作只是將成員變量的值相應復制。舊的值被自然丟棄。

由于對象內包含指針,將造成不良后果:指針的值被丟棄了,但指針指向的內容并未釋放。指針的值被復制了,但指針所指內容并未復制。

 

因此,包含動態(tài)分配成員的類除提供拷貝構造函數外,還應該考慮重載"="賦值操作符號。

類定義變?yōu)?

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

 

 

 

 

 

三、拷貝構造函數使用賦值運算符重載的代碼。

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

 

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多