我读到了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
也不会丢弃新的一行吗?
答案 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
将最多一个输入字符推回到输入流上。因此,strtod
,strtol
等可接受的某些序列对fscanf
是不可接受的。
286)c
,s
和[
使用的匹配规则中没有对多字节字符做出特殊规定 转换说明符 - 输入字段的范围是逐字节确定的。该 然而,结果字段是从初始移位状态开始的多字节字符序列。
%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()
来完成这样的工作,那么必须分三步读取每一行,如图所示,因为每一行都会产生匹配的失败,这会阻止任何尝试匹配后续项目
另请注意,在该示例中,最大字段宽度如何与缓冲区大小匹配,原始代码无法正确执行。