C语言scanf匹配用法
不同系统的换行符制定
三大系统的换行符制定不同,如下表所示:
操作系统 | 换行符 | 标识 | 说明 |
---|---|---|---|
Windows | \r\n | CRLF | 回车换行 |
Linux | \n | LF | 换行 |
Mac OS X | \r | CR | 回车 |
在Windows系统,以文本方式读取文件时,CR被抛弃,只读取LF,而以二进制方式读取文件则不对数据做任何处理。
不同系统之间传递文本文件
不同系统之间,由于换行符不同,传递文本文件存在以下问题:
- 在Linux编辑的文本文件,移至Windows之后,多行文本可能显示为单行文本。
对于不同换行符,新版本Windows 10自带的记事本、Nodepad++、Visual Studio等软件可以区分,未出现多行文本显示为单行文本的情况。 - 在Windows编辑的文本文件,旧版本Linux也许会显示^M符号。
- 在Windows编写的shell脚本,移至Linux之后,无法直接执行,需对换行符进行转换。
- 一些编译器无法识别其他系统的换行符,有时出现莫名其妙的问题,若寻找原因未果,可以猜想换行符的问题。
函数scanf参数列表及返回值
第一参数
函数scanf第一参数为格式字符串,由单个或者多个格式说明符组成。格式说明符的形式如下所示:
%[*][width][length]specifier
最后的说明符(specifier)定义提取的字符类型,相关解释以及对应参数类型如下表所示:
说明符 | 描述 | 摘录 |
---|---|---|
[character] | Scanset | 方括号之间,指定任意数量字符,用以反复匹配指定字符,直到遇到未指定字符为止。 |
[^character] | Negated scanset | 方括号之间,尖角符号之后,指定任意数量字符,用以反复匹配非指定字符,直到遇到指定字符为止。 |
格式说明符还包括可选的星号(*)、宽度和长度,遵循以下说明:
子说明符 | 描述 |
---|---|
* | 星号位于%符号之后,表示从流读取数据但将其忽略。 |
width | 指定读取的最大字符数。 |
length | 可选择hh,h,l,ll,j,z,t,L之中一个,限定相应参数所指向的预期存储类型。 |
除n以外的任何说明符应至少匹配一个字符。 否则,匹配失败,扫描到此结束。
可变长参数
根据格式字符串,函数可能需要一系列附加参数,每个参数都是已分配的存储空间地址,传递形式为可变长参数。
附加参数的数量至少与格式字符串指定的存储值数量一样,而忽略其他参数。
返回值
匹配成功时返回读取并存储的项目数。
当匹配失败,读取错误或者越过文件结尾,返回项目数少于预期数量,甚至为零。
若在成功读取任何数据之前,发生三者任何一种情况,则返回EOF(-1)。
输入缓冲区清除残留字符
错误用法
对于清空输入缓冲区,以下列举三种错误用法。
第一种:
fflush(stdin);
函数fflush用于将输出流缓冲区未写的数据写入关联的输出设备。至于输入流,此行为未定义。
对于一些底层库实现,作用于输入流会清空输入缓冲区,但并非可移植的预期行为。
第二种:
rewind(stdin);
函数rewind用于将与流关联的位置指示器设置为文件开头。若执行成功,清除与流关联的文件结尾和错误内部指示符,并且流上先前调用ungetc的所有影响都被丢弃。
但根据底层库实现,对输入流的操作不一定成功,或许本无清空缓冲区功能。
在Visual Studio编译,对标准输入流stdin执行rewind函数确实有清空输入缓冲区的效果。
在Linux用gcc编译,对标准输入流stdin执行rewind函数无清空输入缓冲区的作用。
第三种:
fseek(stdin, 0, SEEK_END);
函数fseek用于将与流关联的位置指示器设置为新位置。执行成功返回零,清除流的文件末尾内部指示符,并且流上先前调用ungetc的所有影响都被丢弃。失败则返回非零值。
对于以二进制模式打开的流,通过由指定参考位置加偏移量来定义新位置。
对于以文本模式打开的流,偏移量应为零或先前调用ftell的返回值,并且参考位置必须为SEEK_SET。
库的实现允许无意义地支持SEEK_END,如果使用其他参数值调用函数,则支持取决于特定的系统和库实现。因此,依赖于真正标准之外的代码不具有可移植性。
正确用法
输入缓冲区清除残留字符的正确用法:
#include <stdio.h>
static int menu()
{
printf("请输入编号:");
int number;
while (scanf("%d%*[^\r\n]%*c", &number) != 1)
{
scanf("%*[^\r\n]%*c");
printf("请输入正确的编号:");
}
return number;
}
int main()
{
printf("选择编号:%d\n", menu());
getchar();
return 0;
}
代码解析
- 第7行:格式说明符%*[^\r\n]用于反复读取并忽略既非\r也非\n的字符,格式说明符%*c用于读取并忽略剩余的\r或者\n,两者联合就能够清除缓冲区一行字符。
当scanf函数返回值等于一时,格式说明符%d匹配成功, 其后的格式说明符%*[^\r\n]和%*c能够清除缓冲区残留字符,避免影响之后的输入操作。
若scanf函数返回值不等于一,格式说明符%d匹配失败,其后的格式说明符未起作用,即输入缓冲区有残留字符。
注意\r对应在Mac OS X终端按Enter键而存放于缓冲区的字符,\n则对应在Windows或者Linux的控制台窗口按Enter键之后存放于缓冲区的字符。
- 第9行:当while循环条件的scanf函数匹配失败,此时输入缓冲区非空,联合运用%*[^\r\n]和%*c,以清除缓冲区残留字符。
- 第18行:若输入缓冲区为空,等待Enter键,从而暂停程序,方便查看执行结果。
指定字符串的输入长度
#include <stdio.h>
int main()
{
char str[9];
/*
* %8s:输入8个非空白字符
* %*[^\r\n]%*c:清除缓冲区剩余字符
*/
scanf("%8s%*[^\r\n]%*c", str);
puts(str);
getchar(); // 等待Enter键,暂停程序,方便查看执行结果
return 0;
}
输入支持含空格的字符串
#include <stdio.h>
int main()
{
char str[9];
/*
* %8[^\r\n]:输入8个非换行符的字符
* %*[^\r\n]%*c:清除缓冲区剩余字符
*/
scanf("%8[^\r\n]%*[^\r\n]%*c", str);
puts(str);
getchar(); // 等待Enter键,暂停程序,方便查看执行结果
return 0;
}
灵活指定输入长度和格式
#include <stddef.h>
#include <stdio.h>
static char format[BUFSIZ];
const char* getStrFormat(size_t size, const char *specifier)
{
snprintf(format, BUFSIZ, "%%%u%s%%*[^\r\n]%%*c", size, specifier);
return format;
}
int main()
{
char string[BUFSIZ];
scanf(getStrFormat(sizeof string - 1, "[^\r\n]"), string);
puts(string);
getchar();
return 0;
}
参考资料
[1] scanf: http://www.cplusplus.com/reference/cstdio/scanf/
[2] fflush: http://www.cplusplus.com/reference/cstdio/fflush/
[3] rewind: http://www.cplusplus.com/reference/cstdio/rewind/
[4] fseek: http://www.cplusplus.com/reference/cstdio/fseek/