構造函數和析構函數的調用時機
1. 對于全局定義的對象,每當程序開始運行,在主函數 main 接受程序控制權之前,就調
用構造函數創建全局對象,整個程序結束時,自動調用全局對象的析構函數。
2. 對于局部定義的對象,每當程序流程到達該對象的定義處就調用構造函數,在程序離開
局部對象的作用域時調用對象的析構函數。
3. 對于關鍵字 static 定義的靜態對象,當程序流程到達該對象定義處調用構造函數,在整
個程序結束時調用析構函數。
4. 對于用 new 運算符創建的堆對象,每當創建該對象時調用構造函數,在使用 delete 刪
除該對象時,調用析構函數。
拷貝構造函數回顧
在看調用時機之前,先回顧以下拷貝構造函數的定義:
拷貝構造函數的形式是固定的:類名(const 類名 &)
1. 該函數是一個構造函數 —— 拷貝構造也是構造!
2. 該函數用一個已經存在的同類型的對象,來初始化新對象,即對對象本身進行復制
沒有顯式定義拷貝構造函數,這條復制語句依然可以通過,說明編譯器自動提供了默認的
拷貝構造函數。其形式是:
Point(const Point & rhs)
: _ix(rhs._ix)
, _iy(rhs._iy)
{}
但是默認的拷貝構造函數只能實現淺拷貝,無法對復雜的數據結構進行深拷貝,示例如下:
Computer pc("Acer",4500);
Computer pc2 = pc;//調用拷貝構造函數class Computer{
public:void print(){cout << "name:" << _name << endl;cout << "price:" << _price << endl;}
private:int _price;char *_name;
};
編譯可以通過,運行則會報錯。
如果是默認的拷貝構造函數,pc2會對pc的_brand進行淺拷貝,指向同一片內存;pc2被銷
毀時,會調用析構函數,將這片堆空間進行回收;pc再銷毀時,析構函數中又會試圖回收
這片空間,出現double free問題
如果拷貝構造函數需要顯式寫出時(該類有指針成員申請堆空間),在自定義的拷貝構造函數中要換成深拷貝的方式,先申請空間,再復制內容
Computer::Computer(const Computer & rhs)
: _brand(new char[strlen(rhs._brand) + 1]())
, _price(rhs._price)
{
strcpy(_brand, rhs._brand);
}
拷貝構造函數的調用時機
1. 當使用一個已經存在的對象初始化另一個同類型的新對象時;
2. 當函數參數(實參和形參的類型都是對象),形參與實參結合時(實參初始化形參);
—— 為了避免這次不必要的拷貝,可以使用引用作為參數
注意:類內拷貝構造函數必須對形參使用使用,否則會陷入對拷貝的遞歸調用導致棧溢出。
3. 當函數的返回值是對象,執行return語句時(編譯器有優化)。
——為了避免這次多余的拷貝,可以使用引用作為返回值,但一定要確保返回值的生命
周期大于函數的生命周期
拷貝構造函數的形式探究
拷貝構造函數是否可以去掉引用符號?
Point(const Point ?rhs)
—— 類名(const 類名) 形式,首先編譯器不允許這樣寫,直接報錯
如果拷貝函數的參數中去掉引用符號,進行拷貝時調用拷貝構造函數的過程中會發生“實參
和形參都是對象,用實參初始化形參”(拷貝構造第二種調用時機),會再一次調用拷貝構
造函數。形成遞歸調用,直到棧溢出,導致程序崩潰。
拷貝構造函數是否可以去掉const?
Point(Point & rhs)—— 類名(類名 &) 形式
編譯器不會報錯
加const的第一個用意:為了確保右操作數的數據成員不被改變
加const的第二個用意:為了能夠復制臨時對象的內容,因為非const引用不能綁定臨時變量(右值)
先看一個簡單的示例,看看什么是臨時的變量或對象:
參考:https://zhuanlan.zhihu.com/p/165391845
#include <iostream>
using namespace std;void f(int &a){cout << "f(" << a << ") is being called" << endl;
}void g(const int &a){cout << "g(" << a << ") is being called" << endl;
}int main(){int a = 3, b = 4;f(a + b); //編譯錯誤,把臨時變量作為非const的引用參數,傳遞給int &a了g(a + b); //OK,把臨時變量作為const&傳遞是允許的
}
上面的兩個調用之前,a+b的值會存在一個臨時變量中,因為a+b是一個表達式,本質上屬于一個沒有名字的變量,編譯器會自動生成一個臨時變量儲存a+b的值,當把這個臨時變量傳給f時,由于f的聲明中,參數是int&,不是常量引用,所以產生以下編譯錯誤:
error: invalid initialization of non-const reference of type 'int&' from a temporary of type 'int'
那么臨時變量跟引用有什么關系?C++語法規定,const引用可以綁定右值,非const引用不能綁定右值。這里什么是左值和右值?
通俗的說,可以取地址的變量稱為左值,反之則為右值。臨時變量,匿名變量,臨時對象,匿名對象他們在內存中并沒有實際的內存分配,絕大多數情況下屬于右值。對于沒有實際存在于內存中的變量或對象,不允許直接對其進行引用,因為編譯器認為引用的對象必須是內存中實體,面對這種情況,必須加const進行常量引用。
補充:【臨時變量】不能作為【非const引用參數】,不是因為他是常量,而是因為c++編譯器的一個關于語義的限制。如果一個參數是以非const引用傳入,c++編譯器就有理由認為程序員會在函數中修改這個值,并且這個被修改的引用在函數返回后要發揮作用。但如果你把一個臨時變量當作非const引用參數傳進來,由于臨時變量的特殊性,程序員并不能操作臨時變量,而且編譯器認為臨時變量不會常駐內存,隨時可能被釋放掉,所以,一般說來,修改一個臨時變量是毫無意義的,據此,c++編譯器加入了臨時變量不能作為非const引用的這個語義限制,意在限制這個非常規用法的潛在錯誤。
回到之前的拷貝構造函數,由之前的疑問可以得出,拷貝構造函數必須讓對象包含引用符號。
又因非const引用不能綁定臨時變量,所以對于拷貝構造函數必須進行const引用。
對于下圖中的示例,右邊的Computer實際并未被完全實例化,就直接拷貝給了pc3對象,屬于一個臨時對象,這條語句執行完就被釋放掉了。所以必須是const引用。