new 與 delete


到目前為止,變數建立後會配置記憶體空間,這類資源是配置在記憶體的堆疊區(Stack),生命週期侷限於函式執行期間,也就是函式執行過後,配置的空間就會自動清除。

若要將函式執行結果傳回,不能直接傳回這類被自動配置空間的位址,因為函式執行過後,該空間就會釋出,函式呼叫者後續若透過位址取用這些資源,會發生不可預期的結果,例如,不能直接將區域建立的變數位址或陣列位址傳回。

然而程式運行後,資源之間的互用關係錯綜複雜,有些資源「無法預期」被使用的生命週期,因為無法預期,也就有賴於開發者自行管理記憶體資源,也就是開發者得自行在需要的時候配置記憶體,這些記憶體會被配置在堆積區(Heap),不會自動清除,開發者得在不使用資源時自行刪除記憶體。

要自行配置或刪除記憶體,可以使用 newdelete 運算子。舉例來說,可以在程式中以動態方式配置一個 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_ptrshared_ptr 等類別,可以協助管理動態配置的資源,這之後再來談。