目錄
什么是二維數組?
二維數組的聲明方式
方式 1:靜態二維數組?
方式 2:數組指針數組(數組中存放的是指針)
方式 3:雙指針 + 二級堆分配
💡 補充建議
如何用“第一性原理”去推導出 C++ 中二維數組的三種聲明方式?
第一階段:內存連續,列固定,行固定 → 推導出方式①
第二階段:每行獨立、列可能不同(不規則矩陣) → 推導出方式②
第三階段:行數和列數都是運行時才知道的 → 推導出方式③
什么是二維數組?
二維數組本質上是“數組的數組”,你可以將它看作一個矩陣,每個元素有兩個索引:
int A[3][4];
表示一個包含 3 行 4 列 的整數矩陣。
訪問方式是 A[row][col]
,例如 A[1][2]
表示第 2 行第 3 列。
二維數組的聲明方式
我們來逐一講解,并圖示結構 + 內存布局圖解:
方式 1:靜態二維數組?
int A[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10,11,12}
};
結構理解:
這是一個連續內存塊,編譯器分配的實際大小為 3 * 4 * sizeof(int)
,總共 12 個整型元素。
圖示結構:?
A: → 3 行 × 4 列的矩陣(行優先排列)[1, 2, 3, 4][5, 6, 7, 8][9,10,11,12]
?💾 內存模型(連續存儲):
+----+----+----+----+----+----+----+----+----+----+----+----+ ← 低地址
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
+----+----+----+----+----+----+----+----+----+----+----+----+ ← 高地址
按行順序(row-major)存儲:第一行 → 第二行 → 第三行
特點:
屬性 | 說明 |
---|---|
內存連續 | 是 |
高效訪問 | 是 |
易傳給函數 | 是(需行數、列數信息) |
可初始化所有值 | 是 |
靈活性(行數/列數變動) | 否 不可動態變長 |
-
簡單高效
-
編譯器在編譯時就知道每個元素地址的偏移
-
適用于尺寸固定的表格/矩陣處理
優勢體現:性能最佳(連續內存 + 快速索引)
📌 示例:加速矩陣乘法(靠緩存命中率)
int A[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int B[4][2] = {{1,2}, {1,2}, {1,2}, {1,2}};
int C[3][2] = {0};for (int i = 0; i < 3; ++i)for (int j = 0; j < 2; ++j)for (int k = 0; k < 4; ++k)C[i][j] += A[i][k] * B[k][j];
優勢體現點:
-
編譯器知道
A[i][k]
和B[k][j]
在內存中的確切偏移 -
數據連續,CPU 緩存命中率高
-
運行速度遠優于動態結構(如 vector<vector<int>>)
方式 2:數組指針數組(數組中存放的是指針)
int* A[3]; // 數組中存放3個 int* 指針
A[0] = new int[4];
A[1] = new int[4];
A[2] = new int[4];
結構理解:
-
A
是一個大小為 3 的指針數組:A[0]
,A[1]
,A[2]
-
每個
A[i]
是一個指向 長度為 4 的數組 的指針
圖示結構:
A: → [指針] [指針] [指針]↓ ↓ ↓[1,2,3,4] [5,6,7,8] [9,10,11,12]
?💾 內存模型(不連續):
棧:
+-----+-----+-----+ ← A[0], A[1], A[2] 是在棧區的指針變量
| ptr | ptr | ptr |
+-----+-----+-----+↓ ↓ ↓
堆:[1][2][3][4] // A[0] 指向此處[5][6][7][8] // A[1][9][10][11][12] // A[2]
特點:
屬性 | 說明 |
---|---|
內存連續? | ?否? ?每一行在堆中獨立分配 |
指針存儲在哪里? | ?是? ?指針數組本身在棧 |
靈活性? | ?是? ?可以變行數或列數 |
缺點 | ?否? 不利于整體拷貝、效率較低、復雜性高 |
-
比方式①更靈活
-
每一行都可以單獨分配、單獨修改、單獨釋放
-
行長度可以不同(“不規則二維數組”)
優勢體現:行長不一致的表格處理
📌 示例:一個表格,有 3 行,每行元素個數不同
int* A[3];
A[0] = new int[2]{1,2}; // 第一行只有2個元素
A[1] = new int[3]{3,4,5}; // 第二行3個
A[2] = new int[4]{6,7,8,9}; // 第三行4個for (int i = 0; i < 2; ++i) cout << A[0][i] << " ";
for (int i = 0; i < 3; ++i) cout << A[1][i] << " ";
for (int i = 0; i < 4; ++i) cout << A[2][i] << " ";delete[] A[0]; delete[] A[1]; delete[] A[2];
優勢體現點:
-
不規則矩陣結構(每行不一樣)
-
適合表示數據表格(例如 JSON 表、數據庫行)
方式 3:雙指針 + 二級堆分配
int** A;
A = new int*[3]; // 分配3個指針
A[0] = new int[4];
A[1] = new int[4];
A[2] = new int[4];
結構理解:
-
A
是一個指向指針的指針:int**
-
它指向一個動態分配的指針數組
-
每個指針再指向各自的行數組
圖示結構:
堆1(A):
+-----+-----+-----+
| ptr | ptr | ptr | ← A[0], A[1], A[2]
+-----+-----+-----+↓ ↓ ↓
堆2:[1,2,3,4] [5,6,7,8] [9,10,11,12]
?💾 內存模型(全堆 + 不連續):
棧:
+----------------+ ← A(int** 類型,指向堆中指針數組)
| 指向堆的指針地址 |
+----------------+堆(第一次 new):
+-----+-----+-----+
| ptr | ptr | ptr | ← 存放 A[0], A[1], A[2]
+-----+-----+-----+堆(3次 new):
[1,2,3,4] ← A[0]
[5,6,7,8] ← A[1]
[9,10,11,12] ← A[2]
特點:
屬性 | 說明 |
---|---|
全部在堆中? | (適合大型數據) |
是否連續? | ?不連續,多次分配 |
靈活性? | ?最高,可以動態控制行/列數 |
管理復雜度? | ?需手動 delete 兩層,容易泄漏 |
-
最具通用性
-
完全動態二維數組
-
行數、列數在運行時可變,甚至可以動態增刪行列
優勢體現:完全運行時控制二維矩陣大小
📌 示例:用戶輸入決定行列數
int rows, cols;
cin >> rows >> cols;int** A = new int*[rows];
for (int i = 0; i < rows; ++i)A[i] = new int[cols]; // 每行動態開辟// 初始化矩陣
for (int i = 0; i < rows; ++i)for (int j = 0; j < cols; ++j)A[i][j] = i * cols + j;// 打印矩陣
for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j)cout << A[i][j] << " ";cout << endl;
}// 釋放內存
for (int i = 0; i < rows; ++i) delete[] A[i];
delete[] A;
優勢體現點:
-
非常適合處理用戶輸入、數據文件、圖結構(鄰接表)
-
最接近“二維 vector”的行為
💡 補充建議
-
如果你只需要 固定結構,首選
int A[3][4]
-
如果你需要 運行時決定行列,用
int** A
或vector<vector<int>>
-
如果你想用更現代 C++ 寫法:推薦使用
std::vector
來替代動態數組
如何用“第一性原理”去推導出 C++ 中二維數組的三種聲明方式?
基礎事實(不依賴高層語法):
-
內存是線性地址空間(一條一維線)
-
數組是連續的同類型內存塊
-
指針是變量的地址
-
一個數組可以存放指針
-
new / malloc 可以在運行時分配任意大小的內存
-
C++ 編譯器可以通過
arr[i][j]
翻譯成偏移運算:*( *(arr + i) + j )
🔍 我的問題目標:我想建一個二維表格
輸入需求:
-
我希望能訪問
A[i][j]
-
這個結構要能容納多行多列的數據
第一階段:內存連續,列固定,行固定 → 推導出方式①
?設計需求:
-
所有元素個數是 M × N
-
所有行列大小都是固定的
-
適合做 數值型矩陣處理、圖像、表格
🤔 思考過程:
? 我能不能一次申請一個大數組,把所有數據按行拼在一起?
當然可以,我做個拍扁的線性數組:
int data[M * N];
然后我寫一個訪問函數:
int get(int i, int j) {return data[i * N + j];
}
👉 很方便,但語法不直觀。我希望寫 A[i][j]
而不是 data[i * N + j]
于是我想:
int A[M][N];
這不就是把 M*N
連續空間組織成二維格式了嗎?
A[i][j]
被編譯器翻譯為:
*(A + i*N + j) = *( *(A + i) + j )
這就推導出了:
?方式①:int A[3][4]
第二階段:每行獨立、列可能不同(不規則矩陣) → 推導出方式②
新設計需求:
-
每行的長度不同
-
不想為每一行都分配一樣多空間(內存浪費)
-
比如:
Row 0: 3 elements
Row 1: 5 elements
Row 2: 2 elements
思考過程:
? 我能否為每一行單獨分配一個一維數組?
當然可以,我可以寫:
int* row0 = new int[3];
int* row1 = new int[5];
int* row2 = new int[2];
那我怎么管理這些行?
💡 我想到了:把這些指針放進一個數組中!
int* A[3]; // A[0], A[1], A[2] 分別指向三行
A[0] = row0;
A[1] = row1;
A[2] = row2;
現在我就可以訪問 A[i][j]
了:
-
A[i]
是一行指針 -
A[i][j]
是訪問這一行中的元素
這就推導出了:
?方式②:int* A[3];
第三階段:行數和列數都是運行時才知道的 → 推導出方式③
?新設計需求:
-
用戶在運行時告訴我有多少行和列
-
比如:
cin >> rows >> cols;
? 我該怎么在運行時分配二維表格呢?
我不能用 int A[rows][cols]
,因為在 C++ 中數組大小必須是編譯時常量(除 VLA 編譯器擴展)
🤔 思考過程:
第一步:
我要創建一個 行指針數組:
int** A = new int*[rows];
A
就是一個“二維數組的外殼”:A[i]
將是每一行的指針
第二步:
為每一行動態創建列空間:
for (int i = 0; i < rows; ++i)A[i] = new int[cols];
這樣就構成了一個“指針的指針”:二維結構。
得到:
?方式③:int** A; A = new int*[rows]; A[i] = new int[cols];
總結:需求驅動 + 構造能力 → 推導出三種方式
場景/需求 | 你能用的構造 | 推導出的結構 | 實際語法 |
---|---|---|---|
數據量固定、行列固定 | 數組 | 連續二維數組 | int A[M][N] |
行數固定、列數可變 | 數組 + 指針 | 指針數組 + 行數組 | int* A[M]; |
行列都可變 | 雙層指針 + 堆分配 | 二級動態數組 | int** A; |
目標:二維數據結構↓
選擇1:一維內存線? → 折疊二維到一維 → A[i][j] ≈ A[i*N + j] → ? int A[M][N]↓
選擇2:能不能每行分開存? → 每行是 int*,用指針數組存行 → ? int* A[M]↓
選擇3:連行數都未知? → 指針數組也要動態分配 → ? int** A; A = new int*[M]
?