要点3:输入函数对比与自定义输入方式
读取输入的方式
相关函数原型(从控制台获取输入,不考虑宽字符):
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
该函数可以从标准输入读取内容,返回值为读取的参数个数,例如:
#include <stdio.h>
int main()
{
int seed;
printf("%d\n", scanf("%d %d", &seed, &seed));
}
运行程序,输入 两个整数,打印为 2,测试输入3个值仍然打印2,是因为这个"%d %d"指定了只解析两个int,多余的将留在缓冲区中,如果后面再写一个scanf,将从缓冲区中继续解析。
现在多加一个scanf
:
#include <stdio.h>
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
判断,例如:
// qwer.c
#include <stdio.h>
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
函数配对使用,该函数用于显示字符串,并在末尾添加换行符。
#include <stdio.h>
#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【自定义输入】
为满足以下几点编写自定义输入:
- 从标准输入读取数据;
- 能够指定读取字符个数;
- 丢弃换行符;
- 丢弃行缓冲区剩余数据;
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;
}
使用方式
#include <stdio.h>
#define STLEN 8
int main()
{
char words[STLEN];
while(s_gets(words, STLEN) && words[0] != '\n') // 没有输入数据会自动退出程序
{
puts(words);
}
return 0;
}