初學 C 語言鏈表時,最讓人困惑的莫過於:main 函式裡的 list 已經是指標(Node*),為什麼初始化函式 init_linkedlist 還要傳 &list(二級指標 Node**)?直接傳 list(一級指標)不行嗎?
答案很簡單:C 語言只有「傳值呼叫」,若要在函式內修改呼叫端的指標本身(讓它指向新配置的記憶體),必須傳遞該指標的位址——也就是二級指標。
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct Node {
ElemType elem;
struct Node *next;
} Node, *LinkedList; // LinkedList 其實是 Node*
// 初始化:建立頭節點,next 設為 NULL
void init_linkedlist(LinkedList *list) { // 參數是 Node** (二級指標)
*list = (LinkedList)malloc(sizeof(Node)); // 修改呼叫端的 list 指向
(*list)->next = NULL;
}
// 尾插法插入節點
void insert(LinkedList list, ElemType elem) { // 這裡只需一級指標
Node *p, *q;
q = list; // q 指向頭節點
p = (Node *)malloc(sizeof(Node));
p->elem = elem;
p->next = NULL;
while (q->next != NULL) q = q->next; // 找到尾節點
q->next = p; // 掛上新節點
}
int main() {
LinkedList list, p; // list 是 Node*
init_linkedlist(&list); // 傳遞 list 的位址 (Node**)
insert(list, 3);
insert(list, 4);
insert(list, 5);
p = list->next; // 跳過頭節點
while (p != NULL) {
printf("%4d", p->elem);
p = p->next;
}
printf("\n");
return 0;
}
main 的 stack frame: list (Node*) ──→ ??? (未初始化,野指標)
init_linkedlist(&list) 時init_linkedlist 的參數 list_ptr (Node**) ──→ main 的 list 位址
*list = malloc(...)*list (解參考得到 main 的 list) ──→ 新配置的 Node (頭節點)
main 的 list ──→ 頭節點 (有效記憶體)
若改傳一級指標 init_linkedlist(list),函式內收到的是 list 的拷貝值,修改拷貝無法影響原本的 list,導致 main 的 list 仍為野指標,後續操作直接崩潰。
| 場景 | 參數類型 | 原因 |
|---|---|---|
| 修改呼叫端的指標指向 (如 malloc、頭節點初始化、頭插法) | Type** | 需修改指標變數本身的值(位址) |
| 只修改指標指向的資料內容 (如尾插法、遍歷、修改節點資料) | Type* | 透過指標間接存取記憶體,不需改指標本身 |
| 函式需回傳多個指標/陣列 | Type** 或 Type*[] | 輸出型參數 |
「指標也是變數,傳指標就能修改原值?」
❌ 錯誤。傳一級指標 Node* list,函式收到的是 指標值的拷貝(即記憶體位址的拷貝)。你可以透過這個拷貝去修改指向的記憶體內容(如 list->next = ...),但無法修改呼叫端的指標變數本身(如 list = malloc(...))。要修改指標變數本身,必須傳其位址 &list,對應參數為 Node**。
void head_insert(LinkedList *list, ElemType elem) {
Node *p = (Node *)malloc(sizeof(Node));
p->elem = elem;
p->next = (*list)->next; // 新節點指向原本的第一個資料節點
(*list)->next = p; // 頭節點指向新節點
}
頭插法雖不改變頭節點指標 list,但若設計為「無頭節點」的鏈表,頭插法會直接改變鏈表頭指標,同樣需要二級指標。
Node**。理解「傳值呼叫」本質,二級指標就不再神秘。下次寫鏈表,問自己:這個函式要不要改呼叫端的指標指向? 答案決定了一級還是二級。