1. 前言
C 語言是程式語言中非常重要且具有歷史地位的一種。其中的「陣列」是管理與操作資料時不可或缺的基本功能。尤其是「二維陣列」,在處理矩陣或表格型資料時特別方便。
本文將以淺顯易懂的方式介紹 C 語言的二維陣列,從基本用法到進階應用逐步說明,提供讀者在實際撰寫程式時能派上用場的資訊。
C 語言中陣列的重要性
C 語言的陣列是一種資料結構,可以一次性管理多個相同型別的資料。透過陣列,不需要一個一個宣告變數,讓程式碼更加簡潔。舉例來說,當需要存放多個數值或字串時,使用陣列能更有效率地操作。
特別是「二維陣列」,在以下情境中非常有用:
- 數學矩陣的計算
- 遊戲棋盤(例如西洋棋、黑白棋)的管理
- 處理資料表或試算表等結構
本文能學到什麼
本文將依序學習以下內容:
- 二維陣列的基本結構與宣告方式
- 初始化與存取元素的方法
- 二維陣列在記憶體中的配置
- 實際程式中的應用範例
- 如何動態配置與釋放二維陣列
- 使用陣列時的注意事項與常見錯誤
即使是初學者,也能透過本文掌握二維陣列的基本知識,並具備實際應用於程式開發的能力。
適合的讀者對象
這篇解說針對剛開始學習 C 語言的初學者,以及想擴展應用範圍的中階學習者。特別適合以下族群:
- 已經理解一維陣列,但還沒使用過二維陣列的人
- 想在程式中處理表格型資料的人
- 想複習 C 語言基本操作的人
2. 什麼是二維陣列
C 語言中的「二維陣列」是一種能以「行」和「列」的形式存放資料的結構,本質上是「陣列的陣列」。它在矩陣運算或表格資料處理上非常實用,因此在程式設計中經常被使用。
以下將詳細解釋二維陣列的基本概念與結構。
二維陣列的基本結構
二維陣列由多個「行」和「列」組成。具體來說,每一行都是一維陣列,而將這些一維陣列組合起來,就構成了二維陣列。
範例:二維陣列的概念圖
以下的程式碼示範如何利用行與列來存放資料:
array[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
在這個例子中,array
是一個 3 行 4 列的二維陣列,可以用索引存取特定元素。
array[0][0]
= 1array[2][3]
= 12
二維陣列的用途
二維陣列可應用於下列情境:
- 數學矩陣運算
例如:矩陣加法或乘法 - 表格型資料管理
例如:試算表或資料庫結構 - 遊戲開發
例如:管理西洋棋盤或黑白棋盤的狀態 - 影像處理
例如:存放像素顏色資料
由此可見,二維陣列是管理複雜資料時非常重要的基礎結構。
二維陣列與一維陣列的差異
一維陣列的特點
一維陣列以「線性」方式存放資料。
int array[5] = {1, 2, 3, 4, 5};
此時,透過索引即可依序參照資料:
array[0]
= 1array[4]
= 5
二維陣列的特點
二維陣列同時使用「行」和「列」兩個維度來存放資料。
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
此時,必須指定行與列來參照資料:
array[0][2]
= 3array[1][0]
= 4
因此,二維陣列在處理複雜資料結構時特別有用。
3. 二維陣列的宣告與初始化
在 C 語言中,使用二維陣列前需要先宣告,並可在必要時進行初始化。以下將介紹二維陣列的宣告方式與不同的初始化方法。
二維陣列的宣告方式
宣告格式如下:
型別 陣列名稱[行數][列數];
- 型別:存放資料的型別(如
int
、float
、char
)。 - 行數與列數:整數值,指定陣列大小。
宣告範例
以下為宣告一個 3 行 4 列 int
型別陣列的範例:
int array[3][4];
這樣會配置一個能存放 3 行 4 列資料的記憶體區域。
4. 二維陣列的使用方法:元素存取與操作
在 C 語言中,二維陣列的每個元素都可以被存取或修改。以下將說明元素的基本存取方式與操作方法。
元素存取
在二維陣列中,必須指定「行號」和「列號」來存取特定元素。
基本存取方式
array[行號][列號]
例如,假設有以下陣列:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
array[0][0]
→ 1(第 1 行第 1 列)array[1][2]
→ 6(第 2 行第 3 列)
範例:輸出元素的程式
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printf("array[0][0] = %d\n", array[0][0]);
printf("array[1][2] = %d\n", array[1][2]);
return 0;
}
輸出結果
array[0][0] = 1
array[1][2] = 6
元素操作
可以透過指定索引,對陣列中的元素重新賦值。
修改值的方法
array[行號][列號] = 新值;
範例:修改元素值
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 修改元素值
array[0][0] = 10;
array[1][2] = 20;
// 輸出結果
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("array[%d][%d] = %d\n", i, j, array[i][j]);
}
}
return 0;
}
輸出結果
array[0][0] = 10
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 20
二維陣列的迴圈操作
在操作二維陣列時,最常見的方式是使用巢狀迴圈。
範例:用迴圈逐一處理行與列
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 輸出所有元素
for (int i = 0; i < 2; i++) { // 行迴圈
for (int j = 0; j < 3; j++) { // 列迴圈
printf("array[%d][%d] = %d\n", i, j, array[i][j]);
}
}
return 0;
}
輸出結果
array[0][0] = 1
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 6
應用範例:將所有元素設定為特定值
可以使用迴圈將二維陣列中的所有元素設定為相同的值。
範例:將所有元素設為 5
#include <stdio.h>
int main() {
int array[3][3];
// 將所有元素設為 5
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
array[i][j] = 5;
}
}
// 輸出結果
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("array[%d][%d] = %d\n", i, j, array[i][j]);
}
}
return 0;
}
輸出結果
array[0][0] = 5
array[0][1] = 5
array[0][2] = 5
array[1][0] = 5
array[1][1] = 5
array[1][2] = 5
array[2][0] = 5
array[2][1] = 5
array[2][2] = 5
5. 二維陣列的記憶體結構與理解
在 C 語言中,理解二維陣列在記憶體中的存放方式非常重要。掌握這部分知識能提升程式效率,並讓使用指標操作時更加靈活。
以下將深入解釋二維陣列的記憶體配置。
二維陣列的記憶體配置
C 語言的二維陣列實際上是以一維的連續記憶體空間來存放,這種配置方式稱為列優先(row-major order)。
什麼是列優先(row-major)
所謂列優先,是指陣列的資料按照一行接著一行的方式依序存放在記憶體中。
範例:陣列的記憶體配置
以以下二維陣列為例:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
它在記憶體中的實際存放方式為:
記憶體配置: 1 2 3 4 5 6
array[0][0]
→ 記憶體的第 1 個位置array[0][1]
→ 記憶體的第 2 個位置array[1][0]
→ 記憶體的第 4 個位置
使用索引參照元素
在 C 語言中,使用索引參照元素的運算方式如下:
array[i][j] = *(array + (i * 列數) + j)
範例:記憶體位址的計算
以上述 array[2][3]
為例:
array[1][2]
的位址計算方式為:
*(array + (1 * 3) + 2) = *(array + 5)
因為採用列優先配置,所以第 2 行(i = 1
)會先跳過 3 個元素,再加上第 3 列(j = 2
),最終指向第 5 個位置。
利用指標操作二維陣列
C 語言的二維陣列也可以透過指標來操作,這能提升程式的彈性。
指標與二維陣列的關係
由於二維陣列是「陣列的陣列」,因此可以利用指標存取:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int *ptr = &array[0][0];
printf("%d\n", *(ptr + 4)); // 輸出: 5
在這個例子中,指標 ptr
指向陣列的第一個元素,透過位移即可存取其他元素。
記憶體配置的視覺化說明
以下圖示展示 array[2][3]
在記憶體中的配置:
記憶體: 1 2 3 4 5 6
索引:
[0][0] [0][1] [0][2] [1][0] [1][1] [1][2]
由此可見,二維陣列在記憶體中是以連續方式存放的。
提升效率的操作要點
為了更有效率地操作二維陣列,可以注意以下兩點:
- 以列優先方式存取
當透過迴圈操作時,固定行、遍歷列的方式最有效率。
for (int i = 0; i < 行數; i++) {
for (int j = 0; j < 列數; j++) {
// 列優先存取
}
}
- 善用指標
透過指標能減少索引運算,降低計算成本。
6. 實用範例:矩陣運算與遊戲棋盤建立
二維陣列在實際程式中有廣泛的應用,例如數學中的矩陣運算,以及遊戲中的棋盤狀態管理。以下將以兩個具體範例「矩陣運算」與「遊戲棋盤建立」進行說明。
矩陣運算範例
矩陣運算在數學與工程領域中經常出現,利用二維陣列可以輕鬆完成矩陣的加法與乘法。
範例1:矩陣加法
以下程式示範如何進行矩陣的加法:
#include <stdio.h>
int main() {
// 兩個 3x3 矩陣
int matrix1[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int matrix2[3][3] = {
{9, 8, 7},
{6, 5, 4},
{3, 2, 1}
};
int result[3][3];
// 矩陣加法
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
result[i][j] = matrix1[i][j] + matrix2[i][j];
}
}
// 輸出結果
printf("矩陣加法結果:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", result[i][j]);
}
printf("\n");
}
return 0;
}
輸出結果
矩陣加法結果:
10 10 10
10 10 10
10 10 10
範例2:矩陣乘法
矩陣乘法的實作範例如下:
#include <stdio.h>
int main() {
// 兩個 3x3 矩陣
int matrix1[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int matrix2[3][3] = {
{9, 8, 7},
{6, 5, 4},
{3, 2, 1}
};
int result[3][3] = {0};
// 矩陣乘法
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
result[i][j] += matrix1[i][k] * matrix2[k][j];
}
}
}
// 輸出結果
printf("矩陣乘法結果:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", result[i][j]);
}
printf("\n");
}
return 0;
}
輸出結果
矩陣乘法結果:
30 24 18
84 69 54
138 114 90
遊戲棋盤建立範例
二維陣列在遊戲開發中常用來管理棋盤狀態。以下示範簡單的黑白棋(Othello)棋盤初始化:
範例:黑白棋盤的初始化與輸出
#include <stdio.h>
int main() {
// 初始化 8x8 棋盤
int board[8][8] = {0};
// 設定初始狀態
board[3][3] = 1; // 白子
board[3][4] = 2; // 黑子
board[4][3] = 2; // 黑子
board[4][4] = 1; // 白子
// 輸出棋盤
printf("黑白棋盤初始狀態:\n");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
printf("%d ", board[i][j]);
}
printf("\n");
}
return 0;
}
輸出結果
黑白棋盤初始狀態:
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 1 2 0 0 0
0 0 0 2 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
在這個程式中,0 表示空格,1 表示白子,2 表示黑子,並將棋盤的初始狀態輸出至主控台。
7. 二維陣列與指標的關係
在 C 語言中,二維陣列與指標有著密切的關係。透過指標,可以更有效率地操作二維陣列。以下將從基本概念到實際應用範例進行解說。
二維陣列與指標的基本關係
二維陣列本質上是「陣列的陣列」。因此,每一行都可以視為一個陣列,而這些陣列本身則可以透過指標來存取。
範例:二維陣列的基本結構
以下為二維陣列的宣告方式:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
在記憶體中的存放狀態如下:
[1] [2] [3] [4] [5] [6]
array
→ 指向陣列首位元素的指標array[i]
→ 指向第 i 行的指標array[i][j]
→ 指向具體的元素
使用指標參照元素
利用指標運算,可以透過以下方式存取元素:
*(array[0] + 1) // 等同於 array[0][1]
*(*(array + 1) + 2) // 等同於 array[1][2]
以指標傳遞二維陣列給函式
當需要將二維陣列傳遞給函式時,可以透過指標來處理。以下為範例:
範例:在函式中操作二維陣列
#include <stdio.h>
// 函式定義
void printArray(int (*array)[3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
}
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 將陣列傳遞給函式
printArray(array, 2);
return 0;
}
輸出結果
1 2 3
4 5 6
重點
int (*array)[3]
表示指向「列數為 3 的陣列」的指標。- 在函式中可透過行與列存取元素。
使用指標建立動態二維陣列
透過指標可以在程式執行時動態建立二維陣列,提升彈性。
範例:動態建立二維陣列
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 2, cols = 3;
// 動態配置記憶體
int** array = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
// 賦值
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j] = i * cols + j + 1;
}
}
// 輸出
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
// 釋放記憶體
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
return 0;
}
輸出結果
1 2 3
4 5 6
8. 動態配置與釋放二維陣列的方法
在 C 語言中,可以透過動態記憶體配置來在程式執行時建立需要大小的二維陣列。這樣可以更有效率地利用記憶體,並解決固定大小陣列的限制。以下將詳細介紹如何動態配置、操作與釋放二維陣列。
動態記憶體配置的基本概念
常用的函式有 malloc
與 calloc
。使用這些函式,可以在程式執行時依需求決定陣列的大小。
動態建立二維陣列的方法
主要有兩種方式:
- 使用指標陣列:每一行分別配置記憶體。
- 使用一維連續陣列模擬二維:一次性配置整塊記憶體,再透過索引運算進行二維存取。
方法1:使用指標陣列
此方式會先建立指標陣列,再為每一行配置記憶體。
步驟
- 建立一個行數大小的指標陣列。
- 逐一為每一行配置列數大小的記憶體。
範例:動態建立二維陣列
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// 建立指標陣列
int** array = malloc(rows * sizeof(int*));
// 為每一行配置記憶體
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
// 賦值
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j] = i * cols + j + 1;
}
}
// 輸出內容
printf("動態配置的二維陣列:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
// 釋放記憶體
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
return 0;
}
輸出結果
動態配置的二維陣列:
1 2 3 4
5 6 7 8
9 10 11 12
方法2:使用一維連續陣列模擬二維
這種方式會一次性配置「行數 × 列數」大小的記憶體,再透過索引運算來模擬二維結構。
步驟
- 一次性配置足夠大小的記憶體。
- 透過
i * 列數 + j
的方式存取元素。
範例:使用一維陣列建立二維結構
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// 一次性配置記憶體
int* array = malloc(rows * cols * sizeof(int));
// 賦值
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i * cols + j] = i * cols + j + 1;
}
}
// 輸出內容
printf("使用一維陣列模擬二維:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i * cols + j]);
}
printf("\n");
}
// 釋放記憶體
free(array);
return 0;
}
輸出結果
使用一維陣列模擬二維:
1 2 3 4
5 6 7 8
9 10 11 12
動態記憶體配置的注意事項
- 避免記憶體洩漏
動態配置的記憶體必須使用free
釋放,否則會導致記憶體洩漏。 - 檢查配置是否成功
使用malloc
或calloc
時,必須檢查是否回傳NULL
。
if (array == NULL) {
printf("記憶體配置失敗。\n");
return 1;
}
- 正確計算大小
必須正確計算所需大小,確保配置的記憶體足夠。
9. 使用二維陣列時的注意事項
二維陣列雖然方便又強大,但若使用不當,可能導致程式錯誤或記憶體洩漏等問題。以下整理了常見的注意事項與錯誤案例。
避免越界存取
在使用二維陣列時,若存取超過陣列邊界,可能造成不可預期的行為甚至程式崩潰。
範例:越界存取問題
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 錯誤:超出邊界
printf("%d\n", array[2][0]); // 嘗試存取不存在的行
return 0;
}
上述程式嘗試存取 array[2][0]
,但實際上只存在到第 2 行(索引 0 與 1),因此會發生未定義行為。
解決方法
- 正確設定迴圈條件,避免超過行數與列數。
- 必要時可加入邊界檢查。
修正版範例
for (int i = 0; i < 2; i++) { // 僅迴圈至有效行數
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
}
避免記憶體洩漏
若使用動態配置建立二維陣列,忘記釋放記憶體就會造成記憶體洩漏。
錯誤範例:記憶體洩漏
#include <stdlib.h>
int main() {
int rows = 2, cols = 3;
int** array = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
// 錯誤:僅釋放外層指標
free(array);
return 0;
}
這段程式雖然釋放了外層指標 array
,但每一行配置的記憶體並沒有釋放,導致記憶體洩漏。
正確做法
應先釋放每一行,最後再釋放指標本身:
for (int i = 0; i < rows; i++) {
free(array[i]); // 釋放每一行
}
free(array); // 最後釋放外層指標
靜態陣列無法改變大小
在 C 語言中,靜態宣告的陣列大小一旦定義後就無法改變。若需要動態調整大小,必須使用動態配置。
解決方法
- 在需要可變大小時使用動態陣列。
- 若需要頻繁改變大小,可使用
realloc
重新配置記憶體。
避免使用未初始化的陣列
若忘記初始化,二維陣列可能含有隨機值,導致預期外結果。
錯誤範例:未初始化
int array[2][3];
printf("%d\n", array[0][0]); // 輸出不確定的垃圾值
正確做法
- 宣告時直接初始化:
int array[2][3] = {0}; // 全部設為 0
- 若使用動態配置,則建議使用
calloc
初始化為 0:
int* array = calloc(rows * cols, sizeof(int));
記憶體效率與快取效能
為了更有效率地操作二維陣列,應按照列優先順序存取,這樣能充分利用快取效能。
列優先存取範例
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
}
欄優先存取的低效率
若以欄為主進行存取,會降低快取效率,導致程式效能下降。
常見錯誤檢查清單
以下為使用二維陣列時常見錯誤的檢查清單:
- 是否存取了超出邊界的元素?
- 是否完整釋放了所有動態配置的記憶體?
- 是否在使用前已初始化陣列?
- 若需要改變大小,是否正確重新配置了記憶體?
- 是否以列優先方式存取來提升效率?
10. 總結
本文以循序漸進的方式,從基礎到應用,完整介紹了 C 語言中的二維陣列。二維陣列是一種能廣泛應用於矩陣運算、資料管理、遊戲棋盤設計等場景的便利資料結構。以下將重點內容再次整理。
1. 二維陣列的基本結構
- 二維陣列由「行」與「列」構成。
- 宣告方式如下:
int array[行數][列數];
- 存取元素時需使用索引:
array[行號][列號];
2. 初始化與元素操作
- 初始化方式多樣:
- 明確指定:
int array[2][3] = { {1, 2, 3}, {4, 5, 6} };
- 全部歸零:
int array[2][3] = {0};
- 可透過迴圈有效率地操作元素。
3. 記憶體結構與指標關係
- 二維陣列在記憶體中以列優先(row-major)方式存放。
- 可使用指標進行操作,例如:
*(*(array + i) + j);
- 傳遞二維陣列至函式時,必須指定列數:
void printArray(int (*array)[列數], int 行數);
4. 動態建立與釋放二維陣列
- 使用動態記憶體配置可以在執行時決定大小。
- 使用指標陣列的方式:
int** array = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
- 釋放記憶體時必須逐一釋放每行,最後再釋放外層指標:
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
5. 注意事項
- 避免越界存取。
- 不要使用未初始化的陣列。
- 動態配置的記憶體必須確實釋放。
- 操作時盡量遵循列優先存取以提升快取效能。
二維陣列的應用價值
二維陣列在以下場景中特別有用:
- 矩陣運算:有效率地處理數學運算。
- 遊戲開發:管理棋盤(如西洋棋、黑白棋)。
- 資料處理:表格、試算表等結構化資料。
- 影像處理:管理像素資料。
下一步學習建議
理解二維陣列後,C 語言程式設計能力將大幅提升。建議下一步可深入以下主題:
- 多維陣列:學習 3 維或更多維度的陣列。
- 指標的進階應用:進一步理解陣列與指標的靈活操作。
- 記憶體管理:熟悉
realloc
與更高階的記憶體操作技巧。
最後
透過本文,您已學會二維陣列的基礎與應用。請多加練習,並靈活運用這些技巧來撰寫更高效、更強大的程式。C 語言的學習重點在於持續實作與驗證,若有疑問,建議查閱官方文件或進一步學習資源。