前言
“防禦性編程超進化”,標題似乎有點中二,但是這更能體現我苦思冥想終於解決這一問題時激動的心情,索性就不改了。
注:我終於還是把名字改了,閱讀數量實在是太低了,標題也沒有說出這篇博文的關鍵信息。
目的和目標
相信大家有有過這種體驗,我希望用戶輸入數字,用戶偏要輸入一個英文字符或其他標點符號,成功導致程序崩潰。這個用戶也許是自己,也許是其他測試人員,也許是老師等等。
今天我給大家介紹一下我關於防禦性編程的一點體會和心得。下面的代碼是一個遞進的過程,不斷更新後一定程度上已經可以正確獲取用戶輸入的0到9以內的數字,而不會使程序崩潰,提高了程序的健壯性。
近期在學軟件構造,學到了防禦性編程的概念,應用到了數據結構課程設計中,在此記錄。
C語言程序中常常使用用戶輸入的整數確定程序調用的功能模塊,但用戶輸入的選項多種多樣,你讓用戶輸入一個整數,用戶也許會輸入一串字符,這常常會引起程序的崩潰,現在分享一下我的解決思路,“請慢用”。
程序
測試數據有:{1, 2, 3, 4, 。, 3a, a3, 0} (共八個測試數據)。
第一次嘗試
- 剛開始時,我們通過scanf("%d", &option);獲取用戶的選項。
#include <stdio.h>
int main (void)
{
int option; // 選項
int endFlag = 0; // 結束標誌,1退出程序,0繼續程序
while(1) {
scanf("%d", &option);
switch (option) {
case 1: printf("功能1\n");break;
case 2: printf("功能2\n");break;
case 3: printf("功能3\n");break;
case 0: printf("退出\n");endFlag=1;break;
default :break;
}
if (endFlag != 0) {
break;
}
}
return 0;
}
測試結果:
可以看出,輸入“。”後程序進入無限循環,重複輸出“功能3”,程序出錯。
第二次嘗試
- 之後的簡單演變不說了,跳到近期實現的獲取選項的代碼,單獨定義一個函數用於輸入選項。
#include <stdio.h>
int selectOption(int smaller, int bigger);
int main (void)
{
int option; // 選項
int endFlag = 0; // 結束標誌,1退出程序,0繼續程序
while(1) {
option = selectOption(0,3); // 取值0,1,2,3
switch (option) {
case 1: printf("功能1\n");break;
case 2: printf("功能2\n");break;
case 3: printf("功能3\n");break;
case 0: printf("退出\n");endFlag=1;break;
default :break;
}
if (endFlag != 0) {
break;
}
}
return 0;
}
int selectOption(int smaller, int bigger) // 兩個參數表示範圍,要求:smaller < 返回值 < bigger
{
int option = 0;
char cOption;
printf ("請輸入選項(%d~%d):", smaller, bigger);
scanf ("%c", &cOption); // 輸入選擇的操作,字符錄入,防禦性編程
getchar(); // 提取回車符
option = cOption-'0'; // 得出數字
while (option<smaller || option>bigger) // 當輸入的選擇不在smaller到bigger時,重新選擇
{
printf ("該選項不存在,請重新輸入:");
scanf ("%d", &option); //輸入選項
getchar();
}
return option;
}
測試結果:
測試分析:
測試用例 | 預期結果 | 是否符合預期 |
---|---|---|
1 | 功能1 | 是 |
2 | 功能2 | 是 |
3 | 功能3 | 是 |
4 | 重新輸入 | 是 |
。 | 重新輸入 | 否,在下一次輸入時把上次的回車符錄入了 |
3a | 功能3 | 是 |
a3 | 無效輸入 | 否,把a和3分別錄入了 |
0 | 退出 | 是 |
—輸入“a3”,程序判斷選項“a”不存在,並且將“3”當成了下一次循環輸入的字符,所以在原先輸入選項的位置輸出了“功能3”;
第三次嘗試
- 超進化
// 這是防禦性編程超進化
int selectOption(int smaller, int bigger) // 兩個參數表示範圍,要求: smaller < 返回值 < bigger
{
int maxsize = 100;
int option = 0; // 選項
int i;
int length; // 字符串長度
char sOption[maxsize]; // 存儲字符串的數組
int trueOptionFlag = 0;
printf ("請輸入選項(%d~%d):", smaller, bigger);
while (1) {
gets(sOption); // gets() 不獲取回車符,需不需要getchar()一下呢?經驗證不需要,似乎已經處理掉了回車符
// gets()函數用來從標準輸入設備(鍵盤)讀取字符串直到回車結果,但回車不屬於這個字符串,系統自動用'\0'代替最後的換行符。
length = strlen(sOption); // #include <string.h>
if (sOption[0] == '\000') { // 即length=0,也就是隻輸入了回車符,沒有輸入其他字符的情況
continue;
}
// 遍歷一遍字符串,過濾掉無效字符,直到查找到符合條件的字符才停止遍歷,並標記
for (i=0; i<length; i++) {
if (sOption[i] >= '0'+smaller && sOption[i] <= '0'+bigger) {
// 已過濾掉無效字符,你輸入的選項是:...
option = sOption[i]-'0';
trueOptionFlag = 1;
break;
}
}
if (trueOptionFlag != 1) {
printf ("該選項不存在,請重新輸入(%d~%d):", smaller, bigger);
} else {
int j;
if (i > 0) { // 第一個字符無效時輸出
printf("已過濾無效字符:");
for (j=0; j<i; j++) {
putchar(sOption[j]);
}
putchar('\n');
}
printf("你輸入的選項是%d\n", option);
break;
}
}
return option;
}
測試結果:
測試分析:
測試用例 | 預期結果 | 是否符合預期 |
---|---|---|
1 | 功能1 | 是 |
2 | 功能2 | 是 |
3 | 功能3 | 是 |
4 | 選項不存在,重新輸入 | 是 |
。 | 選項不存在,重新輸入 | 是 |
3a | 功能3 | 是 |
a3 | 過濾掉無效字符,功能3 | 是 |
0 | 退出 | 是 |
後記
以上就是我根據對防禦性編程的理解進行的應用實踐了,到這裏就和大家說再見了,如果感覺收穫滿滿,記得贊一個啦;如果有什麼值得改進的地方,歡迎大家留言,謝謝。