指標是 C 語言區別於其他高階語言的核心特性,直接操作記憶體位址賦予了程式極高的效率與控制力。本文從變數作用域與生命週期切入,層層遞進講解指標的基本語法、傳址呼叫機制、指標算術運算規則、const 修飾語意,以及指標與陣列的深層關係,為初學者建立堅實的指標思維模型。
理解指標指向記憶體的有效性,首先要搞清變數「活」在哪裡、「死」在哪裡。
| 類別 | 關鍵字 | 作用域 | 生命週期 | 儲存區 | 預設初值 |
|---|---|---|---|---|---|
| 區域變數 | auto (省略) | 區塊內 { } | 進入區塊建立,離開銷毀 | 堆疊 | 不定值 (垃圾值) |
| 全域變數 | 無 (檔案頂層) | 全檔案 (需 extern 跨檔) | 程式啟動~結束 | 資料區 (BSS/Data) | 0 / NULL |
| 靜態區域變數 | static | 區塊內 { } | 程式啟動~結束 | 資料區 | 0 / NULL |
| 靜態全域變數 | static (檔案頂層) | 當前檔案 (內部連結) | 程式啟動~結束 | 資料區 | 0 / NULL |
static 保持狀態區域變數同名會遮蔽全域變數。static 區域變數僅初始化一次,函數多次呼叫間共享同一記憶體,可用於計數器、單例模式等。
#include <stdio.h>
int x = 1000; // 全域變數
void f1(int a) {
a = 10; // 參數為區域副本
auto int x = 20; // 區域變數,遮蔽全域 x
static int y = 30; // 靜態區域變數,僅初始化一次
y++; x++;
printf("in f1: a=%d, x=%d, y=%d\n", a, x, y); // y 會遞增
}
void f2() { printf("in f2: x=%d\n", x); x = 2000; } // 存取全域 x
int main() {
int x = 30; // main 的區域 x,遮蔽全域 x
f1(100); f1(100); // y 輸出 31, 32
printf("in main: x=%d\n", x); // 輸出 30 (區域 x)
f2(); f3(); // 全域 x 被 f2 改為 2000
return 0;
}
int *p; —— p 儲存 int 型別資料的位址。int a = 10;
int *p = &a; // p 指向 a
printf("p = %p, *p = %d\n", (void*)p, *p); // 輸出位址與值 10
*p = 20; // 透過指標修改 a,a 變為 20
C 語言參數傳遞永遠是值傳遞。若想在函數內修改主調變數,必須傳遞該變數的位址 (指標)。
void swap_val(int a, int b) { // 形參為副本
int t=a; a=b; b=t; // 僅影響副本
}
void swap_ptr(int *a, int *b) { // 形參為位址
int t = *a; *a = *b; *b = t; // 解引用操作原變數
}
int main() {
int x=10, y=20;
swap_ptr(&x, &y); // 傳遞位址
// x=20, y=10
}
函數僅能 return 一個值。若需回傳最大值、最小值等多項資料,採用「輸出型參數」模式:
void maxAndMin(int data[], int size, int *max, int *min) {
*max = *min = data[0];
for (int i=1; i<size; i++) {
if (data[i] > *max) *max = data[i];
if (data[i] < *min) *min = data[i];
}
}
int main() {
int arr[10] = {5, 2, 9, 1, 7, 3, 8, 4, 6, 0};
int max, min;
maxAndMin(arr, 10, &max, &min); // 傳遞變數位址
printf("Max: %d, Min: %d\n", max, min);
}
指標 +1 不代表位址 +1 位元組,而是 + sizeof(指向型別)。這使得指標能自然遍歷陣列。
int a; int *pa = &a; // pa+1 增加 4 bytes (假設 int 4B)
short s; short *ps = &s; // ps+1 增加 2 bytes
char c; char *pc = &c; // pc+1 增加 1 byte
double d; double *pd = &d; // pd+1 增加 8 bytes
// 陣列遍歷等價性
int arr[5] = {10,20,30,40,50};
int *p = arr;
for(int i=0; i<5; i++) {
printf("%d ", *(arr+i)); // arr[i]
printf("%d ", *(p+i)); // p[i]
printf("%d ", i[arr]); // 同樣合法但不推薦
}
指標減法: 同型別指標相減得元素個數 (ptrdiff_t)。
const 修飾指標:三種鎖定模式const 位置決定「誰被鎖死」:
| 宣告 | 口訣 | 指標值 (位址) | 指向值 (*p) |
|---|---|---|---|
const int *pint const *p | const 在 * 左邊:指向常數的指標 | 可修改 (p = &b) | 不可修改 (*p=20 錯誤) |
int *const p | const 在 * 右邊:常數指標 | 不可修改 (p=&b 錯誤) | 可修改 (*p=20 正確) |
const int *const p | 雙重 const | 不可修改 | 不可修改 |
int i1=5, i2=10;
const int *p1 = &i2; // *p1=20 錯誤; p1=&i1 正確
int *const p2 = &i2; // p2=&i1 錯誤; *p2=20 正確
// 常見用途:函數參數唯讀保護
void print_str(const char *s) { // 承諾不修改 s 指向的內容
while(*s) putchar(*s++);
}
arr[i] 編譯器自動轉為 *(arr + i)。arr 在大多數表達式中退化為指向首元素的指標 &arr[0]。*(arr+i)、*(p+i)、p[i]、arr[i] 完全等價。int arr[5] = {1,2,3,4,5};
int *p = arr;
p++; // 合法:指標變數可修改
// arr++; // 錯誤:陣列名是常量,不可修改指向
sizeof(arr); // 20 (整個陣列大小,單位 byte)
sizeof(p); // 4/8 (指標變數大小)
*p++ 等價 *(p++):先取值 *p,再 p++ 移至下一元素。(*p)++:先取值 *p,再將指向的值加 1。*++p:先 p++,再取新位置的值。++*p:先取值,再將值加 1 (前置遞增)。malloc 需手動 free)。&var) 並在函數內解引用 (*ptr) 修改原值。p+N 位移 N * sizeof(*p) 位元組。const 看位置: 左鎖值、右鎖址、雙鎖死。sizeof 計算整體大小、退化為指標時失去長度資訊。掌握這五大核心概念,即可駕馭 C 語言指標的精髓,編寫出高效、安全的系統級程式碼。