到目前為止,變數建立後會配置記憶體空間,這類資源是配置在記憶體的堆疊區(Stack),生命週期侷限於函式執行期間,也就是函式執行過後,配置的空間就會自動清除。
若要將函式執行結果傳回,不能直接傳回這類被自動配置空間的位址,因為函式執行過後,該空間就會釋出,函式呼叫者後續若透過位址取用這些資源,會發生不可預期的結果,例如,不能直接將區域建立的變數位址或陣列位址傳回。
然而程式運行後,資源之間的互用關係錯綜複雜,有些資源「無法預期」被使用的生命週期,因為無法預期,也就有賴於開發者自行管理記憶體資源,也就是開發者得自行在需要的時候配置記憶體,這些記憶體會被配置在堆積區(Heap),不會自動清除,開發者得在不使用資源時自行刪除記憶體。
要自行配置或刪除記憶體,可以使用 new
與 delete
運算子。舉例來說,可以在程式中以動態方式配置一個 int
型態大小的記憶體:
int *p = new int;
在這段程式中,new
運算子會配置 int
需要的空間,並傳回該空間的位址,可以使用指標 p
來儲存位址,這段程式只配置空間但不初始空間的值,若要在配置完成後指定儲存值,可以如此宣告:
int *p = new int(100);
這段程式在配置空間之後,會將空間中的儲存值設定為 100,以下使用一個簡單的程式來示範動態配置的使用:
#include <iostream>
using namespace std;
int main() {
int *p = new int(100);
cout << "空間位址:" << p << endl
<< "儲存的值:" << *p << endl;
*p = 200;
cout << "空間位址:" << p << endl
<< "儲存的值:" << *p << endl;
delete p;
return 0;
}
執行結果:
空間位址:0x787a88
儲存的值:100
空間位址:0x787a88
儲存的值:200
使用 new
動態配置的空間,在程式結束前不會自動歸還,必須使用 delete
將空間歸還,若大量使用 new
而沒有適當使用 delete
的話,由於空間一直沒有歸還,最後將導致整個記憶體空間用盡。
如果想配置連續個指定型態的空間,可以如下:
int *p = new int[1000];
這段程式碼動態配置了 1000 個 int
大小的空間,並傳回空間的第一個位址,配置後的空間資料是未知的,[]
中指定的長度可以是來自於運算式,不必是編譯時期就得決定的值,這個值必須自行儲存下來,因為沒有任何方式,可以從 p
得知到底配置的長度是多少。
如果想在配置連續空間後指定每個空間的初值,可以使用 {}
,例如:
int *p = new int[3]{10, 20, 30};
如果要全部設定為型態的零值,可以如下:
int *p = new int[3]();
連續配置得來的空間,不使用時要使用 delete[]
歸還給記憶體,必須加上 []
,表示歸還的是整個連續空間:
delete [] p;
之前在談陣列時說過,陣列具有指標性質,因此上面的方式,會被用來克服陣列大小必須事先決定的問題,也就是可以用來動態地配置連續空間,並當成陣列來操作,例如底下是個簡單的示範:
#include <iostream>
using namespace std;
int main() {
int size = 0;
cout << "輸入長度:";
cin >> size;
int *arr = new int[size]{0};
cout << "指定元素:" << endl;
for(int i = 0; i < size; i++) {
cout << "arr[" << i << "] = ";
cin >> arr[i];
}
cout << "顯示元素值:" << endl;
for(int i = 0; i < size; i++) {
cout << "arr[" << i << "] = " << arr[i]
<< endl;
}
delete [] arr;
return 0;
}
執行結果:
輸入長度:3
指定元素:
arr[0] = 10
arr[1] = 20
arr[2] = 30
顯示元素值:
arr[0] = 10
arr[1] = 20
arr[2] = 30
若要動態配置連續空間,並當成二維陣列來操作,就記得二維(或多維)陣列,就是以陣列的陣列來實作,二維陣列就是多段一維陣列,如果你的二維陣列有兩段一維陣列,那就是如下:
int **arr = new int*[2];
現在 arr[0]
、arr[1]
可以分別儲存一維陣列位址,目前尚未初始,若每段一維陣列的長度是 3,可以如下動態配置,並將一維陣列每個元素初始設為 0 :
for(int i = 0; i < 2; i++) {
arr[i] = new int[3]{0};
}
來看一下簡單的範例:
#include <iostream>
using namespace std;
int main() {
int **arr = new int*[2];
for(int i = 0; i < 2; i++) {
arr[i] = new int[3]{0};
}
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 3; j++) {
cout << arr[i][j] << " ";
}
cout << endl;
}
for(int i = 0; i < 2; i++) {
delete [] arr[i];
}
delete [] arr;
return 0;
}
記得最後要刪除配置的空間時,也要如以上範例逐一刪除,執行結果如下:
0 0 0
0 0 0
既然可以動態配置,那每段一維陣列長度當然可以不一樣囉!
#include <iostream>
using namespace std;
int main() {
int **arr = new int*[2];
arr[0] = new int[3]{0};
arr[1] = new int[5]{0};
for(int i = 0; i < 3; i++) {
cout << arr[0][i] << " ";
}
cout << endl;
for(int i = 0; i < 5; i++) {
cout << arr[1][i] << " ";
}
cout << endl;
for(int i = 0; i < 2; i++) {
delete [] arr[i];
}
delete [] arr;
return 0;
}
執行結果:
0 0 0
0 0 0 0 0
當然,動態配置的方式,會令程式變得不易理解,在需要動態建立長度不定的容器時,建議考慮使用 vector
之類的容器。
C++ 11 提供了 unique_ptr
、shared_ptr
等類別,可以協助管理動態配置的資源,這之後再來談。