要點3:輸入函式對比與自定義輸入方式
阿新 • • 發佈:2020-08-18
# 讀取輸入的方式
相關函式原型(從控制檯獲取輸入,不考慮寬字元):
```c
int scanf( const char *format, ... );
int getchar(void);
char *gets( char *str );
char *gets_s( char *str, rsize_t n );
char *fgets( char *str, int count, FILE *stream );
```
- scanf
- 如果解析錯誤,內容繼續留在緩衝區供下次使用;
- 解析失敗返回0,成功返回解析的引數個數,不會超過佔位符個數,讀到檔案尾返回EOF(-1);
- 讀取字串,一次只能讀取一個詞,不能用scanf讀取一行;
- getchar
- 可以讀取到換行符;
- 常用於暫停程式,或丟棄緩衝區剩餘字元;
- gets
- 讀取一行,遇到換行符,直接丟棄換行符;
- 會自動在字串末尾新增`\0`;
- 返回字串指標,讀取失敗返回null;
- gets_s
- 可以設定讀取的字串長度;
- 讀到換行符,將換行符丟棄;
- 如果讀取到最大字元數,還沒有讀取到換行符或檔案結尾,讀取並丟棄隨後的輸入直至遇到換行符或EOF;
- 返回字串指標,讀取失敗返回null;
- c11的可選函式
- fgets
- 可以設定讀取的字串長度;
- 讀到換行符不丟棄存到數組裡;
- 不會自動清除行緩衝區剩餘資料;
- 返回字串指標,讀取失敗返回null;
# scanf
該函式可以從標準輸入讀取內容,返回值為讀取的引數個數,例如:
```c
#include
int main()
{
int seed;
printf("%d\n", scanf("%d %d", &seed, &seed));
}
```
執行程式,輸入 兩個整數,列印為 2,測試輸入3個值仍然列印2,是因為這個"%d %d"指定了只解析兩個int,多餘的將留在緩衝區中,如果後面再寫一個scanf,將從緩衝區中繼續解析。
現在多加一個`scanf`:
```c
#include
int main()
{
int seed;
printf("%d\n", scanf("%d %d", &seed, &seed));
printf("%d\n", scanf("%d %d", &seed, &seed));
}
```
## case1:讀取到檔案尾部返回EOF
```
1
```
輸出:
```
1
-1
```
scanf從緩衝區中解析,返回解析成功的引數個數,因為只有一個1,所以第一個給scanf解析,第一行列印1,第二行解析的時候因讀取到了檔案結束表示`EOF`返回-1。
## case2:解析失敗返回0
```
f
```
輸出:
```
0
0
```
這說明,解析失敗的內容還留在緩衝區給下次scanf用,所以兩個scanf都返回的0。
因為無法解析的值會繼續留在緩衝區供下次使用,所以如果是迴圈`scanf`,程式就會跑飛,讓你沒有輸入的機會,可以使用綜上一節提供的示例測試一下,執行後直接輸入`f`。
## case3:返回值最大為佔位符個數
```
1 2 3 4 5 6 7
```
輸出:
```
2
2
```
這表明,返回值最大是佔位符的個數,剩下的內容還留在緩衝區。
## 綜上
`scanf`判斷輸入結束,只能在檔案輸入模式下利用`EOF`判斷,例如:
```c
// qwer.c
#include
int main()
{
int a;
while(scanf("%d", &a) != EOF)
{
printf("%d\n", a);
}
return 0;
}
```
輸入檔案`test.txt`內容:
```
1
2
3
4
```
編譯: `gcc qwer.c -o main -std=c11`。
執行:`./main < test.txt`。
# getchar
這個函式可以從輸入緩衝區僅讀取一個字元,返回int,後面結合`fgets`使用。
# gets
在讀取字串時,`scanf()`和轉換說明`%s`只能讀取一個單詞,可是程式中經常要讀取一整行輸入。`gets`函式簡單易用,它讀取整行輸入,直到遇到換行符,然後丟棄換行符,儲存其餘字元,並在這些字元的末尾新增一個空字元使其成為一個c字串。它經常和`puts`函式配對使用,該函式用於顯示字串,並在末尾新增換行符。
```c
#include
#define STLEN 81
int main()
{
char words[STLEN];
puts("Enter a string, please.");
gets(words); // 典型用法
printf("Your string twice:\n");
printf("%s\n", words);
puts(words);
puts("Done.");
return 0;
}
```
`printf("%s\n", words);`和`puts(words);`效果相同,但是編譯的時候會產生警告,因為`gets`讀取整行輸入,並不知道`words`能存多少,如果輸入字串過長,會導致緩衝區溢位。
例如將`STLEN`設定成`5`,程式依然可以執行,嘗試輸入過長的資料就可能會發成溢位,最直觀的就是可以看到發生段溢位後程序異常退出。
# gets_s
該函式是c11才有的,且為拓展函式,使用方式除了可以設定讀取的字元數之外和`gets`函式用法一樣。
# fgets
這個函式除了可以從標準輸入讀取字串之外,還可以從檔案中讀取,而且可以指定讀取字元個數,比`gets_s`更加靈活易用,利用`fgets`,但是`fgets`不會自動丟棄超過字元個數之外的行緩衝區資料,所以要配合`getchar`將剩餘的緩衝資料丟棄,否則可能造成程式執行以異常。
# s_gets【自定義輸入】
為滿足以下幾點編寫自定義輸入:
- 從標準輸入讀取資料;
- 能夠指定讀取字元個數;
- 丟棄換行符;
- 丟棄行緩衝區剩餘資料;
```c
char *s_gets(char *str, int n)
{
char *ret_var;
int i = 0;
ret_var = fgets(str, n, stdin);
if(ret_var)
{
while(str[i] != '\n' && str[i] != '\0')
{
i++;
}
if(str[i] == '\n')
str[i] = '\0';
else
while(getchar() != '\n')
continue;
}
return ret_var;
}
```
使用方式
```c
#include
#define STLEN 8
int main()
{
char words[STLEN];
while(s_gets(words, STLEN) && words[0] != '\n') // 沒有輸入資料會自動退出程式
{
puts(words);
}
return 0;
}
```