動態記憶體

到目前為止,在我們所有的程式中,我們擁有的記憶體僅限於我們為變數宣告的記憶體,所有變數的大小都在程式執行之前在原始碼中確定。但是,如果我們需要一個可變數量的記憶體,而這個記憶體只能在執行時確定呢?例如,我們需要一些使用者輸入來確定必要的記憶體空間。

答案是動態記憶體,為此 C++ 集成了運算子newdelete.

運算子 new 和 new[]

為了請求動態記憶體,我們使用運算子new. new之後跟一個數據型別說明符,如果需要一個以上的元素的序列,則用方括號括起來表示元素的數量[]。它返回指向新分配的記憶體塊的開始位置的指標。它的形式是

pointer = new type
pointer = new type [number_of_elements]

第一個表示式用於分配記憶體來包含一個型別為型別的單個元素。第二個表示式用於分配一個型別為型別,其中的元素塊(陣列),其中是一個整數值,表示元素的數量。例如

1
2
int * bobby;
bobby = new int [5];

在這種情況下,系統動態分配空間給五個int型別的元素,並返回指向序列中第一個元素的指標,該指標被賦值給bobby。因此,現在,bobby指向一個有效的記憶體塊,其中包含五個int.



型別的元素的空間。透過表示式bobby[0]或表示式*bobby都可以訪問 bobby 指向的第一個元素。正如指標部分所解釋的那樣,兩者是等價的。可以透過bobby[1]*(bobby+1)訪問第二個元素,以此類推...

您可能想知道宣告一個普通陣列和給指標分配動態記憶體之間的區別,正如我們剛才所做的那樣。最重要的區別是陣列的大小必須是一個常量值,這限制了它的大小為我們在設計程式時所決定的值,在程式執行之前,而動態記憶體分配允許我們在程式執行期間(執行時)使用任何變數或常量值作為其大小來分配記憶體。

我們的程式請求的動態記憶體由系統從記憶體堆中分配。然而,計算機記憶體是一種有限的資源,它可能會被耗盡。因此,重要的是要有一些機制來檢查我們分配記憶體的請求是否成功。

C++ 提供了兩種標準方法來檢查分配是否成功

一種是透過處理異常。使用此方法,當分配失敗時,會丟擲一個bad_alloc型別的異常。異常是 C++ 的一個強大的特性,將在本教程後面進行解釋。但現在您應該知道,如果丟擲了這個異常,並且沒有被特定的處理程式處理,程式的執行將會終止。

這種異常方法是 new 使用的預設方法,也是像這樣的宣告中使用的方法

1
bobby = new int [5];  // if it fails an exception is thrown 

另一種方法被稱為nothrow,使用它時,當記憶體分配失敗時,不會丟擲bad_alloc異常或終止程式,而是new返回一個空指標,程式繼續執行。

可以透過使用一個稱為nothrow的特殊物件來指定此方法,該物件在標頭檔案<new>中宣告,作為new:

1
bobby = new (nothrow) int [5];

的引數。在這種情況下,如果此記憶體塊的分配失敗,可以透過檢查bobby是否具有空指標值來檢測失敗

1
2
3
4
5
int * bobby;
bobby = new (nothrow) int [5];
if (bobby == 0) {
  // error assigning memory. Take measures.
  };

這個nothrow方法比異常方法需要更多的工作,因為每次記憶體分配後都必須檢查返回值,但由於它的簡單性,我將在我們的示例中使用它。無論如何,對於更大的專案,這種方法可能會變得乏味,在這種情況下,通常首選異常方法。異常方法將在本教程後面詳細解釋。

運算子 delete 和 delete[]

由於對動態記憶體的需求通常僅限於程式中的特定時刻,一旦不再需要它,就應該釋放它,以便該記憶體再次可用於其他動態記憶體請求。這是運算子delete的目的,其格式為

1
2
delete pointer;
delete [] pointer;

第一個表示式應用於刪除為單個元素分配的記憶體,第二個表示式應用於刪除為元素陣列分配的記憶體。

作為引數傳遞給 delete 的值必須是指向先前使用new分配的記憶體塊的指標,或空指標(在空指標的情況下,delete不起作用)。

// rememb-o-matic
#include <iostream>
#include <new>
using namespace std;

int main ()
{
  int i,n;
  int * p;
  cout << "How many numbers would you like to type? ";
  cin >> i;
  p= new (nothrow) int[i];
  if (p == 0)
    cout << "Error: memory could not be allocated";
  else
  {
    for (n=0; n<i; n++)
    {
      cout << "Enter number: ";
      cin >> p[n];
    }
    cout << "You have entered: ";
    for (n=0; n<i; n++)
      cout << p[n] << ", ";
    delete[] p;
  }
  return 0;
}
How many numbers would you like to type? 5
Enter number : 75
Enter number : 436
Enter number : 1067
Enter number : 8
Enter number : 32
You have entered: 75, 436, 1067, 8, 32,

請注意new語句中方括號內的值是使用者輸入的變數值 (i),而不是常量值。

1
p= new (nothrow) int[i];

但是使用者可能輸入了一個 i 的值,這個值太大,我們的系統無法處理。例如,當我試圖給“有多少個數字”這個問題賦值 10 億時,我的系統無法為該程式分配那麼多的記憶體,我得到了我們為此情況準備的文字訊息(錯誤:無法分配記憶體)。請記住,如果我們嘗試在 new 表示式中不指定 nothrow 引數來分配記憶體,則會丟擲一個異常,如果沒有處理該異常,程式將終止。

始終檢查動態記憶體塊是否成功分配是一個好習慣。因此,如果您使用nothrow方法,您應該始終檢查返回指標的值。否則,使用異常方法,即使您不處理該異常。這樣,程式將在該點終止,而不會導致繼續執行一段假設已分配記憶體塊的程式碼而實際上未分配記憶體的意外結果。

ANSI-C 中的動態記憶體


運算子newdelete是 C++ 獨有的。它們在 C 語言中不可用。但是,使用純 C 語言及其庫,也可以透過函式 malloccallocreallocfree 來使用動態記憶體,這些函式也可在 C++ 中使用,包括<cstdlib>標頭檔案(有關更多資訊,請參見 cstdlib)。

這些函式分配的記憶體塊不一定與 new 返回的記憶體塊相容,因此每個記憶體塊都應使用其自身的一組函式或運算子進行操作。
Index
目錄