1. 程式人生 > 實用技巧 >【轉】C++的賦值建構函式(賦值運算子過載)

【轉】C++的賦值建構函式(賦值運算子過載)

當一個類的物件向該類的另一個物件賦值時,就會用到該類的賦值建構函式。

當沒有過載賦值建構函式(賦值運算子)時,通過預設賦值建構函式來進行賦值操作

A a;
A b;
b = a;

注意:這裡a,b物件是已經存在的,是用a物件來賦值給b的。

賦值運算子的過載宣告如下:

A& operator = (const A& other)

通常大家會對拷貝建構函式和賦值建構函式混淆,這裡仔細比較兩者的區別:

1)拷貝建構函式是一個物件初始化一塊記憶體區域,這塊記憶體就是新物件的記憶體區,而賦值建構函式時對於一個已經被初始化的物件來進行賦值操作。

 1 class  A;  
 2
A a; 3 A b=a; //呼叫拷貝建構函式(b不存在) 4 A c(a) ; //呼叫拷貝建構函式 5 6 /****/ 7 8 class A; 9 A a; 10 A b; 11 b = a ; //呼叫賦值函式(b存在)

2)實現不一樣,拷貝建構函式首先是一個建構函式,它呼叫時候是通過引數的物件初始化產生一個新物件。賦值建構函式是把一個新的物件賦值給一個原有的物件。

舉例:

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
4 5 class MyStr 6 { 7 private: 8 char *name; 9 int id; 10 public: 11 MyStr():id(0),name(NULL) {} 12 MyStr(int _id, char *_name) //建構函式 13 { 14 cout << "constructor" << endl; 15 id = _id; 16 name = new char[strlen(_name) + 1]; 17 strcpy_s(name, strlen(_name) + 1
, _name); 18 } 19 MyStr(const MyStr& str) //拷貝建構函式 20 { 21 cout << "copy constructor" << endl; 22 id = str.id; 23 name = new char[strlen(str.name) + 1]; 24 strcpy_s(name, strlen(str.name) + 1, str.name); 25 } 26 MyStr& operator =(const MyStr& str)//賦值運算子 27 { 28 cout << "operator =" << endl; 29 if (this != &str) 30 { 31 if (name != NULL) 32 delete[] name; 33 this->id = str.id; 34 int len = strlen(str.name); 35 name = new char[len + 1]; 36 strcpy_s(name, strlen(str.name) + 1, str.name); 37 } 38 return *this; 39 } 40 ~MyStr() 41 { 42 delete[] name; 43 } 44 }; 45 46 int main() 47 { 48 MyStr str1(1, "hhxx"); 49 cout << "====================" << endl; 50 MyStr str2; 51 str2 = str1; 52 cout << "====================" << endl; 53 MyStr str3 = str2; 54 return 0; 55 }

結果:

說明:

1、引數

一般地,賦值運算子過載函式的引數是函式所在類的const型別的引用,加const是因為

(1)我們不希望在這個函式中對用來進行賦值的“原版”做任何修改。

(2)加上const,對於const的和非const的實參,函式都能接受;如果不加,就只能接受非const的實參。

用引用是因為:

這樣可以避免在函式呼叫時對實參的一次拷貝,提高了效率。

注意:上面的規定都不是強制的,可以不加const,也可以沒有引用。

2、返回值

一般地,返回值是被賦值者的引用,即*this(如上面例1),原因是:

(1)這樣在函式返回時避免一次拷貝,提高了效率。

(2)更重要的,這樣可以實現連續賦值,即類似a=b=c這樣。如果不是返回引用而是返回值型別,那麼,執行a=b時,呼叫賦值運算子過載函式,在函式返回時,由於返回的是值型別,所以要對return後邊的“東西”進行一次拷貝,得到一個未命名的副本(有些資料上稱之為“匿名物件”),然後將這個副本返回,而這個副本是右值,所以,執行a=b後,得到的是一個右值,再執行=c就會出錯。

注意:這也不是強制的,我們甚至可以將函式返回值宣告為void,然後什麼也不返回,只不過這樣就不能夠連續賦值了

3、賦值運算子過載函式不能被繼承

因為相較於基類,派生類往往要新增一些自己的資料成員和成員函式,如果允許派生類繼承積累的賦值運算子過載函式,那麼,在派生類不提供自己的賦值運算子過載函式時,就只能呼叫基類的,但基類版本只能處理基類的資料成員,在這種情況下,派生類自己的資料成員怎麼辦?所以,C++規定,賦值運算子過載函式不能被繼承。

4、賦值運算子過載函式要避免自賦值

對於賦值運算子過載函式,我們要避免自賦值(即自己給自己賦值)的發生,一般地,我們通過比較賦值者與被賦值者的地址是否相同來判斷兩者是否是同一物件(如例中的if(this != &str)一句)。避免自賦值的意義是:

(1)提高效率,顯然,自己給自己賦值完全是毫無意義的無用功,特別地,對於基類資料成員間的賦值,還會呼叫基類的賦值運算子過載函式,開銷是很大的。如果我們一旦判定是自賦值,就立即return *this,會避免對其它函式的呼叫。

(2)如果類的資料成員中含有指標,自賦值有時會導致災難性的後果。對於指標間的賦值(注意這裡指的是指標所指內容間的賦值,這裡假設用_p給p賦值),先要將p所指向的空間delete掉(為什麼要這麼做呢?因為指標p所指的空間通常是new來的,如果在為p重新分配空間前沒有將p原來的空間delete掉,會造成記憶體洩露),然後再為p重新分配空間,將_p所指的內容拷貝到p所指的空間。如果是自賦值,那麼p和_p是同一指標,在賦值操作前對p的delete操作,將導致p所指的資料同時被銷燬。那麼重新賦值時,拿什麼來賦?

所以,對於賦值運算子過載函式,一定要先檢查是否是自賦值,如果是,直接return *this。

轉載自:

C++中建構函式,拷貝建構函式和賦值函式的區別和實現

一文說盡C++賦值運算子過載函式(operator=)