本專案旨在透過一個「班級各科平均成績統計」的實例,深度鍛鍊 C 語言中最核心也最易混淆的指標與陣列關係、指標陣列操作、動態記憶體分配與釋放。需求看似簡單:輸入 5 科成績與人數,輸出各科成績、總平均、最低分科目與分數,但實作要求展示三種不同記憶體管理策略,層層遞進。
輸入:數學75 物理80 外語83 政治85 體育86 人數30 輸出: 全班各科平均成績如下: 數學:75 物理:80 外語:83 政治:85 體育:86 人數:30 平均成績:81 最低分科目成績:75 最低分科目:數學
| 陣列名 | 型別 | 用途 | 大小 |
|---|---|---|---|
| `a` | `int*` (動態) | 5科成績 + 人數 + 平均 + 最低分 | 8 個 `int` |
| `name` | `char*[6]` | 5科名稱 + "科目"(最低分科目名) | 6 個指標 |
| `mean_low` | `char*[4]` | "人數"、"平均成績"、"最低分科目成績"、"最低分科目" | 4 個指標 |
int main() {
int *a; // 數值陣列:動態分配
char *name[SUB_NUM+1]; // 科目名指標陣列
char *mean_low[3]; // 統計項標籤指標陣列
a = (int*)malloc(8 * sizeof(int));
memset(a, 0, 8 * sizeof(int)); // 初始化為 0
init_name(name); // 靜態字串常量賦值
init_mean_low(mean_low); // 同上
input_message(name, mean_low, a); // 輸入成績、人數
analysis_message(a, name); // 計算平均、最低分
print_message(name, mean_low, a); // 格式化輸出
recycle_memory(name, mean_low, a); // 釋放所有記憶體
return 0;
}
關鍵點:
void init_name(char *name[]) {
name[0] = "數學"; name[1] = "物理"; name[2] = "外語";
name[3] = "政治"; name[4] = "體育"; name[5] = "科目";
}
void init_mean_low(char *mean_low[]) {
mean_low[0] = "人數"; mean_low[1] = "平均成績";
mean_low[2] = "最低分科目成績"; mean_low[3] = "最低分科目";
}
直接指向字串常量,**不可修改內容**,亦無需 `free`。若後續需動態修改字串,必須改為 `malloc` + `strcpy`。
void input_message(char *name[], char *mean_low[], int *a) {
for (int i = 0; i < SUB_NUM; i++) {
printf("%s: ", name[i]);
scanf("%d", a + i); // 指標算術:a+i 等價 &a[i]
while (a[i] > 100 || a[i] < 0) {
printf("輸入錯誤,重新輸入\n%s: ", name[i]);
scanf("%d", a + i);
}
}
printf("請輸入班級人數: ");
scanf("%d", a + SUB_NUM); // 人數存入 a[5]
}
展示指標下標引用法:`a+i`、`*(a+i)`、`a[i]` 三者等價,靈活運用可簡化碼風。
void analysis_message(int *a, char *name[]) {
int sum = 0, min = 200, min_idx = 0;
for (int i = 0; i < SUB_NUM; i++) {
sum += a[i];
if (a[i] < min) { min = a[i]; min_idx = i; }
}
a[SUB_NUM+1] = sum / SUB_NUM; // 平均存入 a[6]
a[SUB_NUM+2] = min; // 最低分存入 a[7]
name[SUB_NUM] = name[min_idx]; // 最低分科目名存入 name[5]
}
單趟掃描完成求和與找最小值,**時間複雜度 O(n)**,空間複雜度 O(1) 額外空間。
void print_message(char *name[], char *mean_low[], int *a) {
printf("分析結果為:\n");
for (int i = 0; i < SUB_NUM; i++)
printf("%s:%d\n", name[i], a[i]);
printf("人數:%d\n", a[SUB_NUM]);
printf("平均成績:%d\n", a[SUB_NUM+1]);
printf("最低科目成績:%d\n", a[SUB_NUM+2]);
printf("最低科目:%s\n", name[SUB_NUM]);
}
void recycle_memory(char *name[], char *mean_low[], int *a) {
free(a); a = NULL;
for (int i = 0; i < SUB_NUM+1; i++) {
free(name[i]); name[i] = NULL; // ❌ 錯誤:釋放字串常量指標!
}
for (int i = 0; i < 4; i++) {
free(mean_low[i]); mean_low[i] = NULL; // ❌ 同樣錯誤
}
}
⚠ 致命錯誤分析:`name[]` 與 `mean_low[]` 存放的是**字串常量指標**(指向唯讀區),**絕不能 `free`**!執行時會導致未定義行為(通常是崩潰)。
正確做法:僅 `free(a)`;指標陣列本身在棧上,離開 `main` 自動回收,無需手動釋放元素。
| 級別 | 描述 | 關鍵差異 |
|---|---|---|
| 1. 陣列實作 | 全部用靜態/棧上陣列 | 無 `malloc`,最簡單但固定大小 |
| 2. 動態分配數值 | 本專案做法:`a` 用 `malloc` | 體驗堆上數值陣列生命週期管理 |
| 3. 全動態含字串 | 連字串也 `malloc` + `strcpy` | 最複雜,需雙重指標 `char**` 管理 |
gcc grade_stat.c -o grade_stat
./grade_stat
void recycle_memory_fixed(int *a) {
free(a); // 僅釋放真正 malloc 的塊
// name, mean_low 為棧上指標陣列,元素指向常量區,無需釋放
}
成績統計專案以極精簡的邏輯,精準切中 C 語言初學者最痛的三個點:**指標陣列語法、動態記憶體流程、字串常量與堆記憶體的區別**。原始碼中的 `recycle_memory` 錯誤,反而是最好的反面教材——**只有真正理解記憶體佈局(代碼段/堆/棧),才能寫出正確的 `malloc`/`free` 配對**。建議動手修復該 Bug,並嘗試實作「級別 3」全動態版本,徹底掌握雙重指標與字串深拷貝。