運算符重載--類的賦值運算符重載

 
在面向對象程序設計中,對象間的相互拷貝和賦值是經常進行的操作。
 如果對象在申明的同時馬上進行的初始化操作,則稱之爲拷貝運算。例如:
class1 A("af"); class1 B=A;
此時其實際調用的是B(A)這樣的淺拷貝操作。
 如果對象在申明之後,在進行的賦值運算,我們稱之爲賦值運算。例如:
class1 A("af"); class1 B;
B=A;
此時實際調用的類的缺省賦值函數B.operator=(A);
不管是淺拷貝還是賦值運算,其都有缺省的定義。也就是說,即使我們不overload這兩種operation,仍然可以運行。
那麼,我們到底需不需要overload這兩種operation 呢?
答案就是:一般,我們我們需要手動編寫析構函數的類,都需要overload 拷貝函數和賦值運算符
下面介紹類的賦值運算符
1.C++中對象的內存分配方式
在C++中,對象的實例在編譯的時候,就需要爲其分配內存大小,因此,系統都是在stack上爲其分配內存的。這一點和C#完全不同!千 萬記住:在C#中,所有類都是reference type,要創建類的實體,必須通過new在heap上爲其分配空間,同時返回在stack上指向其地址的reference.
因此,在C++中,只要申明該實例,在程序編譯後,就要爲其分配相應的內存空間,至於實體內的各個域的值,就由其構造函數決定了。
例如:
class A
{
public:
A()
{
}

A(
int id,char *t_name)
 
{
_id
=id;
name
=new char[strlen(t_name)+1];
strcpy(name,t_name);
}
    private:
char *name;
int _id;
}


int main()
{
A a(
1,"herengang");
A b;
}

在程序編譯之後,a和b在stack上都被分配相應的內存大小。只不過對象a的域都被初始化,而b則都爲隨機值。
其內存分配如下:


2. 缺省情況下的賦值運算符
如果我們執行以下:
b=a;
則其執行的是缺省定義的缺省的賦值運算。所謂缺省的賦值運算,是指對象中的所有位於stack中的域,進行相應的複製。但是,如果對象有位於heap上的域的話,其不會爲拷貝對象分配heap上的空間,而只是指向相同的heap上的同一個地址。
執行b=a這樣的缺省的賦值運算後,其內存分配如下:

因此,對於缺省的賦值運算,如果對象域內沒有heap上的空間,其不會產生任何問題。但是,如果對象域內需要申請heap上的空間,那麼在析構對象的時候,就會連續兩次釋放heap上的同一塊內存區域,從而導致異常。
    ~A()
{        
delete name;
}

3.解決辦法--重載(overload)賦值運算符
因此,對於對象的域在heap上分配內存的情況,我們必須重載賦值運算符。當對象間進行拷貝的時候,我們必須讓不同對象的成員域指向其不同的heap地址--如果成員域屬於heap的話。    
因此,重載賦值運算符後的代碼如下:
class A
{
public:

A()
 
{
}

A(
int id,char *t_name)
{
_id
=id;
name
=new char[strlen(t_name)+1];
strcpy(name,t_name);
}


A
& operator =(A& a)
//注意:此處一定要返回對象的引用,否則返回後其值立即消失!
{
if(name!=NULL)
delete name;
this->_id=a._id;
int len=strlen(a.name);
name
=new char[len+1];
strcpy(name,a.name);
return *this;
}


~A()
{
cout
<<"~destructor"<<endl;
delete name;
}


int _id;
char *name;
}
;

int main()
{
 A a(1,"herengang");
A b;
b=a;

}

其內存分配如下:

這樣,在對象a,b退出相應的作用域,其調用相應的析構函數,然後釋放分別屬於不同heap空間的內存,程序正常結束。


references:
類的深拷貝函數的重載
public class A
{
public:
...
A(A &a);//重載拷貝函數
A& operator=(A &b);//重載賦值函數
 //或者 我們也可以這樣重載賦值運算符 void operator=(A &a);即不返回任何值。如果這樣的話,他將不支持客戶代買中的鏈式賦值 ,例如a=b=c will be prohibited!
    private:
int _id;
char *username;
}

A::A(A &a)
{
_id=a._id;
username=new char[strlen(a.username)+1];
if(username!=NULL)
strcpy(username,a.usernam);
}

A& A::operaton=(A &a)
{
if(this==&a)//  問:什麼需要判斷這個條件?(不是必須,只是優化而已)。答案:提示:考慮a=a這樣的操作。
return *this;
   if(username!=NULL)
delete username;
        _id=a._id;
username=new char[strlen(a.username)+1];
if(username!=NULL)
strcpy(username,a.usernam);
return *this;    
}
//另外一種寫法:
void A::operation=(A &a)
{
   if(username!=NULL)
delete username;
        _id=a._id;
username=new char[strlen(a.username)+1];
if(username!=NULL)
strcpy(username,a.usernam);
}

其實,從上可以看出,賦值運算符和拷貝函數很相似。只不過賦值函數最好有返回值(進行鏈式賦值),返回也最好是對象的引用(爲什麼不是對象本身呢?note2有講解), 而拷貝函數不需要返回任何同時,賦值函數首先要釋放掉對象自身的堆空間(如果需要的話),然後進行其他的operation.而拷貝函數不需要如此,因爲對象此時還沒有分配堆空間。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章