陣列與指標
如同城市規劃師掌握土地配置與交通動線,透過陣列與指標精準控制記憶體空間與資料流向,打造高效運作的程式系統。
目標
1. 陣列的基本概念
1.1 什麼是陣列?
- 陣列是一組相同資料類型的變數集合
- 類似宿舍的置物櫃,每個櫃子編號從0開始
- 宣告語法:
範例:資料類型 陣列名稱[元素數量];
int scores[5]; // 可儲存5個整數 float temps[24]; // 可儲存24個浮點數
1.2 陣列初始化
- 靜態初始化:宣告時直接賦值
int primes[] = {2, 3, 5, 7, 11}; // 自動推算長度 char vowels[5] = {'a', 'e', 'i', 'o', 'u'};
- 動態初始化:後續賦值
scores[0] = 90; // 第一個元素 scores[1] = 85; // 第二個元素
2. 陣列與記憶體
2.1 記憶體連續性
- 陣列元素在記憶體中是連續儲存
- 可用&運算子查看位址:
輸出範例:printf("primes[0]位址: %p\n", &primes[0]); printf("primes[1]位址: %p\n", &primes[1]);
primes[0]位址: 0x7ffd4a3b2f10 primes[1]位址: 0x7ffd4a3b2f14 // 相差4 bytes(int大小)
2.2 sizeof運算子
- 計算陣列總記憶體佔用:
printf("primes陣列大小: %zu bytes\n", sizeof(primes));
- 計算單個元素大小:
printf("每個元素大小: %zu bytes\n", sizeof(primes[0]));
3. 陣列實作練習
3.1 基本操作
// 計算陣列總和
int numbers[] = {10, 20, 30, 40, 50};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += numbers[i];
}
printf("總和: %d\n", sum);
4. 指標的基本概念
4.1 什麼是指標?
- 指標是儲存記憶體位址的變數
- 透過指標可以直接操作記憶體中的資料
- 宣告語法:
範例:資料類型* 指標變數名稱;
int* ptr; // 指向整數的指標 float* fptr; // 指向浮點數的指標
4.2 指標運算子
- & (取址運算子):取得變數的記憶體位址
int num = 10; int* ptr = # // ptr現在儲存num的位址
- ***** (取值運算子):取得指標指向的值
printf("num的值: %d\n", *ptr); // 輸出10
5. 指標與陣列的關係
5.1 陣列名稱的本質
- 陣列名稱本身就是指向第一個元素的指標
int arr[3] = {10, 20, 30}; printf("%p == %p\n", arr, &arr[0]); // 兩者相同
- 可以用指標方式存取陣列元素:
printf("arr[1]: %d\n", *(arr + 1)); // 輸出20
5.2 指標算術運算
- 指標加減整數會根據資料類型自動調整位址
int* ptr = arr; printf("ptr: %p\n", ptr); // 指向arr[0] printf("ptr+1: %p\n", ptr+1); // 指向arr[1]
6. 陣列作為函數參數
6.1 傳遞陣列給函數
- 函數參數可以使用指標或陣列形式宣告
// 兩種等價的宣告方式 void printArray(int* arr, int size); void printArray(int arr[], int size);
- 實際呼叫範例:
void printArray(int* arr, int size) { for(int i=0; i<size; i++) { printf("%d ", arr[i]); } } int main() { int nums[] = {1, 2, 3, 4, 5}; printArray(nums, 5); // 傳入陣列名稱和大小 }
6.2 修改陣列內容
- 函數內透過指標可直接修改原始陣列:
void squareArray(int* arr, int size) { for(int i=0; i<size; i++) { arr[i] = arr[i] * arr[i]; } }
7. 綜合實作練習
7.1 指標操作練習
int nums[] = {10, 20, 30, 40, 50};
int* ptr = nums;
// 請試試看以下運算結果:
printf("%d\n", *ptr); // 輸出?
printf("%d\n", *(ptr+2)); // 輸出?
printf("%d\n", ptr[3]); // 輸出?
9. 動態記憶體管理
9.1 靜態 vs 動態分配
- 靜態分配:編譯時決定大小(傳統陣列)
int arr[100]; // 固定大小
- 動態分配:執行時決定大小
int* arr = malloc(100 * sizeof(int)); // 可調整大小
9.2 記憶體管理函數
malloc
:分配未初始化記憶體int* nums = malloc(5 * sizeof(int)); // 分配5個int空間
calloc
:分配並初始化為0int* zeros = calloc(5, sizeof(int)); // 5個int全部設0
free
:釋放記憶體(重要!)free(nums); // 釋放記憶體 nums = NULL; // 避免懸吊指標
10. 動態陣列實作
10.1 建立動態陣列
int size;
printf("輸入陣列大小: ");
scanf("%d", &size);
int* dynArr = malloc(size * sizeof(int));
for(int i=0; i<size; i++) {
dynArr[i] = i*10; // 初始化值
}
10.2 調整陣列大小
int newSize = size * 2;
int* temp = realloc(dynArr, newSize * sizeof(int));
if(temp != NULL) {
dynArr = temp; // 擴容成功
for(int i=size; i<newSize; i++) {
dynArr[i] = 0; // 初始化新增空間
}
}
11.6 指標作為函數參數的簡單應用
11.6.1 基本概念(與scanf類比)
// scanf 就是最典型的指標參數應用
int num;
scanf("%d", &num); // 傳遞num的地址讓scanf能修改它的值
// 同理,我們可以自訂這樣的函數
void set_value(int* ptr) {
*ptr = 100; // 透過指標修改外部變數
}
int main() {
int a = 0;
set_value(&a); // 類似scanf的用法
printf("%d", a); // 輸出100
}
11.6.2 陣列修改函數
// 像scanf修改變數一樣修改陣列元素
void set_array_element(int* arr, int index) {
printf("請輸入新值: ");
scanf("%d", &arr[index]); // 類似scanf(&變數)的用法
}
int main() {
int nums[5] = {0};
set_array_element(nums, 2); // 修改nums[2]
printf("新值: %d", nums[2]);
}
11.6.3 簡易交換函數
// 像scanf需要&一樣,交換函數也需要指標參數
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 3, y = 5;
swap(&x, &y); // 傳地址才能修改原始變數
printf("x=%d, y=%d", x, y); // 輸出x=5, y=3
}
11.7 重點提示
- 記住
scanf
需要&
的原理 - 當函數需要修改原始變數時,就要傳指標
- 陣列名稱本身就是指標,所以不需要
&
- 這種用法讓函數能真正修改外部變數,而不只是複本
12. 多維陣列實作
12.1 靜態二維陣列
// 宣告3x4的整數矩陣
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 存取元素
printf("%d", matrix[1][2]); // 輸出7
12.2 動態二維陣列
int rows = 3, cols = 4;
int** matrix = malloc(rows * sizeof(int*));
for(int i=0; i<rows; i++) {
matrix[i] = malloc(cols * sizeof(int));
}
// 初始化
for(int i=0; i<rows; i++) {
for(int j=0; j<cols; j++) {
matrix[i][j] = i*cols + j + 1;
}
}
// 釋放記憶體
for(int i=0; i<rows; i++) {
free(matrix[i]);
}
free(matrix);
12.3 三維陣列實作
// 靜態宣告
int cube[2][3][4] = {
{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}},
{{13,14,15,16}, {17,18,19,20}, {21,22,23,24}}
};
// 動態宣告
int x=2, y=3, z=4;
int*** cube = malloc(x * sizeof(int**));
for(int i=0; i<x; i++) {
cube[i] = malloc(y * sizeof(int*));
for(int j=0; j<y; j++) {
cube[i][j] = malloc(z * sizeof(int));
}
}
12.4 矩陣運算範例
// 矩陣相加
void matrix_add(int** a, int** b, int** result, int rows, int cols) {
for(int i=0; i<rows; i++) {
for(int j=0; j<cols; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
}
// 矩陣轉置
void matrix_transpose(int** src, int** dst, int rows, int cols) {
for(int i=0; i<rows; i++) {
for(int j=0; j<cols; j++) {
dst[j][i] = src[i][j];
}
}
}
12.5 記憶體連續分配優化
// 單次分配所有記憶體
int** create_matrix(int rows, int cols) {
int** mat = malloc(rows * sizeof(int*));
int* data = malloc(rows * cols * sizeof(int));
for(int i=0; i<rows; i++) {
mat[i] = &data[i * cols];
}
return mat;
}
void free_matrix(int** mat) {
free(mat[0]); // 釋放資料區塊
free(mat); // 釋放指標陣列
}
課堂作業:陣列與指標函數實作「成績分析器」
作業目標
- 使用陣列儲存學生成績
- 用指標傳遞陣列給函數
- 實作常見統計函數
a. 平均分數
b. 分級人數 (0
59, 6069, 7079, 8089, 90~100)
核心任務
// 函數原型宣告
float calculateAverage(float* ptr, int n); // 計算平均
void analyzeScores(float* ptr, int n, int* level); // 分析成績分佈
程式結構
#include <stdio.h>
int main() {
int num;
printf("請輸入學生人數:");
scanf("%d", &num);
float scores[num]; // 宣告變長陣列
int level[5] = { 0 }; //
// 透過指標傳遞陣列
inputScores(scores, num);
// 計算並顯示平均
float avg = calculateAverage(scores, num);
printf("\n平均成績:%.2f\n", avg);
// 分析成績分佈
analyzeScores(scores, num);
return 0;
}
執行範例
請輸入學生人數:5
請輸入5位學生成績:
學生1: 88
學生2: 72
學生3: 95
學生4: 63
學生5: 77
平均成績:79.00
成績分佈:
0~59分: 0人
60~69分: 1人
70~79分: 2人
80~89分: 1人
90~100分: 1人