文章目錄
- 問題背景
- %d和%c讀取緩沖區的差別
- 清空緩存區
問題背景
在寫C語言的命令行程序時,我們經常會用到用戶輸入和標準輸出,特別的,當用戶輸入后,我們發現程序運行不是我們要的樣子,這個時候,很可能就是輸入緩存區的問題。比如下面這個程序:
//alarm.c
#include <stdio.h>
#include "alarm.h"int main()
{int is_quit = 0;while (is_quit == 0){printf("請輸入報警編號(輸入`q`退出): ");char alarm_id = '\0';scanf("%c", &alarm_id);if (alarm_id == 'q'){is_quit = 1;break;}alarm_id -= '0';switch ((int)alarm_id){case UPPER_LIMIT_ABS_ALARM:upper_limit_abs_alarm_handler(0, 0);break;case LOWER_LIMIT_ABS_ALARM:lower_limit_abs_alarm_handler(0, 0);break;case UPPER_LIMIT_ABS_HOLD_ALARM:upper_limit_abs_hold_alarm_handler(0, 0);break;case LOWER_LIMIT_ABS_HOLD_ALARM:lower_limit_abs_hold_alarm_handler(0, 0);break;default:break;}}return 0;
}
附alarm.h
//alarm.h
/* 報警類型枚舉 */
enum alarm_type
{NO_ALARM, /* 無報警 */UPPER_LIMIT_ABS_ALARM, /* 上限絕對值報警 */LOWER_LIMIT_ABS_ALARM, /* 下限絕對值報警 */UPPER_LIMIT_ABS_HOLD_ALARM, /* 上限絕對值報警帶保持功能 */LOWER_LIMIT_ABS_HOLD_ALARM, /* 下限絕對值報警帶保持功能 */
};/** 上限絕對值報警** @param limit 上限* @param process_value 被檢測的值* @return 1: 超過上限, 0: 未超過*/
int upper_limit_abs_alarm(int limit, int process_value)
{if (process_value > limit){return 1;}return 0;
}/** 下限絕對值報警** @param limit 下限* @param process_value 被檢測的值* @return 1: 低于下限, 0: 未低于*/
int lower_limit_abs_alarm(int limit, int process_value)
{if (process_value < limit){return 1;}return 0;
}
/** 上限絕對值報警帶保持功能:所謂保持功能,是指接通電源后,測量值* 即使在報警范圍內,也不立即使報警打開,待離開報警范圍并再次進入* 報警范圍后,才會發出報警。** @param limit 上限* @param process_value 被檢測的值* @return 1: 超過上限, 0: 未超過*/
int upper_limit_abs_hold_alarm(int limit, int process_value)
{static int alarm_status = 0;if (alarm_status == 0){if (process_value > limit){alarm_status = 1;}}if (alarm_status == 1){if (process_value > limit){return 1;}}return 0;
}
/** 下限絕對值報警帶保持功能:所謂保持功能,是指接通電源后,測量值* 即使在報警范圍內,也不立即使報警打開,待離開報警范圍并再次進入* 報警范圍后,才會發出報警。** @param limit 下限* @param process_value 被檢測的值* @return 1: 低于下限, 0: 未低于*/
int lower_limit_abs_hold_alarm(int limit, int process_value)
{static int alarm_status = 0;if (alarm_status == 0){if (process_value < limit){alarm_status = 1;}}if (alarm_status == 1){if (process_value < limit){return 1;}}return 0;
}void upper_limit_abs_alarm_handler(int upper_limit, int process_value)
{printf("請輸入報警上限:");scanf("%d", &upper_limit);printf("請輸入被檢測的值:");scanf("%d", &process_value);if (upper_limit_abs_alarm(upper_limit, process_value)){printf("超過上限\n");}
}
void lower_limit_abs_alarm_handler(int lower_limit, int process_value)
{printf("請輸入報警下限: ");scanf("%d", &lower_limit);printf("請輸入被檢測的值: ");scanf("%d", &process_value);if (lower_limit_abs_alarm(lower_limit, process_value)){printf("低于下限\n");}
}
void upper_limit_abs_hold_alarm_handler(int upper_limit, int process_value)
{printf("請輸入報警上限: ");scanf("%d", &upper_limit);printf("請輸入被檢測的值: ");scanf("%d", &process_value);if (upper_limit_abs_hold_alarm(upper_limit, process_value)){printf("超過上限\n");}
}
void lower_limit_abs_hold_alarm_handler(int lower_limit, int process_value)
{printf("請輸入報警下限: ");scanf("%d", &lower_limit);printf("請輸入被檢測的值: ");scanf("%d", &process_value);if (lower_limit_abs_hold_alarm(lower_limit, process_value)){printf("低于下限\n");}
}
這個程序主要時根據用戶輸入的報警編號和實際值,確認輸出報警信息:“超過上限”,“低于下限”等,程序運行起來后,發現用戶輸入實際值后,循環執行了兩次:
請輸入報警編號(輸入`q`退出): 1
請輸入報警上限:30
請輸入被檢測的值:40
超過上限
請輸入報警編號(輸入`q`退出): 請輸入報警編號(輸入`q`退出):
這個問題就是由于用的scanf接收%c的輸入導致。具體就是:我們循環使用scanf()的時候,如果輸入緩沖區還有數據的話,那么scanf()就不會詢問用戶輸入,而是直接就將輸入緩沖區的內容拿出來用了,這就導致了前面的錯誤影響到后面的內容
%d和%c讀取緩沖區的差別
對于 %d,在緩沖區中,空格、回車、Tab 鍵都只是分隔符,不會被 scanf 當成數據取用。%d 遇到它們就跳過,取下一個數據。但是如果是 %c,那么空格、回車、Tab 鍵都會被當成數據輸出給 scanf 取用,例如下面這個程序:
# include <stdio.h>
int main(void)
{int a, c;char b;scanf("%d%c%d", &a, &b, &c);printf("a = %d, b = %c, c = %d\n", a, b, c);return 0;
}
輸出如下:
1 5 6
a = 1, b = , c = 5
解決這個%c的問題,方法有兩個:
- 既然不想將字符’ ’ 賦給變量 b,那么就先定義一個字符變量 ch,然后用 scanf 將字符 ’ ’ 取出來給變量 ch;
# include <stdio.h>
int main(void)
{int a, c;char b;char ch;scanf("%d%c%d", &a, &b, &ch, &c);printf("a = %d, b = %c, c = %d\n", a, b, c);return 0;
}
- 直接清空輸入緩沖區。
顯然方法二是最簡潔的,而且也是通用的。
清空緩存區
清空緩存區的方法也有多種。
第一種:使用 getchar 循環清空緩沖區。但是這個位置比較關鍵,到底寫到哪里比較好。如果對于我們開頭提到的程序,如果直接寫到scanf函數的后面:
#include <stdio.h>
#include "alarm.h"int main()
{int is_quit = 0;while (is_quit == 0){printf("請輸入報警編號(輸入`q`退出): ");char alarm_id = '\0';scanf("%c", &alarm_id);// 清空緩沖區int c;while ((c = getchar()) != '\n' && c != EOF);if (alarm_id == 'q'){is_quit = 1;break;}alarm_id -= '0';switch ((int)alarm_id){case UPPER_LIMIT_ABS_ALARM:upper_limit_abs_alarm_handler(0, 0);break;case LOWER_LIMIT_ABS_ALARM:lower_limit_abs_alarm_handler(0, 0);break;case UPPER_LIMIT_ABS_HOLD_ALARM:upper_limit_abs_hold_alarm_handler(0, 0);break;case LOWER_LIMIT_ABS_HOLD_ALARM:lower_limit_abs_hold_alarm_handler(0, 0);break;default:break;}}return 0;
}
這個運行結果:
請輸入報警編號(輸入`q`退出): 1
請輸入報警上限:30
請輸入被檢測的值:40
超過上限
請輸入報警編號(輸入`q`退出): 2
請輸入報警編號(輸入`q`退出): 2
請輸入報警下限:
第二次輸入報警編號后,又執行了一次循環體。這個就不對了。原因在于,后面的對于輸入的處理之前就清空了緩存區,清空早了,應該在下次scanf之前進行清空,也就是要放到數據處理之后,放到最后:
#include <stdio.h>
#include "alarm.h"int main()
{int is_quit = 0;while (is_quit == 0){printf("請輸入報警編號(輸入`q`退出): ");char alarm_id = '\0';scanf("%c", &alarm_id);if (alarm_id == 'q'){is_quit = 1;break;}alarm_id -= '0';switch ((int)alarm_id){case UPPER_LIMIT_ABS_ALARM:upper_limit_abs_alarm_handler(0, 0);break;case LOWER_LIMIT_ABS_ALARM:lower_limit_abs_alarm_handler(0, 0);break;case UPPER_LIMIT_ABS_HOLD_ALARM:upper_limit_abs_hold_alarm_handler(0, 0);break;case LOWER_LIMIT_ABS_HOLD_ALARM:lower_limit_abs_hold_alarm_handler(0, 0);break;default:break;}// 清空緩沖區int c;while ((c = getchar()) != '\n' && c != EOF);}return 0;
}
這樣程序運行就正常了。
第二種清空緩存區的方法是:使用 fflush(stdin),但是在某些編譯器(如 Windows 的 GCC)中,可以使用 fflush(stdin) 清空輸入緩沖區。但此方法并非標準 C 的一部分,可能在其他平臺上無法正常工作。
推薦采用第一種方法。