拷貝構造函數
基本形式
注意形參類型是類類型,不能聲明爲explicit,因爲有些地方需要隱式轉換
class Foo
{
public:
Foo();
Foo(const Foo&);
};
合成拷貝構造函數
你不聲明構造函數,也會給你一個合成的。淺拷貝問題。
調用時機
1.非引用類型形參
2.返回非引用類型
3.使用{}初始化一個數組
4.標準庫容器的push或insert操作。emplace調用的是構造函數。
爲啥形參是引用的
不是引用的話,會一直調用拷貝構造函數,會死循環下去。
拷貝初始化與explicit構造函數
vector構造函數爲explicit類型,所以以下寫法不正確。
vector<int> v1(10); //直接初始化
veector<int> v2=10; //xxx,構造不被允許
解釋:其實veector<int> v2=10; 包含兩句意思,第一句是調用構造函數創一個臨時對象,第二句是拷貝。
下列語句也不行:
void f(vector<int>);
f(10); //xxx,也是不允許構造,即第一步都不行
f(vector<int>(10)); //正確
編譯器優化:
有時編譯器會做出優化。把兩步優化成一步。前提是拷貝構造是private,不是private直接在第二步拷貝時就報錯了。
string null_book = "000000"; //包含兩步,創建對象和拷貝構造
//優化成
string null_book("000000");
賦值運算符
基本形式
class Foo
{
public:
Foo& operator=(const Foo&);
};
當重載運算符函數是成員函數時,第一個參數就綁定到this指針。
Sales_data a1,a2;
a1 = a2; //調用=運算符
合成 賦值運算符
你不寫也會送你一個合成的。淺拷貝問題。
析構函數
基本形式
class Foo
{
public:
~Foo();
};
函數執行流程
構造函數先執行初始化列表,再執行函數體;析構函數先執行函數體,再銷燬成員(逆序銷燬)。
類的析構函數和指針類型成員
調用成員的析構函數,內置類型的成員沒有析構函數。
注意不會自動幫內置指針成員調用delete p;
而智能指針是類類型,具有析構函數。
調用時機(句句箴言)
1.離開作用域被銷燬時。
2.也會調用對象的成員的析構函數。
3.容器(標準庫容器或數組)被銷燬時,也調用元素的析構。
4. 對new這些動態創建的對象,你得調用delete,纔會幫你調用析構。
5. 臨時對象,它的完整表達式結束時。
注意局部指針或引用離開作用域時,不會自動幫你調用。
合成析構函數
你不寫,也會幫你合成一個,但功能受限。
三五法則
1.一般你定義了析構函數,說明裏面有動態內存釋放,所以你必須得定義析構函數。
class HasPtr
{
public:
HasPtr(const string&s):ps(new string(s)){}
~HasPtr(){
delete ps; //需要手動釋放
}
private:
string *ps;
};
如果你不自己定義拷貝函數的話,就會用合成的,會發生淺拷貝。
HasPtr f(HasPtr p) //這裏一次拷貝
{
HasPtr ret = p; //這裏一次拷貝
return ret; //xxx,p和ret都被銷燬了,delete兩次
}
更有甚者,你一調用就銷燬實參的成員了。
HasPtr p("some");
f(p); //會釋放p.ps
HasPtr q(p); //xxx,都沒了ps了
2. 定義了拷貝函數也必須定義賦值運算符,反之亦然,如果沒有成員涉及動態內存分配,不需要再定義析構函數。
比如爲對象分配一個新的序號