本項目是一個基於 C 語言的控制台四則運算練習軟件,實現了用戶註冊/識別、難度選擇、隨機出題、實時計時、自動評分、錯題重做、歷史排名、結果持久化等完整功能。代碼採用模塊化設計,通過文件存儲用戶數據與測試記錄,展示了 C 語言在文件操作、隨機數、時間管理、結構體數組排序等方面的綜合應用。
| 模塊 | 核心功能 | 關鍵技術點 |
|---|---|---|
| 用戶管理 | 首次輸入用戶名/學號,後續自動識別 | 文件讀寫 (fread/fwrite)、結構體序列化 |
| 出題引擎 | 加減乘除四模式,三級難度(1-3位數),指定題量 | srand(time(0))、rand() 範圍控制、整數/浮點除法區分 |
| 交互評分 | 即時判對錯,記錄用時,生成詳細報告 | time() 計時、文件追加寫入、正確率統計 |
| 數據持久化 | 測試過程、錯題、正確答案分文件存儲 | 多文件指針管理、fprintf/fscanf 格式化 |
| 排名系統 | 按平均正確率降序排序,顯示歷史最佳 | 結構體數組冒泡排序、文件讀取重構數組 |
| 錯題重做 | 讀取錯題文件重新作答,對照標準答案 | 文件讀取解析、交互式輸入驗證 |
#define MAX_STUDENTS 50
#define MAX_NAME_LEN 20
struct Student {
char name[MAX_NAME_LEN + 1];
char num[11]; // 學號/ID
float avscore; // 平均正確率百分比
} students[MAX_STUDENTS];
// 全局統計變量
float total_score = 0; // 累計正確率和
int test_count = 0; // 測試次數
time_t start_time, end_time; // 計時
Judge/SaveMessage)void Judge() {
FILE *fp = fopen("studentmessage.txt", "rb+");
if (!fp) { fp = fopen("studentmessage.txt", "wb+"); } // 首次運行創建
int i = 0;
while (fread(&students[i], sizeof(struct Student), 1, fp) == 1) {
i++;
if (i >= MAX_STUDENTS) { printf("用戶已滿\n"); exit(0); }
}
// 若文件為空,i=0,進入註冊流程
fclose(fp);
}
void SaveMessage() {
FILE *fp = fopen("studentmessage.txt", "ab"); // 追加二進制
FILE *fp1 = fopen("TestResult.txt", "a"); // 文本日誌
printf("輸入姓名: "); scanf("%s", students[0].name);
printf("輸入學號: "); scanf("%s", students[0].num);
fwrite(&students[0], sizeof(struct Student), 1, fp);
fprintf(fp1, "姓名:%s\n學號:%s\n", students[0].name, students[0].num);
fclose(fp); fclose(fp1);
}
void Addition(int count) {
FILE *fp = fopen("TestResult.txt", "a");
FILE *fp_err = fopen("failedResult.txt", "a");
FILE *fp_ans = fopen("rightResult.txt", "a");
int difficulty, range;
printf("選擇難度 1.簡單 2.一般 3.困難: "); scanf("%d", &difficulty);
range = (difficulty==1)?10 : (difficulty==2)?100 : 1000;
int correct = 0;
fprintf(fp, "加法測試:\n被加數 + 加數 = 你的答案 正確答案 結果\n");
for (int i = 0; i < count; i++) {
int a, b, ans, user_ans;
if (difficulty == 1) { a = rand()%range; b = rand()%range; }
else { a = range/10 + rand()%(range - range/10); b = range/10 + rand()%(range - range/10); }
ans = a + b;
printf("%d + %d = ", a, b);
scanf("%d", &user_ans);
if (user_ans == ans) {
fprintf(fp, "%d + %d = %d %d √\n", a, b, user_ans, ans);
correct++;
} else {
fprintf(fp, "%d + %d = %d %d ×\n", a, b, user_ans, ans);
fprintf(fp_err, "%d + %d =\n", a, b); // 錯題供重做
fprintf(fp_ans, "%d + %d = %d\n", a, b, ans); // 標準答案
}
}
float rate = (float)correct / count;
fprintf(fp, "正確 %d 錯誤 %d 正確率 %.2f%%\n\n", correct, count-correct, rate*100);
total_score += rate * 100; test_count++;
fclose(fp); fclose(fp_err); fclose(fp_ans);
}
減法/乘法 邏輯同加法,僅運算符與生成範圍調整。除法 需處理浮點精度:
// 除法:生成非零除數,保留小數點後一位比較
int a = ...; int b = range/10 + rand()%(range - range/10); // 確保 b != 0
float ans = (float)a / b;
ans = (int)(ans * 10) / 10.0; // 強制截斷到 0.1 位
printf("%d / %d = ", a, b);
float user_ans; scanf("%f", &user_ans);
if (user_ans == ans) ... // 直接比較浮點(因已截斷)
Sort/FileCopy)void Sort() {
// 讀取歷史記錄 nrecord.txt 到 students 數組
FILE *fp = fopen("nrecord.txt", "a+");
fprintf(fp, "%s %s %.2f\n", students[0].name, students[0].num, total_score/test_count);
rewind(fp);
int n = 0;
while (fscanf(fp, "%s %s %f", students[n].name, students[n].num, &students[n].avscore) == 3) n++;
fclose(fp);
// 簡單冒泡排序(降序)
for (int i = 0; i < n-1; i++)
for (int j = 0; j < n-1-i; j++)
if (students[j].avscore < students[j+1].avscore) {
struct Student tmp = students[j];
students[j] = students[j+1];
students[j+1] = tmp;
}
printf("排名:\n姓名 學號 正確率\n");
for (int i = 0; i < n; i++)
printf("%s %s %.2f%%\n", students[i].name, students[i].num, students[i].avscore);
}
void FileCopy() {
// 以學號命名備份測試詳情
char filename[20];
sprintf(filename, "%s.txt", students[0].num);
FILE *in = fopen("TestResult.txt", "r");
FILE *out = fopen(filename, "w");
int ch;
while ((ch = fgetc(in)) != EOF) fputc(ch, out);
fclose(in); fclose(out);
}
ChooseFunc/Begin)void ChooseFunc() {
int choice;
while (1) {
printf("\n1.開始測試 2.查看結果 3.查看排名 4.錯題重做 5.結束測試\n選擇: ");
scanf("%d", &choice);
switch (choice) {
case 1: start_time = time(NULL); Begin(); break;
case 2: ShowResult(); break;
case 3: Sort(); break;
case 4: Rework(); break;
case 5:
end_time = time(NULL);
printf("本次答題用時: %ld 秒\n", end_time - start_time);
FileCopy(); TestOver(); return;
}
}
}
students[0]),多用戶切換邏輯缺失。需實現「查找現有用戶 → 找到則加載歷史 → 否則新建」流程。scanf 遇非預期輸入(如輸入字符串)會陷入死循環。需檢查返回值並清理輸入緩衝區。qsort 或堆排序。system("color b0") 僅 Windows 有效,跨平台建議用 ANSI 轉義序列或 ncurses。main.c ├── main() // 初始化、清空臨時文件、啟動流程 ├── Judge() // 用戶識別/創建 ├── SaveMessage() // 保存用戶信息 ├── ChooseFunc() // 主菜單循環 ├── Begin() // 運算類型選擇子菜單 ├── Addition/Subtracter/Multiplication/Division() // 四則運算實現 ├── Rework() // 錯題重做 ├── Sort() // 排名排序顯示 ├── ShowResult() // 顯示詳細測試記錄 ├── FileCopy() // 備份測試結果 ├── TestOver() // 結束提示 └── 結構體定義、全局變量
項目感悟:這是一個典型的「練手級」控制台應用,雖代碼風格較為樸素(大量重複代碼、全局變量、固定數組),但完整覆蓋了文件持久化、隨機數、時間統計、結構體數組操作、菜單狀態機等 C 語言核心實戰技能。重構方向:提取公共出題函數、引入鏈表/動態數組支持多用戶、使用
qsort、增加輸入驗證、分離業務邏輯與 IO。結對編程過程中,雙人協作快速暴露邏輯漏洞(如除法浮點比較、文件指針管理),驗證了「兩人腦力 > 單人」的工程效能。