首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

從鏈表初始化看 C 語言二級指標的必要性

     編輯:關於C

前言:為什麼初始化鏈表要傳「指標的指標」?

初學 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 後

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,但若設計為「無頭節點」的鏈表,頭插法會直接改變鏈表頭指標,同樣需要二級指標。

總結

  • 一級指標:傳遞資料存取權(可改內容,不可改指向)。
  • 二級指標:傳遞指標變數的修改權(可改指向,如 malloc、頭節點初始化、頭插法)。
  • 鏈表初始化、頭插法、刪除頭節點、鏈表鏡像反轉等操作,若涉及「改變鏈表頭指標」,統統需要 Node**。

理解「傳值呼叫」本質,二級指標就不再神秘。下次寫鏈表,問自己:這個函式要不要改呼叫端的指標指向? 答案決定了一級還是二級。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved