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将最多一个输入字符推回到输入流上。 因此, strtodstrtol等可接受的一些序列对于fscanf是不可接受的。

286)对cs[转换说明符]使用的匹配规则中的多字节字符没有特殊规定 – 输入字段的范围是逐字节确定的。 然而,结果字段是从初始移位状态开始的多字节字符序列。

%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()用于这样的工作,那么必须分三步读取每一行,如图所示,因为每一行都会产生匹配的失败,这会阻止任何尝试匹配后续项目。

另请注意,在该示例中,最大字段宽度如何与缓冲区大小匹配,原始代码未正确执行。