本文主要介紹了拷貝構造函數和賦值運算符的區別,以及在何時調用拷貝構造函數、什麼狀況下調用賦值運算符。最後,簡單的分析了下深拷貝和淺拷貝的問題。函數
在默認狀況下(用戶沒有定義,可是也沒有顯式的刪除),編譯器會自動的隱式生成一個拷貝構造函數和賦值運算符。但用戶可使用delete
來指定不生成拷貝構造函數和賦值運算符,這樣的對象就不能經過值傳遞,也不能進行賦值運算。測試
class Person { public: Person(const Person& p) = delete; Person& operator=(const Person& p) = delete; private: int age; string name; };
上面的定義的類Person
顯式的刪除了拷貝構造函數和賦值運算符,在須要調用拷貝構造函數或者賦值運算符的地方,會提示_沒法調用該函數,它是已刪除的函數_。
還有一點須要注意的是,拷貝構造函數必須以引用的方式傳遞參數。這是由於,在值傳遞的方式傳遞給一個函數的時候,會調用拷貝構造函數生成函數的實參。若是拷貝構造函數的參數仍然是以值的方式,就會無限循環的調用下去,直到函數的棧溢出。this
拷貝構造函數和賦值運算符的行爲比較類似,都是將一個對象的值複製給另外一個對象;可是其結果卻有些不一樣,拷貝構造函數使用傳入對象的值生成一個新的對象的實例,而賦值運算符是將對象的值複製給一個已經存在的實例。這種區別從二者的名字也能夠很輕易的分辨出來,拷貝構造函數也是一種構造函數,那麼它的功能就是建立一個新的對象實例;賦值運算符是執行某種運算,將一個對象的值複製給另外一個對象(已經存在的)。調用的是拷貝構造函數仍是賦值運算符,主要是看是否有新的對象實例產生。若是產生了新的對象實例,那調用的就是拷貝構造函數;若是沒有,那就是對已有的對象賦值,調用的是賦值運算符。指針
調用拷貝構造函數主要有如下場景:code
代碼以下:對象
class Person { public: Person(){} Person(const Person& p) { cout << "Copy Constructor" << endl; } Person& operator=(const Person& p) { cout << "Assign" << endl; return *this; } private: int age; string name; }; void f(Person p) { return; } Person f1() { Person p; return p; } int main() { Person p; Person p1 = p; // 1 Person p2; p2 = p; // 2 f(p2); // 3 p2 = f1(); // 4 Person p3 = f1(); // 5 getchar(); return 0; }
上面代碼中定義了一個類Person
,顯式的定義了拷貝構造函數和賦值運算符。而後定義了兩個函數:f
,以值的方式參傳入Person對象;f1
,以值的方式返回Person對象。在main
中模擬了5中場景,測試調用的是拷貝構造函數仍是賦值運算符。執行結果以下:
blog
分析以下:內存
說到拷貝構造函數,就不得不提深拷貝和淺拷貝。一般,默認生成的拷貝構造函數和賦值運算符,只是簡單的進行值的複製。例如:上面的Person
類,字段只有int
和string
兩種類型,這在拷貝或者賦值時進行值複製建立的出來的對象和源對象也是沒有任何關聯,對源對象的任何操做都不會影響到拷貝出來的對象。反之,假如Person
有一個對象爲int *
,這時在拷貝時還只是進行值複製,那麼建立出來的Person
對象的int *
的值就和源對象的int *
指向的是同一個位置。任何一個對象對該值的修改都會影響到另外一個對象,這種狀況就是淺拷貝。get
深拷貝和淺拷貝主要是針對類中的指針和動態分配的空間來講的,由於對於指針只是簡單的值複製並不能分割開兩個對象的關聯,任何一個對象對該指針的操做都會影響到另外一個對象。這時候就須要提供自定義的深拷貝的拷貝構造函數,消除這種影響。一般的原則是:編譯器
對於拷貝構造函數的實現要確保如下幾點: