初學 C 語言時編寫「猜拳(石頭剪刀布)」遊戲是經典練習。許多初學者會遇到一個令人困惑的問題:**明明在函數裡計算了勝負並返回分數,為什麼總分永遠不對?** 本文剖析「函數局部變量隨調用重置」的核心成因,並給出正確的跨函數積分累計方案。
// 錯誤版本:compare 函數內部計分
int compare(int x, int y) {
int j = 0; // ❌ 關鍵錯誤:每次調用函數,j 都重新初始化為 0!
if (x < 3 && x > 0) {
if (y - x == 0) {
printf("平手!\n");
} else if (y - x == 1 || x - y == 1) { // 差值為 1
if (x > y) { printf("你贏了!\n"); j++; }
else { printf("電腦贏了!\n"); j--; }
} else if (y - x == 2 || x - y == 2) { // 差值為 2
if (x > y) { printf("電腦贏了!\n"); j--; }
else { printf("你贏了!\n"); j++; }
}
} else {
printf("輸入錯誤!\n");
}
return j; // 返回單局得分:+1, 0, -1
}
問題本質: j 是 compare 的**自動局部變量**,存儲在棧幀中。每次 main 迴圈調用 compare,棧幀重建,j 重置為 0。函數返回的只是**單局得分**,而非累計總分。
// 正確版本:compare 只返回單局結果
int compare(int player, int computer) {
if (player < 1 || player > 3) return -99; // 錯誤碼
int diff = computer - player; // 1:剪刀 2:石頭 3:布
if (diff == 0) return 0; // 平手
if (diff == 1 || diff == -2) return -1; // 電腦贏
return 1; // 玩家贏
}
int main() {
srand((unsigned)time(NULL)); // 種子
int total_score = 0; // ✅ 總分變量在 main 中,生命週期覆蓋整個遊戲
int rounds = 5;
for (int i = 0; i < rounds; i++) {
int player, computer;
printf("請出拳 (1剪刀 2石頭 3布): ");
scanf("%d", &player);
computer = rand() % 3 + 1;
printf("電腦出: %d\n", computer);
int round_score = compare(player, computer);
if (round_score == -99) { printf("輸入無效!\n"); i--; continue; }
total_score += round_score; // ✅ 在 main 累加
printf("本局得分: %d, 總分: %d\n", round_score, total_score);
}
if (total_score > 0) printf("最終你贏了!");
else if (total_score < 0) printf("最終電腦贏了!");
else printf("最終平手!");
return 0;
}
| 變量 | 定義位置 | 存儲區 | 生命週期 | 作用域 | 能否跨調用保持值 |
|---|---|---|---|---|---|
j (錯誤版) | 函數內部 | 棧 | 單次函數調用 | 函數內部 | ❌ 每次調用重置 |
total_score (正確版) | main 內部 | 棧 | 整個 main 執行期間 | main 內部 | ✅ 迴圈內持久保持 |
靜態局部變量 static int j | 函數內部 | 全域/靜態區 | 程序整個運行期間 | 函數內部 | ✅ 但破壞函數純粹性,不推薦 |
| 全域變量 | 函數外部 | 全域/靜態區 | 程序整個運行期間 | 全文件可見 | ✅ 但污染命名空間,不推薦 |
隨著遊戲複雜度增加(如多玩家、歷史記錄、不同規則),建議引入狀態結構體:
typedef struct {
int player_score;
int computer_score;
int round;
int history[100]; // 記錄每局結果
} GameState;
void play_round(GameState *state, int player_move) {
// ... 邏輯 ...
state->history[state->round++] = round_result;
// 更新分數
}
這樣實現了狀態與邏輯分離,便於單元測試、存檔讀檔、多實例運行。
「函數內部計分變量重置」是 C 語言初學者**對變量存儲類別(自動 vs 靜態)與作用域理解不足**的典型表現。記住鐵律:**數據的所有權歸屬於負責其生命週期的函數/模塊**。主迴圈擁有遊戲總分,子函數只負責單次邏輯判斷,這是結構化程式設計的基石。