在C語言的世界裡,指針無疑是最迷人、也最讓初學者頭疼的特性。有人說「不懂指針,就不懂C語言」,這句話雖然聽起來誇張,但道出了指針在C語言中的核心地位。本篇筆記將帶你從指針的基本概念出發,層層深入,最終掌握指針的高級應用與常見陷阱。
簡單來說,指針是一個特殊的變數,它儲存的不是普通數值,而是另一個變數的記憶體地址。你可以把它想像成一張「藏寶圖」,上面標記著寶藏(資料)確切的位置(記憶體地址)。
int num = 42; // 普通整數變數
int *ptr = # // 指向 int 的指針,儲存 num 的地址
printf("num 的值: %d\n", num);
printf("num 的地址: %p\n", &num);
printf("ptr 儲存的地址: %p\n", ptr);
printf("透過 ptr 取值: %d\n", *ptr);
關鍵符號解析:
&(取址運算子):獲取變數的記憶體地址*(解參考運算子):透過地址獲取該位置儲存的值int *ptr:聲明一個指向 int 型資料的指針在大多數情況下,陣列名會自動轉換為指向首元素的指針:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 等價於 int *p = &arr[0];
printf("%d\n", *p); // 輸出 10
printf("%d\n", *(p+1)); // 輸出 20
指針支援加減運算,但步長取決於所指向的資料型別大小:
int *p = arr;
p++; // 移動到下一個 int 元素(地址 + sizeof(int))
p += 2; // 向後移動 2 個 int 元素
常用於函數中修改指針本身的值(如動態記憶體分配):
void allocate(int **ptr, int size) {
*ptr = malloc(size * sizeof(int));
}
int *p = NULL;
allocate(&p, 10); // 傳遞指針的地址,函數可修改 p 本身
int add(int a, int b) { return a + b; }
int (*func_ptr)(int, int) = add; // 聲明函數指針
printf("%d\n", func_ptr(3, 4)); // 輸出 7
* → 使用前必須檢查// 錯誤示範:記憶體洩漏
int *p = malloc(sizeof(int));
p = NULL; // 丟失了原本分配的記憶體地址,無法釋放
// 正確做法
int *p = malloc(sizeof(int));
if (p) {
// 使用 p
free(p);
p = NULL; // 釋放後置 NULL,防止懸空指針
}
| 聲明 | 含義 |
|---|---|
const int *p | 指向常量的指針(不可透過 p 修改值) |
int *const p | 常量指針(p 本身不可修改指向) |
const int *const p | 指向常量的常量指針(雙重限制) |
void reverse_string(char *str) {
if (!str) return;
char *end = str + strlen(str) - 1;
while (str < end) {
char tmp = *str;
*str++ = *end;
*end-- = tmp;
}
}
typedef struct {
int *data;
size_t size;
size_t capacity;
} DynamicArray;
void da_init(DynamicArray *arr) {
arr->data = NULL;
arr->size = 0;
arr->capacity = 0;
}
void da_push(DynamicArray *arr, int value) {
if (arr->size >= arr->capacity) {
size_t new_cap = arr->capacity ? arr->capacity * 2 : 4;
int *new_data = realloc(arr->data, new_cap * sizeof(int));
if (!new_data) return; // 處理分配失敗
arr->data = new_data;
arr->capacity = new_cap;
}
arr->data[arr->size++] = value;
}
& 和 * 運算子指針雖然強大,但「能力越大,責任越大」。只有深入理解記憶體模型,才能駕馭指針這把雙刃劍,寫出高效、健壯的C程式碼。持續練習、閱讀優秀開源專案原始碼,你會發現指針其實並不可怕,反而能讓你窺見程式運行的最本質邏輯。