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

菜鳥修煉C語言:通訊錄基礎實作——單向鏈表與多檔案模組化

     編輯:關於C

專案背景與目標

作為「菜鳥修煉C語言」系列的第一個小專案,本通訊錄系統旨在透過完整的單向鏈表操作,讓初學者掌握 C 語言中最核心也最易混淆的指標與動態記憶體管理。專案需求包含新增、刪除、顯示聯系人,並採用多檔案模組化架構,奠定良好的工程化習慣。

設計需求重點

  • 資料結構:姓名(6 字元混編碼)、電話(18 字元含區碼)
  • 功能:尾插新增、按姓名刪除、全量顯示、選單驅動
  • 技術要求:`typedef` 別名、`#define` 巨集分配記憶體、三模組分離(輸入/顯示/主控)

核心模組解析

1. 共用標頭檔 (record.h)

#ifndef _RECORD_H_
#define _RECORD_H_

typedef struct {
    char name[8];
    char tel[20];
} DATA;

typedef struct node {
    DATA data;
    struct node *next;
} ADDR;

// 巨集:安全分配節點記憶體
#define ASK(p) do { \
    p = (ADDR *)malloc(sizeof(ADDR)); \
    if (p == NULL) { \
        printf("malloc memory failed!"); \
        exit(-1); \
    } \
} while(0)

#endif

統一定義資料結構與分配巨集,供 `main.c`、`opre.c` 共同引用,確保結構體佈局一致。

2. 主控流程 (main.c)

int main(int argc, char *argv[]) {
    int cmd = 0;
    ADDR *list_head;
    ASK(list_head);        // 分配頭節點
    list_head->next = NULL; // 初始化空鏈表

    while (1) {
        cmd = menu_select();
        if (cmd == '0') return 0;
        hand_menu(cmd, list_head);
    }
}

主函式僅負責:分配頭節點 → 進入選單迴圈 → 分派命令。注意 `hand_menu` 參數為 `ADDR *list_head`(值傳遞),因此刪除頭節點時需透過回傳值更新主函式的指標——這正是初學者最易踩的坑,下文詳述。

3. 業務邏輯實作 (opre.c)

新增聯系人:尾插法

void add_person(ADDR *node) {
    ADDR *new_p;
    ASK(new_p);
    new_p->next = NULL;

    printf("請輸入姓名:"); scanf("%s", new_p->data.name);
    printf("請輸入電話號碼:"); scanf("%s", new_p->data.tel);

    while (node->next) node = node->next; // 走到尾節點
    node->next = new_p;
}

傳入頭節點,遍歷至尾部插入。因不涉及頭節點變更,無需回傳新頭指標。

刪除聯系人:單向鏈表刪除節點

void del_person(ADDR *node) {
    char name[8];
    ADDR *pre = NULL;
    printf("請輸入要刪除的名字:"); scanf("%s", name);

    while (node) {
        if (!strcmp(node->data.name, name)) {
            pre->next = node->next; // 斷鏈
            free(node);             // 釋放記憶體
            printf("成功刪除!\n");
            return;
        }
        pre = node;
        node = node->next;
    }
    printf("沒有找到該名字!\n");
}

嚴重缺陷:若刪除的是第一個資料節點(`head->next`),`pre` 仍為 `NULL`,執行 `pre->next` 會導致段錯誤。且函式為 `void`,主函式的 `list_head` 無法感知頭節點變化。
修正方案:回傳 `ADDR*` 新頭指標,主函式接收更新;刪除邏輯需特別處理「刪除首節點」情況(參考 1913 版本的 `del_person` 實作)。

顯示所有聯系人:遞迴實作

void dis_person(ADDR *node) {
    if (!node) return;
    node = node->next;       // 跳過頭節點
    dis_person(node);        // 遞迴下一節點
    if (node)
        printf("姓名:%s 號碼:%s\n", node->data.name, node->data.tel);
}

採用遞迴後序列印,實際輸出順序為「最後節點先印」,即逆序顯示。若需正序,應先印再遞迴,或改用迴圈遍歷。

關鍵技術點總結

技術點本專案應用常見誤區
typedef 結構體別名`ADDR` 簡化 `struct node` 宣告混淆結構體標籤與別名
巨集封裝 malloc`ASK(p)` 統一錯誤處理巨集參數無括號導致優先級錯誤
單向鏈表操作增/刪/查/遍歷完整流程刪除頭節點、斷鏈後未 free
多檔案編譯`gcc main.c opre.c -o app`標頭檔重複引用無防禦宏
指標傳遞機制值傳遞指標 vs 指標的指標函式內修改指標不影響呼叫端

編譯與測試

gcc main.c opre.c -o address_book_v1
./address_book_v1

測試建議:連續新增 3 筆 → 顯示驗證順序 → 刪除中間節點 → 刪除首節點(觀察是否崩潰) → 刪除尾節點 → 再次顯示。

遞進學習路徑

  1. 修正 `del_person` 回傳新頭指標,主函式接收更新
  2. 將 `dis_person` 改為迴圈正序列印
  3. 加入「查詢聯系人」功能(字串比對遍歷)
  4. 引入檔案存取,進階至 通訊錄(二)持久化版本

總結

這個基礎版通訊錄雖功能簡單,卻涵蓋了 C 語言指標操作、動態記憶體、鏈表算法、模組化編程四大核心支柱。代碼中暴露的刪除邏輯缺陷,正是從「能跑通」走向「寫得對」的關鍵鍛鍊機會。建議動手修復 Bug,再對比進階版的寫法,體會良好介面設計(回傳值、錯誤碼)的重要性。

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