作為「菜鳥修煉C語言」系列的第一個小專案,本通訊錄系統旨在透過完整的單向鏈表操作,讓初學者掌握 C 語言中最核心也最易混淆的指標與動態記憶體管理。專案需求包含新增、刪除、顯示聯系人,並採用多檔案模組化架構,奠定良好的工程化習慣。
#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` 共同引用,確保結構體佈局一致。
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`(值傳遞),因此刪除頭節點時需透過回傳值更新主函式的指標——這正是初學者最易踩的坑,下文詳述。
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 筆 → 顯示驗證順序 → 刪除中間節點 → 刪除首節點(觀察是否崩潰) → 刪除尾節點 → 再次顯示。
這個基礎版通訊錄雖功能簡單,卻涵蓋了 C 語言指標操作、動態記憶體、鏈表算法、模組化編程四大核心支柱。代碼中暴露的刪除邏輯缺陷,正是從「能跑通」走向「寫得對」的關鍵鍛鍊機會。建議動手修復 Bug,再對比進階版的寫法,體會良好介面設計(回傳值、錯誤碼)的重要性。