《C++ Primer》看到這一章我都驚呆了,C++竟然還能有這種功能:類可以控制該類型對象拷貝、賦值、移動和銷燬時做什麼。
以上操作通過:拷貝構造函數、移動構造函數、拷貝賦值運算符、移動賦值運算符、以及析構函數構成。
拷貝構造函數:
定義: 如果一個構造函數的第一個參數是自身類類型的引用,且任何額外參數都有默認值,則此構造函數是拷貝構造函數。
本質上是個構造函數,所以它出現就會構造新對象,沒有新對象它就沒出現。
拷貝構造函數的第一個參數必須是一個引用類型,因爲如果是值傳遞方式穿參的話,會立即再次調用拷貝構造函數,這樣就形成無限遞歸了!危險!
如果我們沒有自定義一個拷貝構造函數,編譯器會自動幫我們合成一個。默認拷貝構造函數會將其參數成員逐個拷貝到正在創建的對象中。
以下情況會發生拷貝初始化:
1. 用=定義變量。
2. 將一個對象作爲實參傳遞給一個非引用類型的形參。
3. 從一個返回類型爲非引用類型的函數返回一個對象。
4. 列表初始化一個數組中的元素或一個聚合類中的成員。
有個小坑是,A aa = a;
像這樣定義並初始化一個A類型的變量aa,只會調用拷貝構造函數,不會調用拷貝賦值運算符。
#include <iostream>
using namespace std;
class A{
public:
A(int i=0):n(i){}
A(A &a, int i=0):n(i){
cout<< a.getI() <<endl;
cout<< getI() <<endl;
cout<<"hello"<<endl;
}
const int getI(){
return n;
}
private:
int n = 0;
};
int main(){
A a(998);
A b = a;//調用拷貝構造函數
//輸出結果爲:
//998
//0
//hello
//證明 a 被當成了拷貝構造函數的第一個參數
return 0;
}
拷貝賦值運算符
這其實就是個操作符重載。本質上是個名爲operator=
的函數,所有有形參有返回值,有函數體,都有。
當然要成爲一個偉大的賦值運算符,當然要付出一點代價:
1. 名字必須是operator=
2. 必須定義爲成員函數
3. 左側運算對象就綁定到隱式的this參數上
4. 接受一個與其所在類相同類型的參數
5. 爲了與內置類型的賦值保持一致,返回指向左側對象的引用。
如果類沒有定義自己的拷貝賦值操作符,編譯器會自動生成一個。它會將右側運算對象的每個非static成員賦予左側運算對象的對應成員。
#include <iostream>
using namespace std;
class A{
public:
A(int i=0):n(i){}
A(A &a, int i=0):n(i){
cout<< a.getI() <<endl;
cout<< getI() <<endl;
cout<<"hello"<<endl;
}
A &operator=(const A &r){
n = r.n;
cout<<"copying..."<<endl;
return *this;
}
const int getI(){
return n;
}
private:
int n = 0;
};
int main(){
A a(998);
A b(333);
b = a;
cout<< b.getI()<<endl;
//copying...
//998
return 0;
}
析構函數:
析構函數釋放對象使用的資源,並銷燬對象的非static數據成員。
跟構造函數一樣的是:沒有返回類型,如果沒自定義,編譯器就會生成一個默認的。
跟構造函數不一樣的是:沒有構造函數初始化列表,名字是類名前加一個波浪號~,析構函數不接受參數,所以不能被重載,不能定義爲delete。
note:析構函數先執行函數體,然後銷燬成員,順序按初始化的逆序。銷燬一個內置類型的指針不會delete它指向的對象。