「菜鳥修煉」系列壓軸之作:學生成績管理系統,約 600 行代碼,整合單向鏈表增刪改查、檔案持久化、多欄位排序、選單驅動。模組劃分清晰,適合作為 C 語言綜合練習範本。
/* students.h */
typedef struct st {
unsigned long ID;
char name[10];
float chinese, math, english;
double all_sub;
float average;
struct st *next;
} STU;
#define ASK_STU(p) do { \
p = (STU *)malloc(sizeof(STU)); \
if (!p) { printf("malloc failed!\n"); exit(1); } \
} while(0)
/* 函式宣告 */
STU *add_student(STU *);
STU *del_student(STU *);
void modify_student(STU *);
void search_student(STU *);
STU *display_student(STU *);
void save_student(STU *);
STU *init_student(STU *);
STU *select_sort(STU *, char); // 通用排序
STU *search_by_nb(STU *, unsigned long);
STU *search_by_name(STU *, char *);
/* students.c 片段 */
STU *select_sort(STU *g, char flag) {
STU *p, *q, *t, *s, *h;
h = (STU *)malloc(sizeof(STU)); // 虛擬頭節點
h->next = g; p = h;
while (p->next->next) {
for (s = p, q = p->next; q->next; q = q->next) {
int cmp = 0;
switch (flag) {
case '1': cmp = q->next->ID < s->next->ID; break;
case '2': cmp = q->next->chinese < s->next->chinese; break;
case '3': cmp = q->next->math < s->next->math; break;
case '4': cmp = q->next->english < s->next->english; break;
case '5': cmp = q->next->average < s->next->average; break;
}
if (cmp) s = q;
}
if (s != q) { // 需交換
t = s->next;
s->next = t->next;
t->next = p->next;
p->next = t;
}
p = p->next;
}
g = h->next; free(h);
return g;
}
STU *add_student(STU *stu_head) {
STU *stu, *head = stu_head, *node = stu_head;
ASK_STU(stu); stu->next = NULL;
printf("學號: "); scanf("%lu", &stu->ID);
while (search_by_nb(node, stu->ID)) {
printf("學號重複,重新輸入: "); scanf("%lu", &stu->ID);
}
printf("姓名: "); scanf("%9s", stu->name);
// 三科成績輸入與 0~100 驗證...
printf("英文: "); scanf("%f", &stu->english);
while (stu->english < 0 || stu->english > 100) { /* 重輸 */ }
/* 數學、語文同理 */
stu->all_sub = stu->chinese + stu->math + stu->english;
stu->average = stu->all_sub / 3;
if (!head) return stu;
while (node->next) node = node->next;
node->next = stu;
return select_sort(head, '1'); // 預設按學號排序
}
STU *del_student(STU *stu_head) {
int sel; STU *head = stu_head;
printf("1:全部 2:按姓名 3:按學號 0:返回\n選擇: ");
sel = getchar(); while (getchar()!='\n');
if (sel == '0' || !head) return head;
if (sel == '1') return del_all(head); // 遞迴釋放全部
return del_one(head, sel); // 2 或 3
}
STU *del_one(STU *stu_head, int flag) {
STU *node = stu_head, *head = stu_head, *pre = NULL;
char name[10]; unsigned long ID;
if (flag == '2') { printf("姓名: "); scanf("%9s", name); }
else { printf("學號: "); scanf("%lu", &ID); }
// 處理頭節點
if ((flag == '2' && !strcmp(node->name, name)) ||
(flag == '3' && node->ID == ID)) {
head = node->next; free(node);
printf("刪除成功!\n"); getchar();
return head;
}
while (node) {
int match = (flag == '2') ? !strcmp(node->name, name) : (node->ID == ID);
if (match) {
pre->next = node->next; free(node);
printf("刪除成功!\n"); getchar();
return head;
}
pre = node; node = node->next;
}
printf("未找到!\n"); getchar();
return head;
}
/* store.c */
#define FILE_NAME "students"
void save_student(STU *stu_head) {
FILE *fp = fopen(FILE_NAME, "w+");
STU *node = stu_head;
while (node) {
fprintf(fp, "%lu %s %.1f %.1f %.1f %.1lf %.1f\n",
node->ID, node->name,
node->chinese, node->math, node->english,
node->all_sub, node->average);
node = node->next;
}
fclose(fp);
printf("儲存成功!\n"); getchar();
}
STU *init_student(STU *stu_head) {
STU *head = stu_head, *node = stu_head, *new_node;
FILE *fp = fopen(FILE_NAME, "r+");
if (!fp) return head;
ASK_STU(new_node); new_node->next = NULL;
while (fscanf(fp, "%lu %s %f %f %f %lf %f",
&new_node->ID, new_node->name,
&new_node->chinese, &new_node->math, &new_node->english,
&new_node->all_sub, &new_node->average) == 7) {
if (!head) head = node = new_node;
else { while (node->next) node = node->next; node->next = new_node; }
ASK_STU(new_node); new_node->next = NULL;
}
free(new_node); // 最後多分配的一個
fclose(fp);
return head;
}
/* main.c */
int main() {
int cmd, flag = 0;
while (1) {
cmd = select_menu(); // 顯示選單、讀取輸入
hand_menu(cmd, &flag);
}
}
void hand_menu(int cmd, int *flag) {
static STU *stu_head = NULL;
if (!*flag) { stu_head = init_student(stu_head); *flag = 1; }
switch (cmd) {
case '0': save_student(stu_head); exit(0);
case '1': system("cls"); stu_head = add_student(stu_head); break;
case '2': system("cls"); stu_head = del_student(stu_head); break;
case '3': system("cls"); modify_student(stu_head); break;
case '4': system("cls"); search_student(stu_head); break;
case '5': system("cls"); stu_head = display_student(stu_head); break;
case '6': system("cls"); save_student(stu_head); break;
}
system("cls");
}
| 核心能力 | 本專案鍛鍊點 |
|---|---|
| 鏈表操作 | 增、刪、改、查、排序、遞迴釋放 |
| 檔案 I/O | 格式化讀寫、啟動載入、退出儲存 |
| 模組化設計 | 多檔案分離、介面抽象、靜態變數封裝 |
| 輸入健壯性 | 範圍檢查、唯一性驗證、緩衝區保護 |
| 排序演算法 | 鏈表選擇排序、多鍵值切換、虛擬頭節點技巧 |
透過此專案,從指標操作到系統架構設計都有實質突破。後續可嘗試:C++ 重構(RAII、STL)、演算法優化(合併排序 O(n log n))、GUI 介面、資料庫替代檔案存儲。