scanf函数,说明符%s和新行

时间:2015-01-28 16:11:25

标签: c

我读到了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也不会丢弃新的一行吗?

3 个答案:

答案 0 :(得分:4)

%s消耗所有直到一个空格字符,并丢弃前导空白字符而不是尾随字符。第二个[中的scanf转换说明符不会跳过前导空白字符,因此由于第一个scanf遗留的换行符(即空白字符)而无法扫描

要解决此问题,请使用

int c;
while((c=getchar())!='\n' && c!=EOF);

在第一个scanf之后清除stdin或在第二个%[中的格式说明符(scanf)之前添加空格。

答案 1 :(得分:4)

  

所以%s也不会丢弃新的一行?

%s告诉scanf弃置任何前导空格,包括换行符。然后它将读取任何非空白字符,在输入缓冲区中留下任何尾随空格。

因此,假设您的输入流看起来像"\n\ntest\n"scanf("%s", buf)将丢弃两个前导换行符,使用字符串"test",并在输入流中保留尾随换行符,因此调用输入流看起来像"\n"

修改

在此处回复xdevel2000的评论。

让我们谈谈转换说明符的工作原理。以下是online C 2011 standard

中的一些相关段落
7.21.6.2 fscanf功能
...
9除非规范包含n说明符,否则从流中读取输入项。输入项被定义为输入字符的最长序列,其不超过任何指定的字段宽度,并且是匹配输入序列的前缀,或者是匹配输入序列的前缀。 285) 输入项目之后的第一个字符(如果有)仍未读取。如果输入项的长度为零,则指令的执行失败;此条件是匹配失败,除非文件结束,编码错误或读取错误阻止了流的输入,在这种情况下,它是输入失败。

10除了%说明符的情况外,输入项(或者在%n指令的情况下, 输入字符数)转换为适合转换说明符的类型。如果输入项不是匹配序列,则指令的执行失败:此条件是匹配失败。除非*指示赋值抑制,否则转换的结果将放在第一个参数所指向的对象中 尚未收到转换结果的format参数。如果此对象没有合适的类型,或者无法在对象中表示转换结果,则行为未定义。

12转换说明符及其含义如下:
...
c匹配字段宽度指定的字符序列(如果指令中没有字段宽度,则为1)。 286)
...
s匹配一系列非空白字符。 286)
...
[匹配一组预期字符的非空字符序列 ( scanset )。 286)
...
285)fscanf将最多一个输入字符推回到输入流上。因此,strtodstrtol等可接受的某些序列对fscanf是不可接受的。

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

%s匹配一系列非空白字符。这是一个描述其工作原理的基本算法(不考虑文件结束或其他异常条件):

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

因此,将任何转换说明符描述为“保留”尾随空格(这意味着将尾随空格保存到目标缓冲区)是错误的;事实并非如此。尾随空格留在输入流中,以便下一个要读取的输入操作。

答案 2 :(得分:1)

您对标准的摘录省略了重要的背景。前面的文本指定在处理c[n以外的类型的转换说明符时,跳过空格是第一步

除了n说明符之外,下一步是读取一个输入项,该输入项被定义为“输入字符的最长序列,它不超过任何指定的字段宽度,是,或者是匹配输入序列的前缀“(引自C99,但等价于C2011)。

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

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