通訊錄是 C 語言綜合訓練的經典專案,涵蓋**結構體陣列、動態記憶體、檔案 I/O、模組化設計、選單驅動架構**。本文提供完整、可編譯、符合現代 C 標準的實現,並指出原始碼中的關鍵問題與改進方向。
#define MAX_CONTACTS 100
#define NAME_LEN 20
#define PHONE_LEN 15
#define ADDR_LEN 50
typedef struct {
char name[NAME_LEN];
char sex[8];
int age;
char phone[PHONE_LEN];
char address[ADDR_LEN];
} Contact;
typedef struct {
Contact *data; // 動態陣列
size_t size; // 當前聯繫人數
size_t capacity; // 容量
} AddressBook;
bool ab_init(AddressBook *ab, size_t init_cap) {
ab->data = malloc(init_cap * sizeof(Contact));
if (!ab->data) return false;
ab->size = 0;
ab->capacity = init_cap;
return true;
}
void ab_destroy(AddressBook *ab) {
free(ab->data);
ab->data = NULL;
ab->size = ab->capacity = 0;
}
bool ab_ensure_capacity(AddressBook *ab) {
if (ab->size < ab->capacity) return true;
size_t new_cap = ab->capacity * 2;
Contact *tmp = realloc(ab->data, new_cap * sizeof(Contact));
if (!tmp) return false;
ab->data = tmp;
ab->capacity = new_cap;
return true;
}
bool ab_add(AddressBook *ab) {
if (!ab_ensure_capacity(ab)) return false;
Contact *c = &ab->data[ab->size];
printf("姓名: "); scanf("%19s", c->name);
printf("性別: "); scanf("%7s", c->sex);
printf("年齡: "); scanf("%d", &c->age);
printf("電話: "); scanf("%14s", c->phone);
printf("地址: "); scanf("%49s", c->address);
ab->size++;
return true;
}
bool ab_delete(AddressBook *ab, const char *name) {
for (size_t i = 0; i < ab->size; i++) {
if (strcmp(ab->data[i].name, name) == 0) {
// 後續元素前移覆蓋
memmove(&ab->data[i], &ab->data[i+1],
(ab->size - i - 1) * sizeof(Contact));
ab->size--;
return true;
}
}
return false; // 未找到
}
int cmp_by_name(const void *a, const void *b) {
return strcmp(((Contact*)a)->name, ((Contact*)b)->name);
}
void ab_sort(AddressBook *ab) {
qsort(ab->data, ab->size, sizeof(Contact), cmp_by_name);
}
void print_menu() {
puts("******** 簡易通訊錄 ********");
puts("1. 新增聯繫人");
puts("2. 刪除聯繫人");
puts("3. 查找聯繫人");
puts("4. 修改聯繫人");
puts("5. 顯示所有");
puts("6. 按姓名排序");
puts("7. 保存到檔案");
puts("8. 從檔案載入");
puts("0. 退出");
printf("請選擇: ");
}
int main() {
AddressBook ab;
ab_init(&ab, 10);
int choice;
while (1) {
print_menu();
if (scanf("%d", &choice) != 1) break;
while (getchar() != '\n'); // 清理緩衝區
switch (choice) {
case 1: ab_add(&ab); break;
case 2: { char n[NAME_LEN]; scanf("%19s", n); ab_delete(&ab, n); } break;
case 3: { /* 查找邏輯 */ } break;
case 4: { /* 修改邏輯 */ } break;
case 5: ab_display(&ab); break;
case 6: ab_sort(&ab); break;
case 7: ab_save(&ab, "contacts.dat"); break;
case 8: ab_load(&ab, "contacts.dat"); break;
case 0: ab_destroy(&ab); return 0;
default: puts("無效選項");
}
}
ab_destroy(&ab);
return 0;
}
| 問題 | 原始碼 | 修正方案 |
|---|---|---|
| 固定陣列大小 10 | Man man[10] | 動態陣列 + realloc 擴容 |
| gets() 混用 scanf() | gets(x); scanf("%c", &sign) | 純用 scanf("%19s") 或 fgets + 解析 |
| 字串比較邏輯錯誤 | while (strcmp(...) != 0 && i < n) | 正確,但需注意 i 邊界 |
| 排序演算法錯誤 | 選擇排序寫法有誤 | 使用 qsort + 比較函數 |
| 無檔案持久化 | 記憶體中運作 | 增加二進制/文本檔案讀寫 |
| system("pause") 不可移植 | Windows 專用 | 移除或用跨平台暫停 |
通訊錄專案是從「寫語法」邁向「寫系統」的關鍵一步。掌握**動態記憶體、模組化介面設計、錯誤處理、資料持久化、標準庫算法(qsort/bsearch)**,並養成**可移植、安全、可維護**的編碼習慣,才是此專案的核心收穫。