C++ 使用 new 建立二維陣列

mkckr0發表於2023-01-11

1. 直接建立

C++ 使用 new 建立二維陣列最直接的方法就是 new T[M][N]。返回的指標型別是 T (*)[N],它是指向陣列的指標,可以直接使用陣列下標形式訪問元素。釋放記憶體直接使用delete[]。示例程式碼:

#include <iostream>

class A
{
public:
    A()
    {
        std::cout << "A::A" << std::endl;
    }
    ~A()
    {
        std::cout << "A::~A" << std::endl;
    }

    int x;
};

int main()
{
    A (*p)[3] = new A[2][3];
    delete[] p;
}

執行結果:

A::A
A::A
A::A
A::A
A::A
A::A
A::~A
A::~A
A::~A
A::~A
A::~A
A::~A

可以看到 A 的建構函式和解構函式正常執行。如果覺得 T (*)[N] 繁瑣,可以直接使用 auto p = new T[M][N]。三維陣列甚至更高維陣列都可以使用這種方法。例如,三維陣列使用 new T[M][N][O] 進行建立,依舊使用 delete[] p 進行釋放。

為什麼可以這樣寫?因為這種多維陣列和普通的多維陣列都是透過一維陣列實現的。例如,int a[6][8],實際上編譯器會轉化為 int b[6 * 8] 一維陣列。然後每次訪問二維陣列 a[i][j] 相當於訪問 b[i * 8 + j]。從二維、三維陣列的轉化過程中可以發現一些規律。

T a[M][N] 	 --> T b[M * N],  	 a[i][j]    --> b[i * N + j]
T a[M][N][O] --> T b[M * N * O], b[i][j][k] --> b[i * N * O + j * O + k]

編譯器進行下標轉換時,並沒有用到第 0 維的大小,而其它維的大小都是必須的。這也就是為什麼下面程式碼能正確執行。

int a[2][3];
int (*p)[3] = a;

由於多維陣列本質上是一維陣列,所以釋放記憶體都是 delete[] p,而沒有奇怪的 delete[][] 語法。

2. 藉助指標陣列

還有一種方法就是先 new T*[M] 建立一個指標陣列,其每個元素儲存每一行的首個元素的地址,再使用 new T[N] 建立每一行。示例程式碼如下:

A** p = new A*[2];
for (int i = 0; i < 2; ++i) {
    p[i] = new A[3];
}

for (int i = 0; i < 2; ++i) {
    delete[] p[i];
}
delete[] p;

這種方法非常繁瑣,首先 new T*[M] 不能寫成 new (T(*)[M]),因為它是指標陣列而不是陣列指標。其次,需要對每一行呼叫 new T[N]。釋放記憶體時,要先使用 delete[] 釋放每一行,再呼叫 delete[] 釋放陣列指標。這幾個步驟一步都不能錯,不然就出現野指標或者記憶體洩漏。這段程式碼我也是用 Address Sanitizer 和 Leak Sanitizer 檢查一遍才寫對。

這種方法唯一的好處就是可以建立交錯陣列(Jagged Array),也就是每一行的大小不一樣。例如:

A **p = new A *[2];
p[0] = new A[3];
p[1] = new A[4];

for (int i = 0; i < 2; ++i)
{
    delete[] p[i];
}
delete[] p;

3. 藉助 std::vector

可以用 std::vector 對上面這種方法進行包裝,使其更加易用。示例程式碼如下:

std::vector<std::vector<int>> v{ std::vector<int>(3), std::vector<int>(4) };
std::cout << v[0].size() << " " << v[1].size() << std::endl;

這段程式碼建立了一個二維陣列,第 0 行有 3 個元素,第 1 行有 4 個元素。這種方法既能建立交錯陣列,也不需要手動釋放記憶體。

相關文章