scanf函数,指定符%s和新行
我读到了C11标准:
输入空白字符(由isspace函数指定)将被跳过,除非指定包含[,c或n指定符]。
所以我理解,如果我使用那些说明符,下一个scanf
可以包含例如一个新行。
但如果我写这个:
char buff[5 + 1]; printf("Input: "); scanf("%10s", buff); printf("Input: "); char buff_2[5 + 1]; scanf("%[abcde]", buff_2);
然后我输入,即RR
然后返回 ,下一个scanf
因为\n
失败。
那么%s
也不会丢弃一条新线路?
那么%s也不会丢弃一条新线路?
%s
告诉scanf
丢弃任何前导空格,包括换行符。 然后它将读取任何非空白字符,在输入缓冲区中留下任何尾随空格。
因此,假设您的输入流看起来像"\n\ntest\n"
, scanf("%s", buf)
将丢弃两个主要换行符,使用字符串"test"
,并在输入流中保留尾随换行符,因此在调用之后,输入流看起来像"\n"
。
编辑
在这里回应xdevel2000的评论。
我们来谈谈转换说明符的工作原理。 以下是在线C 2011标准中的一些相关段落:
7.21.6.2 fscanf函数
…
9除非规范包含n说明符,否则将从流中读取输入项。 输入项被定义为输入字符的最长序列,其不超过任何指定的字段宽度,并且是匹配的输入序列的前缀,或者是匹配的输入序列的前缀。 285)输入项目保持未读后的第一个字符(如果有)。 如果输入项的长度为零,则指令的执行失败; 除非文件结束,编码错误或读取错误阻止了流的输入,否则此条件是匹配失败,在这种情况下,它是输入失败。10除了
%
说明符之外,输入项(或者,在%n
指令的情况下,输入字符的数量)将转换为适合转换说明符的类型。 如果输入项不是匹配序列,则指令的执行失败:此条件是匹配失败。 除非用*
指示赋值抑制,否则转换的结果将放在由尚未收到转换结果的format参数后面的第一个参数指向的对象中。 如果此对象没有适当的类型,或者无法在对象中表示转换结果,则行为未定义。12转换说明符及其含义是:
…
c
匹配字段宽度指定的字符序列(如果指令中不存在字段宽度,则为1)。 286)
…
s
匹配一系列非空白字符。 286)
…
[
匹配一组预期字符( 扫描集 )中的非空字符序列。 286)
…
285)
fscanf
将最多一个输入字符推回到输入流上。 因此,strtod
,strtol
等可接受的一些序列对于fscanf
是不可接受的。286)对
c
,s
和[
转换说明符]使用的匹配规则中的多字节字符没有特殊规定 – 输入字段的范围是逐字节确定的。 然而,结果字段是从初始移位状态开始的多字节字符序列。
%s
匹配一系列非空白字符。 这是一个描述其工作原理的基本算法(不考虑文件结尾或其他exception条件):
c <- next character from input stream while c is whitespace c <- next character from input stream while c is not whitespace append c to target buffer c <- next character from input stream push c back onto input stream append 0 terminator to target buffer
非空白字符(如果有)之后的第一个空白字符被推回到输入流上,以便下一个要读取的输入操作。
相比之下, %c
说明符的算法很简单(除非你使用的字段宽度大于1,我从来没有做过,也不会进入这里):
c <- next character from input stream write c to target
%[
转换说明符的算法有点不同:
c <- next character from input stream while c is in the list of characters in the scan set append c to target buffer c <- next character from input stream append 0 to target buffer push c back onto input stream
因此,将任何转换说明符描述为“保留”尾随空格(这意味着将尾随空格保存到目标缓冲区)是错误的; 事实并非如此。 尾随空格留在输入流中,供下一个要读取的输入操作使用。
%s
消耗所有内容直到空格字符并丢弃前导空格字符而不是尾随字符。 第二个scanf
中的[
转换说明符不会跳过前导空格字符,因此由于第一个scanf
遗留的换行符(这是一个空白字符)而无法扫描。
要解决此问题,请使用
int c; while((c=getchar())!='\n' && c!=EOF);
在第一个scanf
之后清除stdin
或在第二个scanf
的格式说明符( %[
)之前添加一个空格。
您从标准中摘录省略了重要的背景。 前面的文本指定跳过空格是处理c
, [
或n
以外的类型的转换说明符的第一步 。
除了n
指定符之外,下一步是读取输入项,该输入项被定义为“输入字符的最长序列,其不超过任何指定的字段宽度,并且是匹配输入的前缀,或者是匹配输入的前缀序列“(引自C99,但等同于C2011)。
一个项目“[m]处理一系列非空格字符”,因此对于您指定的输入,第一个scanf()
读取所有内容,但不包括换行符。
标准明确指定
除非与指令匹配,否则尾部空格(包括换行符)将保持未读状态。
所以新线在这一点上肯定仍然没有被扫描。
给下一个scanf()
的格式以%[
转换说明符开头,正如您已经观察到的那样,它不会导致跳过空格(前导或其他),尽管它可以在扫描的项目中包含空格。 但是,由于输入中的下一个字符是换行符,并且%[
给定的扫描集%[
不包含该字符,因此会扫描该字符的零字符。 回到标准(C99,再次):
如果输入项的长度为零,则指令的执行失败; 除非文件结束,编码错误或读取错误阻止了流的输入,否则此条件是匹配失败,在这种情况下,它是输入失败。
有更简单的方法可以逐行读取自由格式输入,但如果必须,可以使用scanf()
。 例如:
char buff[10 + 1] = {0}; printf("Input: "); /* * Ignore leading whitespace and scan a string of up to 10 non-whitespace * characters. Zero-length inputs will produce a matching failure, leaving * the buffer unchanged (and initialized to an empty string). End of * input will produce an input error, which is ignored. */ scanf("%10s", buff); /* Scan and ignore anything else up to a newline. There will * be an (ignorable) matching failure if the next available character is a * newline. Any input error generated by this call is also ignored. */ scanf("%*[^\n]"); /* * Consume the next character, if any. If there is one, it will be a * newline. An input error will occur if we're already at the end of stdin; * a careful program would test for that (by comparing the return value to * EOF) but this one doesn't. */ scanf("%*c"); printf("Input: "); /* scan the second string; again, we're ignoring matching and input errors */ char buff_2[5 + 1] = {0}; scanf("%5[abcde]", buff_2);
如果你只是将scanf()
用于这样的工作,那么必须分三步读取每一行,如图所示,因为每一行都会产生匹配的失败,这会阻止任何尝试匹配后续项目。
另请注意,在该示例中,最大字段宽度如何与缓冲区大小匹配,原始代码未正确执行。