C++學習之記憶體的分配和初始化
C++定義了2個運算子來分配和釋放動態記憶體。new分配記憶體,delete釋放記憶體。
1. 使用new動態分配和初始化物件
在自由空間分配的記憶體是無名的,new返回一個指向分配的物件的指標。
int *pi = new int; // pi指向一個動態分配的、未初始化的無名物件
預設情況下,動態分配的物件是預設初始化,內建型別(int, double)或組合型別(struct)的物件的值是未定義的,十分危險。類型別(vector、string)物件使用預設建構函式進行初始化。
string *ps = new string; // string初始化為空 int *pi = new int; // pi指向一個未初始化的int
使用圓括號對物件進行初始化
int *q1 = new int(1024); // q1指向的物件的值為1024
string *ps = new string(10, '9'); // 初始化為"9999999999"
使用列表初始化(花括號)
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
對動態分配的物件進行值初始化,只需在型別名之後跟一對空括號即可
string *ps1 = new string; // 預設初始化為空string string *ps2 = new string(); // 值初始化為空string int *pi1 = new int; // 預設初始化,*pi1的值未定義 int *pi2 = new int(); // 值初始化為0, *pi2為0
對於定義了自己的建構函式的類型別來說,物件都會通過預設建構函式進行初始化。而內建型別則不具備這個功能,需要我們對其初始化。
若我們提供了一個括號包圍的初始化器,則可以使用auto根據初始化器來自動推斷我們想分配的物件的型別。由於編譯器要用初始化器的型別來推斷要分配的型別,只有當括號中僅有單一初始化器時才可以使用auto:
auto p1 = new auto(pi1); // 與pi1型別相同的物件,該物件用pi1進行初始化,p1是int *
auto p2 = new auto(pi1,pi2); // ERROR:括號中只能由單個初始化器
動態分配的const物件必須初始化
用new分配const物件。
const int *pci = new const int(1024); // 分配並初始化一個const int
const string *pcs = new const string;
若系統記憶體耗盡,則記憶體分配失敗,new會丟擲bad_alloc的異常。可以使用定位new來阻止拋異常:
int *p3 = new (nothrow) int; // 若分配失敗,new返回一個空指標
2、釋放動態記憶體
動態記憶體使用完畢後,使用delete將記憶體歸還給系統。delete執行2個動作:銷燬給定的指標指向的物件;釋放對應的記憶體。傳遞給delete的指標必須指向動態分配的記憶體或一個空指標。
delete p3;
const物件的值不能被改變,但是其本身可以被銷燬。
const int *pci = new const int(1024); // 分配並初始化一個const int
delete pci; // 釋放一個const物件
動態物件的生存期
對於一個由內建指標管理的動態物件,直到被顯式釋放之前都是存在的。
動態記憶體的使用存在的三個常見問題:
- 忘記delete記憶體
- 使用已經釋放的物件
- 同一記憶體多次釋放
delete一個指標之後,指標值變得無效,但是很多機器上依然儲存著動態記憶體的地址,指標變成了空懸指標:指向一塊曾經儲存資料但是現在已經無效的記憶體的指標。
避免空懸指標:在指標即將離開其作用域之前釋放掉關聯的記憶體,若需要保留指標,可以在delete之後將nullptr賦予指標,表明指標不指向任何物件。
使用智慧指標可以避免這些問題。
3、shared_ptr和new的結合
若不初始化一個智慧指標,則它會初始化為一個空指標,可以用new返回的指標來初始化智慧指標。
shared_ptr<double> p4; // shared_ptr指向一個double
接收指標引數的智慧指標建構函式是explicit的,不能將一個內建指標轉換為智慧指標,必須採用直接初始化:
shared_ptr<int> p5 = new int(42); // ERROR
shared_ptr<int> p5(new int(42)); // p2指向一個值為42的int
一個返回shared_ptr的函式不能在其返回語句中隱式轉換一個普通指標:
shared_ptr<int> clone(int p){
return new int(p); // error: 隱式轉換為shared_ptr<int>
}
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p)); // 正確:顯式用int*建立shared_ptr<int>
}
一個用來初始化智慧指標的普通指標必須指向動態記憶體,因為智慧指標預設使用delete釋放它關聯的物件,可以將智慧指標繫結到一個指向其他型別的資源的指標上。