本項目包含兩大獨立模塊:密碼保護的通訊錄管理系統(增刪改查、權限分級、文件持久化)與萬年曆顯示程序(日期驗證、閏年計算、日曆渲染)。代碼展示了 C 語言結構體、文件操作、指針、控制流綜合應用,是課程設計、畢業設計的優秀參考範本。
struct Info {
char name[15];
char phone[12];
char sex[2]; // "M"/"F"
char age[4];
char address[20];
};
| 功能 | 管理員 | 普通用戶 | 關鍵技術點 |
|---|---|---|---|
| 新增記錄 | ✓ | ✗ | 二進制文件追加寫 fwrite |
| 修改記錄 | ✓ | ✗ | 臨時文件重寫 remove/rename |
| 刪除記錄 | ✓ | ✗ | 標記刪除 + 文件重建 |
| 顯示所有 | ✓ | ✓ | 順序讀取 fread + 格式化輸出 |
| 多字段查詢 | ✓ | ✓ | strcmp 逐字段匹配 |
| 修改密碼 | ✓ | ✗ | 舊密碼驗證 + 覆寫密碼文件 |
void InitializationPassword() {
FILE *fp = fopen("password.txt", "rb");
if (!fp) { Initial(); return; } // 首次運行創建密碼
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
if (len == 0) { fclose(fp); Initial(); return; }
fseek(fp, 0, SEEK_SET);
fread(password_stored, 1, 10, fp); // 讀取存儲密碼
fclose(fp);
}
void EnterPassword() {
printf("Enter password: ");
gets(password_input); // 注意:gets 不安全,教學代碼常用
}
void Modify() {
FILE *fp_src = fopen("info.dat", "rb");
FILE *fp_tmp = fopen("temp.dat", "wb");
char target_name[20];
gets(target_name);
struct Info record;
while (fread(&record, sizeof(record), 1, fp_src)) {
if (strcmp(record.name, target_name) == 0) {
// 交互式獲取新字段值
gets(record.name); gets(record.phone); ...
fwrite(&record, sizeof(record), 1, fp_tmp);
} else {
fwrite(&record, sizeof(record), 1, fp_tmp); // 原樣複製
}
}
fclose(fp_src); fclose(fp_tmp);
remove("info.dat");
rename("temp.dat", "info.dat"); // 原子替換
}
void Search() {
int mode; char keyword[20];
scanf("%d", &mode); gets(keyword);
FILE *fp = fopen("info.dat", "rb");
struct Info record;
while (fread(&record, sizeof(record), 1, fp)) {
int match = 0;
switch (mode) {
case 1: match = strcmp(record.name, keyword) == 0; break;
case 2: match = strcmp(record.phone, keyword) == 0; break;
case 3: match = strcmp(record.sex, keyword) == 0; break;
case 4: match = strcmp(record.age, keyword) == 0; break;
case 5: match = strcmp(record.address, keyword) == 0; break;
}
if (match) print_record(&record);
}
fclose(fp);
}
days % 7 == 1)(year%4==0 && year%100!=0) || year%400==0total_days % 7 決定 1 號對應星期幾void DisplayTheCalendar() {
February(); // 設置 2 月天數 (28/29)
TotalDays(); // 計算距 1900-01-01 總天數
Calendar(); // 打印表頭 + 首行偏移
Showing(); // 打印日期網格
}
void Calendar() {
printf("SUN\tMON\tTUE\tWED\tTHU\tFRI\tSAT\n");
for (int i = days % 7; i > 0; i--) // 關鍵:days%7 為 1號前空格數
printf("\t");
}
void Showing() {
for (int d = 1; d <= month_days[month]; d++) {
printf("%d\t", d);
if (days % 7 == 6) printf("\n"); // 週六換行
days++;
}
printf("\n");
}
| 問題 | 風險 | 重構方向 |
|---|---|---|
gets() 無緩衝區保護 | 緩衝區溢出漏洞 | 改用 fgets(buf, sizeof(buf), stdin) 去除尾部 \n |
| 全局/靜態變量濫用 | 耦合度高,難測試 | 封裝為 Context 結構體傳遞 |
| 無輸入驗證(年份/月份範圍) | 異常輸入導致邏輯錯誤 | 增加 validate_date() 返回錯誤碼 |
| 魔術數字(10, 15, 20 等) | 維護困難 | 定義常量 MAX_NAME_LEN 等 |
| 密碼明文存儲 | 安全隱患 | 至少存儲鹽值哈希 SHA256(salt+pass) |
遞歸調用 EnterDate() 驗證失敗 | 棧溢出風險 | 改用循環 while(!valid) EnterDate() |
// 通訊錄模塊
typedef struct {
FILE* db_file;
char* password_hash;
int is_admin;
} AddressBookContext;
AddressBookContext* ab_create(const char* db_path);
void ab_destroy(AddressBookContext* ctx);
int ab_add(AddressBookContext* ctx, const Contact* c);
int ab_find(AddressBookContext* ctx, const Query* q, Contact** results, int* count);
// ...
// 曆法模塊
typedef struct {
int year, month;
} CalendarContext;
int cal_validate(CalendarContext* ctx);
void cal_render(const CalendarContext* ctx, char* buffer, size_t size);
# GCC 編譯(需鏈接數學庫 -lm) gcc address_book.c calendar.c -o ab_cal -Wall -Wextra -std=c99 # 運行 ./ab_cal # 首次運行提示設置密碼 # 後續運行驗證密碼進入對應權限界面
這個雙模塊項目雖屬教學級代碼,但完整覆蓋了「數據建模→文件持久化→用戶交互→權限控制→算法實現」軟件工程全鏈路。建議以此為藍本,逐步引入模塊化編譯、單元測試、錯誤碼體系、安全加固,將其進化為生產級組件庫。