本文將帶領讀者從零開始,使用 C 語言開發一個功能完整的命令列通訊錄系統。專案涵蓋需求分析、資料結構設計、檔案持久化、模組化程式碼架構,適合 C 語言初學者鞏固指標、結構體、檔案操作與模組化設計等核心概念。
啟動時顯示歡迎選單,使用者輸入 1-6 選擇操作:
每項操作完成後自動返回主選單,直到使用者選擇退出。
| 模組 | 核心邏輯 | 關鍵資料結構 |
|---|---|---|
| 初始化 | 嘗試讀取資料檔;不存在則建立新檔並寫入聯絡人數量 0 | 檔案指標、結構體陣列 |
| 新增 | 輸入姓名/電話 → 確認 → 寫入陣列尾端 → 更新檔案 | Person 結構體、全域計數器 |
| 刪除 | 顯示列表 → 輸入編號 → 確認 → 陣列元素前移覆蓋 → 更新檔案 | 陣列位移演算法 |
| 修改 | 顯示列表 → 輸入編號 → 確認 → 輸入新資料 → 覆蓋陣列元素 → 更新檔案 | 暫存緩衝區避免輸入中途污染資料 |
| 查看 | 遍歷陣列列印所有聯絡人;空陣列提示 | 迴圈索引 |
| 搜尋 | 輸入姓名 → 線性搜尋陣列 → 找到顯示電話、否則提示無紀錄 | strcmp 字串比對 |
// 常數定義
#define MAX_CONTACTS 100 // 最大聯絡人數
#define NAME_LEN 13 // 姓名最大長度(含結束符)
#define PHONE_LEN 12 // 電話最大長度
// 聯絡人結構體
typedef struct {
char name[NAME_LEN];
char phone[PHONE_LEN];
} Person;
// 全域變數
Person contacts[MAX_CONTACTS]; // 聯絡人陣列
int contactCount = 0; // 目前聯絡人數量
const char *DATA_FILE = "contacts.dat"; // 資料檔名
// 函式宣告
void initSystem(void);
void addContact(void);
void deleteContact(void);
void editContact(void);
void listContacts(void);
void searchContact(void);
int saveToFile(void);
int validateIndex(int idx, int min, int max);
void showMenu(void);
initSystem()程式啟動時自動呼叫,負責從二進位檔案載入資料:
void initSystem(void) {
FILE *fp = fopen(DATA_FILE, "rb");
if (fp) {
fread(&contactCount, sizeof(int), 1, fp);
for (int i = 0; i < contactCount; i++) {
fread(&contacts[i], sizeof(Person), 1, fp);
}
fclose(fp);
printf("通訊錄初始化完成,共載入 %d 筆資料。\n", contactCount);
} else {
// 檔案不存在,建立空檔
fp = fopen(DATA_FILE, "wb");
if (fp) {
fwrite(&contactCount, sizeof(int), 1, fp);
fclose(fp);
printf("建立新通訊錄檔案。\n");
}
}
}
設計重點:務必先寫入/讀取 contactCount,否則重啟後無法知道陣列有效長度。
addContact()void addContact(void) {
if (contactCount >= MAX_CONTACTS) {
printf("通訊錄已滿,無法新增。\n");
return;
}
printf("請輸入姓名 (無空格):");
scanf("%12s", contacts[contactCount].name);
printf("請輸入電話 (無空格):");
scanf("%11s", contacts[contactCount].phone);
int confirm;
printf("確定新增 \"%s\" 嗎?(1=是/0=否):", contacts[contactCount].name);
scanf("%d", &confirm);
if (confirm) {
contactCount++;
saveToFile();
printf("新增成功!\n");
}
}
deleteContact()採用「後續元素前移」覆蓋被刪除位置,最後遞減計數器:
void deleteContact(void) {
listContacts();
if (contactCount == 0) return;
int idx;
printf("請輸入要刪除的編號 (1-%d):", contactCount);
scanf("%d", &idx);
if (!validateIndex(idx, 1, contactCount)) return;
int confirm;
printf("確定刪除 \"%s\"?資料無法復原 (1=是/0=否):", contacts[idx-1].name);
scanf("%d", &confirm);
if (!confirm) return;
// 陣列前移
for (int i = idx - 1; i < contactCount - 1; i++) {
contacts[i] = contacts[i + 1];
}
contactCount--;
saveToFile();
printf("刪除完成。\n");
}
saveToFile()每次資料異動後呼叫,確保持久化一致性:
int saveToFile(void) {
FILE *fp = fopen(DATA_FILE, "wb");
if (!fp) {
perror("檔案開啟失敗");
return -1;
}
fwrite(&contactCount, sizeof(int), 1, fp);
for (int i = 0; i < contactCount; i++) {
fwrite(&contacts[i], sizeof(Person), 1, fp);
}
fclose(fp);
return 0;
}
validateIndex()int validateIndex(int num, int min, int max) {
if (num >= min && num <= max) return 1;
printf("輸入編號超出範圍!\n");
return 0;
}
int main(void) {
initSystem();
int choice;
while (1) {
showMenu();
scanf("%d", &choice);
switch (choice) {
case 1: addContact(); break;
case 2: deleteContact(); break;
case 3: editContact(); break;
case 4: listContacts(); break;
case 5: searchContact(); break;
case 6:
printf("感謝使用,再見!\n");
return 0;
default:
printf("無效選項,請輸入 1-6。\n");
}
}
}
realloc 動態陣列,突破 100 筆上限。scanf 緩衝區殘留換行符、支援含空格姓名(改用 fgets + 去除換行)。本專案完整原始碼(含註解與 Makefile)可從 GitHub Repository 取得,歡迎 Fork 練習擴充功能。
這個通訊錄專案雖然規模不大,卻完整串聯了 C 語言的核心技能:結構體陣列、檔案二進位讀寫、指標操作、模組化設計、錯誤處理與使用者互動流程。建議初學者先完整抄寫並執行,再嘗試加入「依姓名排序」、「匯出 CSV」等擴充功能,將知識內化為實戰能力。