「菜鳥修煉C語言」系列第三彈,使用單向鏈表實現通訊錄核心功能。重點練習 typedef、巨集定義、鏈表節點增刪查、多檔案模組化編譯。
main.c(主程式/選單)、record.h(資料結構/巨集)、opre.c(鏈表操作)/* 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) { printf("malloc failed!"); exit(-1); } \
} while(0)
#endif
/* main.c */
#include
#include "record.h"
int menu_select(void);
void hand_menu(int cmd, ADDR *list_head);
int main() {
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);
}
}
int menu_select() {
int select;
printf("\n<------通訊錄------>\n");
printf("1:新增 2:刪除 3:顯示 0:退出\n輸入: ");
select = getchar(); while (getchar()!='\n');
while (select < '0' || select > '3') {
printf("錯誤,重新輸入: ");
select = getchar(); while (getchar()!='\n');
}
return select;
}
void hand_menu(int cmd, ADDR *list_head) {
switch (cmd) {
case '1': add_person(list_head); break;
case '2': list_head = del_person(list_head); break; // 可能刪除頭節點
case '3': dis_person(list_head); break;
}
}
/* opre.c */
#include
#include
#include "record.h"
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;
}
ADDR *del_person(ADDR *node) { // 返回新頭節點
char name[8];
ADDR *pre = NULL;
printf("輸入刪除姓名: "); scanf("%s", name);
while (node) {
if (!strcmp(node->data.name, name)) {
if (pre) pre->next = node->next;
free(node);
printf("刪除成功!\n");
return (pre) ? node : node->next; // 簡化:實際需傳回頭指標
}
pre = node;
node = node->next;
}
printf("未找到!\n");
return node; // 原版邏輯有缺陷,見下文改進
}
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);
}
| 問題 | 原代碼 | 改進建議 |
|---|---|---|
| 刪除頭節點處理 | pre 初始為 NULL,刪頭節點時 pre->next 解引用空指標 | 傳入 ADDR **head 或返回新頭指標 |
| 遞迴顯示風險 | 資料量大時棧溢位 | 改用迴圈遍歷 |
| 輸入緩衝區 | scanf("%s") 無長度限制 | 用 scanf("%7s", name) 防溢位 |
| 記憶體洩漏 | 程式退出未釋放鏈表 | 加入 free_list 函式 |
gcc main.c opre.c -o address_book
# 或
cl main.c opre.c /Fe address_book.exe (MSVC)