教師工資計算程序中反覆出現的輸入驗證代碼,暴露了scanf家族函數的經典陷阱:非數字輸入導致緩衝區殘留、無限循環、程序假死。本文深度解析根因,並提供可復用的通用輸入驗證方案。
scanf("%d", &n)返回成功匹配並賦值的項目數,而非讀取字符數0(匹配失敗),緩衝區內「abc」殘留1(匹配成功123),緩衝區內「abc」殘留while (scanf("%d", &n) != 1) {
// 輸入abc後,scanf反覆讀取同一個殘留的abc,永遠返回0
// 程序陷入無限循環,CPU佔用100%
}
%d、%f自動跳過空白字符(含換行)%c、%[...] 不跳過空白字符,直接讀取殘留換行符核心原則:先用fgets讀整行,再用sscanf/strtol解析,徹底隔離輸入與解析。
#include
#include
#include
#include
#include
#include
/**
* 讀取並驗證範圍內的整數
* @param prompt 提示信息
* @param min 最小值(含)
* @param max 最大值(含)
* @return 合法整數,用戶強制退出返回INT_MIN
*/
int readInt(const char* prompt, int min, int max) {
char buf[64];
while (1) {
printf("%s", prompt);
if (!fgets(buf, sizeof(buf), stdin)) return INT_MIN; // EOF
// 去除尾部換行符
buf[strcspn(buf, "\n")] = '\0';
// 空輸入檢查
if (strlen(buf) == 0) {
printf("輸入不能為空,請重新輸入。\n");
continue;
}
// 字符合法性檢查:可選符號 + 純數字
char* p = buf;
if (*p == '+' || *p == '-') p++;
if (*p == '\0') { // 只有符號
printf("格式錯誤:請輸入整數。\n");
continue;
}
for (; *p; p++) {
if (!isdigit(*p)) {
printf("格式錯誤:包含非數字字符。\n");
goto next_loop;
}
}
// 範圍解析(防溢出)
errno = 0;
long val = strtol(buf, NULL, 10);
if (errno == ERANGE || val < min || val > max) {
printf("範圍錯誤:請輸入 %d 到 %d 之間的整數。\n", min, max);
continue;
}
return (int)val;
next_loop:
continue;
}
}
int nDuty = readInt("輸入老師值班天數(5-22):", 5, 22);
int nInvigilate = readInt("輸入老師監考次數(0-10):", 0, 10);
int nClass = readInt("輸入老師上課次數(0-30):", 0, 30);
// 雙精度浮點數
double readDouble(const char* prompt, double min, double max) {
char buf[64];
while (1) {
printf("%s", prompt);
if (!fgets(buf, sizeof(buf), stdin)) return NAN;
buf[strcspn(buf, "\n")] = '\0';
errno = 0;
char* endptr;
double val = strtod(buf, &endptr);
if (endptr == buf || *endptr != '\0' || errno == ERANGE || val < min || val > max) {
printf("格式或範圍錯誤,請輸入 %.2f 到 %.2f 之間的數字。\n", min, max);
continue;
}
return val;
}
}
// 非空字符串(去除首尾空白)
char* readNonEmptyString(char* buf, size_t size, const char* prompt) {
while (1) {
printf("%s", prompt);
if (!fgets(buf, size, stdin)) return NULL;
// 去除換行
buf[strcspn(buf, "\n")] = '\0';
// 去除首尾空白
char* start = buf;
while (isspace(*start)) start++;
char* end = start + strlen(start) - 1;
while (end > start && isspace(*end)) *end-- = '\0';
if (*start != '\0') {
memmove(buf, start, end - start + 2);
return buf;
}
printf("輸入不能為空。\n");
}
}
scanf無法健壯處理異常輸入掌握這套模式,不僅能解決教師工資計算等作業題,更能應對嵌入式、系統編程中嚴苛的輸入健壯性要求。