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

C語言進階:通訊錄系統持久化升級——檔案 I/O 與鏈表整合

     編輯:關於C語言

版本演進

在通訊錄(一)基礎上,新增資料持久化功能:程式啟動自動載入 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/Ofprintf/fscanf 對應結構欄位欄位含空格需改用分隔符或定寬讀取
指標傳遞操作函式回傳 ADDR* 新頭指標呼叫端必須接收回傳值更新頭指標

常見坑與修正

  1. 載入邏輯重複讀取首筆:原代碼先 fscanf 一次再 rewind 再讀,導致首筆資料被處理兩次。修正:直接 rewind 後進入 while(fscanf...) 迴圈。
  2. 緩衝區溢位:scanf("%s") 無長度限制 → scanf("%7s", name) 對應 char name[8]。
  3. 記憶體洩漏:程式結束未釋放鏈表 → 建議在 main 退出前遍歷 free。

編譯與測試

gcc main.c opre.c -o address_book_v2
./address_book_v2
# 測試流程:新增幾筆 → 顯示 → 儲存 → 退出 → 重啟驗證資料保留

下一步擴展方向

  • 引入二分搜尋樹或雜湊表優化查詢效能
  • 支援多使用者資料隔離(檔案命名含用戶 ID)
  • 增加資料備份/還原、匯出 CSV 功能
  • 移植至嵌入式平台(如 STM32 + SPI Flash)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved