當一個類的對象向該類的另一個對象賦值時,就會用到該類的賦值構造函數。
當沒有重載賦值構造函數(賦值運算符)時,通過默認賦值構造函數來進行賦值操作
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。
轉載自: