在通訊錄(一)基礎上,新增資料持久化功能:程式啟動自動載入 phonebook.dat,退出或手動儲存將鏈表寫入檔案。核心新增知識:FILE* 操作、fprintf/fscanf 格式化讀寫、靜態區域變數保持鏈表頭指標。
main.c:引入 flag 標記首次初始化,選單新增「4:儲存」record.h:資料結構不變,保留 ASK 巨集opre.c:新增 save_person、init_person,修正 add/del/dis 介面回傳新頭指標/* main.c */
#include
#include "record.h"
int menu_select(void);
void hand_menu(int cmd, int *flag);
int main() {
int cmd = 0, flag = 1;
while (1) {
cmd = menu_select();
if (cmd == '0') return 0;
hand_menu(cmd, &flag);
}
}
void hand_menu(int cmd, int *flag) {
static ADDR *list_head = NULL; // 靜態變數:生命週期贯穿整個程式
if (*flag) {
list_head = init_person(list_head); // 首次載入檔案
*flag = 0;
}
switch (cmd) {
case '1': list_head = add_person(list_head); break;
case '2': list_head = del_person(list_head); break;
case '3': dis_person(list_head); break;
case '4': save_person(list_head); break;
}
}
/* opre.c 片段 */
#define FILE_NAME "phonebook.dat"
void save_person(ADDR *list_head) {
FILE *pf = fopen(FILE_NAME, "w+");
ADDR *node = list_head;
while (node) {
fprintf(pf, "%s %s\n", node->data.name, node->data.tel);
node = node->next;
}
fclose(pf);
printf("儲存成功!\n");
}
ADDR *init_person(ADDR *list_head) {
ADDR *head = list_head, *node = list_head, *new_node;
FILE *pf = fopen(FILE_NAME, "r");
if (!pf) return head; // 檔案不存在視為空
char name[8], tel[20];
ASK(new_node);
if (fscanf(pf, "%s %s", new_node->data.name, new_node->data.tel) != 2) {
free(new_node);
fclose(pf);
return head;
}
rewind(pf); // 重置檔案指標到開頭
while (fscanf(pf, "%s %s", name, tel) == 2) {
ASK(new_node);
new_node->next = NULL;
strcpy(new_node->data.name, name);
strcpy(new_node->data.tel, tel);
if (!head) head = node = new_node;
else {
while (node->next) node = node->next;
node->next = new_node;
}
}
fclose(pf);
return head;
}
ADDR *add_person(ADDR *list_head) {
ADDR *head = list_head, *node = list_head, *new_p;
ASK(new_p); new_p->next = NULL;
printf("姓名: "); scanf("%7s", new_p->data.name);
printf("電話: "); scanf("%19s", new_p->data.tel);
if (!node) return new_p; // 空鏈表直接做頭
while (node->next) node = node->next;
node->next = new_p;
return head;
}
ADDR *del_person(ADDR *list_head) {
ADDR *head = list_head, *node = list_head, *pre = node;
char name[8];
printf("刪除姓名: "); scanf("%7s", name);
if (!head) { printf("空通訊錄\n"); return head; }
if (!strcmp(head->data.name, name)) { // 刪除頭節點
head = head->next;
free(node);
printf("刪除成功!\n");
return head;
}
while (node) {
if (!strcmp(node->data.name, name)) {
pre->next = node->next;
free(node);
printf("刪除成功!\n");
return head;
}
pre = node; node = node->next;
}
printf("未找到!\n");
return head;
}
void dis_person(ADDR *list_head) {
ADDR *node = list_head;
if (!node) { printf("無資料\n"); return; }
printf("姓名\t\t號碼\n");
while (node) {
printf("%s\t%s\n", node->data.name, node->data.tel);
node = node->next;
}
}
| 技術 | 應用 | 注意事項 |
|---|---|---|
| 靜態區域變數 | static ADDR *list_head 在 hand_menu 中 | 避免全域變數汙染,保持模組封裝 |
| 檔案模式 | "w+" 覆寫、"r" 唯讀 | 載入失敗需檢查 fopen 回傳值 |
| 格式化 I/O | fprintf/fscanf 對應結構欄位 | 欄位含空格需改用分隔符或定寬讀取 |
| 指標傳遞 | 操作函式回傳 ADDR* 新頭指標 | 呼叫端必須接收回傳值更新頭指標 |
fscanf 一次再 rewind 再讀,導致首筆資料被處理兩次。修正:直接 rewind 後進入 while(fscanf...) 迴圈。scanf("%s") 無長度限制 → scanf("%7s", name) 對應 char name[8]。main 退出前遍歷 free。gcc main.c opre.c -o address_book_v2
./address_book_v2
# 測試流程:新增幾筆 → 顯示 → 儲存 → 退出 → 重啟驗證資料保留