英文:
The times when we have to use `scanf("\n")` and `scanf("%*c")`
问题
在C语言中,当我们在一个scanf(""%s"")
或scanf(""%c"")
之前使用scanf(""%s"")
或scanf(""%[^\n]"")
时,在输入时,第一个输入的末尾会多出一个'\n'
,这将传递到第二个输入并干扰输入流。据我所知,我已经尝试了这些代码在两个不同的系统中,使用了相同的gcc编译器,但它们在每次执行时都表现不同。在第一个系统中,我不得不使用scanf(""\n"")
来丢弃换行字符。但在第二个系统中,没有这种需要。它自动丢弃了换行字符。
然后我尝试了三个代码:
代码1:
printf("输入学生的姓名: ");
scanf("%s", name);
printf("输入学生的电子邮件: ");
scanf("%s", email);
这里我不需要忽略换行字符,编译器没有任何问题。
代码2:
printf("输入学生的姓名: ");
scanf("%s", name);
scanf("%*c");
printf("输入学生的电子邮件: ");
scanf("%s", email);
在这里,我只是添加了scanf("%*c")
来丢弃换行字符,这与“代码1”具有相同的结果。但是当我将scanf("%*c")
替换为scanf("\n")
时,输入会再次混乱,我现在必须输入3次,因为它不能正确地丢弃第一个换行字符。
代码3:
char chr, s[100], sen[100];
scanf("%c", &chr);
scanf("\n");
scanf("%s", s);
scanf("\n");
scanf("%[^\n]%*c", sen);
在这里,如果没有scanf("\n")
,代码将无法正常工作。
我真的不明白这里发生了什么。为什么有时我必须转义换行字符,而其他时候则不需要。
英文:
In C, when we use a scanf("%s")
or scanf("%c")
before a scanf("%s")
or scanf("%[^\n]")
, while giving in the input, there will be an extra '\n'
at the end of the first input and that will be passed into the second input and mess up the input stream. I've tried these codes in two different systems with the same gcc compiler as far as I know. But it did different things each time. In the first system I had to use a scanf("\n")
to discard the newline character. But in the second system there was no such need. It discarded the newline character automatically.
Then I tried three codes,
code 1:
printf("Enter the name of the student: ");
scanf("%s", name);
printf("Enter the email of the student: ");
scanf("%s", email);
Here I didn't have to ignore the newline character, I had no issues with the compiler.
code 2:
printf("Enter the name of the student: ");
scanf("%s", name);
scanf("%*c");
printf("Enter the email of the student: ");
scanf("%s", email);
here I just added the scanf("%*c")
to discard the newline character, this had the same result as code 1
. But when I replace scanf("%*c")
with scanf("\n")
the input is messed up again where I have to give 3 inputs now because it doesn't properly discard the first newline character.
code 3:
char chr, s[100], sen[100];
scanf("%c", &chr);
scanf("\n");
scanf("%s", s);
scanf("\n");
scanf("%[^\n]%*c", sen);
Here the code doesn't work without the scanf("\n")
s.
I don't really understand what's happening here. Why is it that sometimes I have to escape the newline character and other times it's not required.
答案1
得分: 5
In C, when we use a scanf("%s")
or scanf("%c")
before a scanf("%s")
or scanf("%[^\n]")
, while giving in the input, there will be an extra '\n' at the end of the first input and that will be passed into the second input and mess up the input stream.
I would not put it that way. I would say:
Whenever we use scanf
with any format specifier, the '\n' indicating the end of the line the user typed remains on the input stream where it may cause later problems. There are three cases:
- If the next input is
scanf
using any format specifier other than%c
or%[…]
, the lingering '\n' will automatically be skipped along with any other leading whitespace. - If the next input is
scanf
using%c
or%[…]
, the '\n' will be read by that format specifier (although that is usually not desired). - If the next input is something other than
scanf
, such asfgets
orgetchar
, the '\n' will be read by that function (although that is usually not desired).
(Or perhaps more concisely: "If we use scanf("%c")
or scanf("%[…]")
after having previously called scanf
on something else, the leftover '\n' tends to mess things up.")
Situation #2 is easily fixed by adding an extra leading space to the format specifier, explicitly instructing scanf
to skip leading whitespace (as it implicitly does for all other format specifiers).
Calling scanf("\n")
by itself is not usually recommended, as it doesn't do what it appears to do.
These rules aren't the complete story on scanf
, but they're a good start and explain the behavior in most basic uses.
I've tried these codes in two different systems with the same gcc compiler as far as I know. But it did different things each time.
That is surprising. If you could describe that difference exactly and reproducibly, it might be interesting to explore.
In your code 1, you're reading two simple strings, so as long as they don't contain spaces, you're fine. There's a '\n' left over after scanf
reads the first string, but it's skipped by the second %s
.
In your code 2, the call to scanf("%*c")
reads and discards the '\n'. This is indeed one way to explicitly discard a stray newline, but I don't believe it's the best way. (The problems are that it will read any character — not necessarily just a newline — and if you call it at a spot where there isn't a stray character to consume, it will block, waiting for a character it can consume.)
In your code 3, you say "the code doesn't work without the scanf("\n")
s", and that's partly true. After reading a string with %s
, there's a '\n' still on the input stream. So the next %[^\n]
is prematurely satisfied by that stray '\n'. So your call to scanf("\n")
is one way to strip it. (But you didn't need to call scanf("\n")
between the %c
and the %s
.)
As an aside, rather than calling scanf("\n")
, I believe it would have been cleaner to just add a leading space to the following %[…]
specifier: scanf(" %[^\n]%*c", sen);
.
(Also you don't necessarily need that %*c
out at the end. I know what it's for, but I'd say you don't or shouldn't need it. More on this below.)
The bottom line is that scanf
is a pretty highly problematic input function. It's nice and simple to use for really simple inputs, in simple, beginning programs. But it's not much good — it's a frustrating nuisance, generally more trouble than it's worth — for anything fancy.
What do I mean by "really simple inputs"? It's a pretty short list:
- If you want to read a single
int
with%d
, or a singlefloat
ordouble
with%f
or%lf
, that's fine. - If you want to read a simple string with
%s
, that's fine, but beware that the string will not contain any spaces. You also have to make sure you've properly allocated the memory whichscanf
will read the string into. It's also a good idea to use a width modifier like%29s
so thatscanf
can avoid overflowing that memory. - If you want to read a single character, that might be fine, but remember to use
" %c"
, not plain"%c"
.
And that's it. If you want to do anything fancier than that, scanf
starts turning into a pumpkin. In particular:
- If you want to read a string that might contain spaces,
%s
won't work, and there's no good, simple alternative. (There's%[…]
, of course, but it's not simple.) - If you anticipate that the user might type something wrong, like a letter when you're expecting a number, or two characters when you're expecting a single one, and if you want to detect this and print a nice message like "Improper input, try again",
scanf
is not the right tool for the job.
And, as a more general rule, if you're trying to do something with scanf
, and it's not working, and you come to believe that you need to "flush" some unwanted input, stop right there. The very fact that the word "flush" has entered your mind is an almost perfectly reliable indicator that scanf
is not the right tool for the job you're trying to do, and you should strongly consider abandoning scanf
in favor of something else. (Whatever it is that you're trying to do, although it might be barely possible to do using scanf
and some clumsy input-flushing mechanism, it's just about guaranteed to be harder to do, and work less well, than if you used something else).
See here and here for some similar sets of guidelines on what to try to do, versus not do, using scanf
. And when you're ready to try something else, read What can I use for input conversion instead of scanf
.
Finally, a few more words about your call to scanf("%[^\n]%*c", sen);
, and specifically that extra %*c
specifier at the end. I said I thought you didn't need it. Here's why.
What does that %*c
do? Well, %[^\n]
reads a line of text up to but not including a newline, so %*c
reads and discards the newline, presumably so that it "won't stick around on the input stream and cause problems later". But let's think about this a little more.
When you get right down to it, scanf
is always leaving newlines on the
英文:
> In C, when we use a scanf("%s")
or scanf("%c")
before a scanf("%s")
or scanf("%[^\n]")
, while giving in the input, there will be an extra '\n' at the end of the first input and that will be passed into the second input and mess up the input stream.
I would not put it that way. I would say:
Whenever we use scanf
with any format specifier, the \n
indicating the end of the line the user typed remains on the input stream where it may cause later problems. There are three cases:
- If the next input is
scanf
using any format specifier other than%c
or%[…]
, the lingering\n
will automatically be skipped along with any other leading whitespace. - If the next input is
scanf
using%c
or%[…]
, the\n
will be read by that format specifier (although that is usually not desired). - If the next input is something other than
scanf
, such asfgets
orgetchar
, the\n
will be read by that function (although that is usually not desired).
(Or perhaps more concisely: "If we use scanf("%c")
or scanf("%[…]")
after having previously called scanf
on something else, the leftover \n
tends to mess things up.")
Situation #2 is easily fixed by adding an extra leading space to the format specifier, explicitly instructing scanf
to skip leading whitespace (as it implicitly does for all other format specifiers).
Calling scanf("\n")
by itself is not usually recommended, as it doesn't do what it appears to do.
These rules aren't the complete story on scanf
, but they're a good start and explain the behavior in most basic uses.
> I've tried these codes in two different systems with the same gcc compiler as far as I know. But it did different things each time.
That is surprising. If you could describe that difference exactly and reproducibly, it might be interesting to explore.
In your code 1, you're reading two simple strings, so as long as they don't contain spaces, you're fine. There's a \n
left over after scanf
reads the first string, but it's skipped by the second %s
.
In your code 2, the call to scanf("%*c")
reads and discards the \n
. This is indeed one way to explicitly discard a stray newline, but I don't believe it's the best way. (The problems are that it will read any character — not necessarily just a newline — and if you call it at a spot where there isn't a stray character to consume, it will block, waiting for a character it can consume.)
In your code 3, you say "the code doesn't work without the scanf("\n")
s", and that's partly true. After reading a string with %s
, there's a \n
still on the input stream. So the next %[^\n]
is prematurely satisfied by that stray \n
. So your call to scanf("\n")
is one way to strip it. (But you didn't need to call scanf("\n")
between the %c
and the %s
.)
As an aside, rather than calling scanf("\n")
, I believe it would have been cleaner to just add a leading space to the following %[…]
specifier: scanf(" %[^\n]%*c", sen);
.
(Also you don't necessarily need that %*c
out at the end. I know what it's for, but I'd say you don't or shouldn't need it. More on this below.)
The bottom line is that scanf
is a pretty highly problematic input function. It's nice and simple to use for really simple inputs, in simple, beginning programs. But it's not much good — it's a frustrating nuisance, generally more trouble than it's worth — for anything fancy.
What do I mean by "really simple inputs"? It's a pretty short list:
- If you want to read a single
int
with%d
, or a singlefloat
ordouble
with%f
or%lf
, that's fine. - If you want to read a simple string with
%s
, that's fine, but beware that the string will not contain any spaces. You also have to make sure you've properly allocated the memory whichscanf
will read the string into. It's also a good idea to use a width modifier like%29s
so thatscanf
can avoid overflowing that memory. - If you want to read a single character, that might be fine, but remember to use
" %c"
, not plain"%c"
.
And that's it. If you want to do anything fancier than that, scanf
starts turning into a pumpkin. In particular:
- If you want to read a string that might contain spaces,
%s
won't work, and there's no good, simple alternative. (There's%[…]
, of course, but it's not simple.) - If you anticipate that the user might type something wrong, like a letter when you're expecting a number, or two characters when you're expecting a single one, and if you want to detect this and print a nice message like "Improper input, try again",
scanf
is not the right tool for the job.
And, as a more general rule, if you're trying to do something with scanf
, and it's not working, and you come to believe that you need to "flush" some unwanted input, stop right there. The very fact that the word "flush" has entered your mind is an almost perfectly reliable indicator that scanf
is not the right tool for the job you're trying to do, and you should strongly consider abandoning scanf
in favor of something else. (Whatever it is that you're trying to do, although it might be barely possible to do using scanf
and some clumsy input-flushing mechanism, it's just about guaranteed to be harder to do, and work less well, than if you used something else.)
See here and here for some similar sets of guidelines on what to try to do, versus not do, using scanf
. And when you're ready to try something else, read What can I use for input conversion instead of scanf
?.
Finally, a few more words about your call to scanf("%[^\n]%*c", sen);
, and specifically that extra %*c
specifier at the end. I said I thought you didn't need it. Here's why.
What does that %*c
do? Well, %[^\n]
reads a line of text up to but not including a newline, so %*c
reads and discards the newline, presumably so that it "won't stick around on the input stream and cause problems later". But let's think about this a little more.
When you get right down to it, scanf is always leaving newlines on the input stream. It's the default behavior with scanf
. And, if the next call is careful to always skip the stray newline, everything works out fine.
Suppose you call
scanf("%d", &num1);
scanf("%d", &num2);
scanf("%d", &num3);
There's a newline left over after scanning num1
, and there's a newline left over after scanning num2
, but neither of them cause any problems, because the next %d
cleanly skips it.
It's not really a problem that there's always a stray newline after a scanf call, unless the next input call doesn't skip it. That happens if (a) the next input call is scanf
using %c
or %[…]
, or (b) the next input call isn't scanf
at all, but is rather something like fgets
or getchar
.
So one way (and IMO the best and cleanest way) of dealing with stray newlines with scanf
is just to declare that it's always the next call's job to skip them:
- If the next call involves a specifier like
%d
or%f
or%s
, it automatically skips leading whitespace, so we're fine. - If the next call involves the specifiers
%c
or%[…]
, add an explicit leading space to force a stray newline to be skipped:" %c"
or" %[…]"
. - If the next call isn't
scanf
... well, that's a bad idea. My recommendation is to never mixscanf
with other input functions in the same program.
There's the concept of a loop invariant, which is something
useful you can say that's true for each trip through a loop, like
"the variable i
is always the index of the next cell we'll fill
in" or "len
is always the length of the string built so far."
Similarly, when reading input a line at a time, it's useful to
have a convention for how the newlines are handed.
We could have either:
- "As each line of input is read, the
\n
is read also. The input stream is left positioned at the beginning of the next line to be read." Or, - "After reading each line of input, the
\n
is left on the input stream. It is always the job of the next line-reading function to first skip past that\n
."
Both if these conventions are consistent. If you're reading lines of
input using fgets
, you're obviously using convention #1.
But if you're reading input using scanf
, well, scanf
is
always mostly happy with convention #2.
And I believe, for sanity's sake, that if you're using scanf
,
you should embrace convention #2 wholeheartedly. Don't try to
strip the \n
after reading a line. Don't use locutions like
%*c
after %[…]
so that "the newline won't cause problems
later". Just let the stray newlines linger, and make sure that
you always skip them next time. %d
and %f
and %s
do that
automatically. %c
and %[…]
do that, in effect, if you
explicitly add a leading space. And don't try to use fgets
or
getchar
in a program where you're also using scanf
. (Or, if
you must intermix scanf
and fgets
, then right before calling
fgets
, do something to skip the newline if it's there. That's
one place where a call to scanf("\n")
might be warranted.)
答案2
得分: 3
scanf()
仅消耗与转换格式字符串匹配的字符。因此,当stdin
是行缓冲的默认情况下,即从终端读取时,它会留下剩余的输入,例如用户输入的换行符以提交输入行。
大多数转换会跳过这样的空白字符,但对于%c
或%[...]
不会。对于这些格式,你可以在格式之前插入一个空格以明确跳过待处理的换行符和其他空白字符。
还要确保测试scanf()
的返回值以检测无效或丢失的输入,并指定要存储到目标数组中的字符的最大数量,以避免缓冲区溢出。
#include <stdio.h>
int main() {
char name[100];
char email[100];
char grade;
printf("输入学生的名字: ");
if (scanf("%99[^\n]", name) != 1)
return 1;
printf("输入等级字母: ");
if (scanf(" %c", &grade) != 1)
return 1;
printf("输入学生的邮箱: ");
if (scanf("%99s", email) != 1)
return 1;
printf("名字: %s\n", name);
printf("邮箱: %s\n", email);
printf("等级: %c\n", grade);
return 0;
}
为避免待处理的换行问题并提高错误恢复能力,建议一次读取一行输入,并使用sscanf()
来转换用户输入。
#include <stdio.h>
int main() {
char line[200];
char name[100];
char email[100];
char grade;
printf("输入学生的名字: ");
if (!fgets(line, sizeof line, stdin)) {
printf("文件意外结束\n");
return 1;
}
if (sscanf(line, "%99[^\n]", name) != 1) {
printf("未提供名字\n");
return 1;
}
printf("输入等级字母: ");
if (!fgets(line, sizeof line, stdin)) {
printf("文件意外结束\n");
return 1;
}
if (sscanf(line, " %c", &grade) != 1) {
printf("未提供等级\n");
return 1;
}
printf("输入学生的邮箱: ");
if (!fgets(line, sizeof line, stdin)) {
printf("文件意外结束\n");
return 1;
}
if (sscanf(line, "%99s", email) != 1)
printf("未提供邮箱\n");
return 1;
}
printf("名字: %s\n", name);
printf("邮箱: %s\n", email);
printf("等级: %c\n", grade);
return 0;
}
英文:
scanf()
only consumes the characters that match the conversion format string. Hence it leaves the rest of the input pending in stdin
, eg: the newline entered by the user to submit a line of input when stdin
is line buffered as is the default when reading from the terminal.
Such white space is skipped by most conversions, but not for %c
or %[...]
. For these formats, you can explicitly skip the pending newline and other white space by inserting a space before the format.
Also make sure you test the return value of scanf()
to detect invalid or missing input and specify the maximum number of characters to store to the destination array for %s
and %[...]
conversions to avoid buffer overflows.
#include <stdio.h>
int main() {
char name[100];
char email[100];
char grade;
printf("Enter the name of the student: ");
if (scanf("%99[^\n]", name) != 1)
return 1;
printf("Enter the grade letter: ");
if (scanf(" %c", &grade) != 1)
return 1;
printf("Enter the email of the student: ");
if (scanf("%99s", email) != 1)
return 1;
printf("name: %s\n", name);
printf("email: %s\n", email);
printf("grade: %c\n", grade);
return 0;
}
To avoid the pending newline issue and improve error recovery, it is recommended to read the input one line at a time and use sscanf()
to convert the user input.
#include <stdio.h>
int main() {
char line[200];
char name[100];
char email[100];
char grade;
printf("Enter the name of the student: ");
if (!fgets(line, sizeof line, stdin)) {
printf("unexpected end of file\n");
return 1;
}
if (sscanf(line, "%99[^\n]", name) != 1) {
printf("no name given\n");
return 1;
}
printf("Enter the grade letter: ");
if (!fgets(line, sizeof line, stdin)) {
printf("unexpected end of file\n");
return 1;
}
if (sscanf(line, " %c", &grade) != 1) {
printf("no grade given\n");
return 1;
}
printf("Enter the email of the student: ");
if (!fgets(line, sizeof line, stdin)) {
printf("unexpected end of file\n");
return 1;
}
if (sscanf(line, "%99s", email) != 1)
printf("no email given\n");
return 1;
}
printf("name: %s\n", name);
printf("email: %s\n", email);
printf("grade: %c\n", grade);
return 0;
}
答案3
得分: 3
I'll only translate the code-related portions:
code 1:
printf("Enter the name of the student: ");
scanf("%s", name);
printf("Enter the email of the student: ");
scanf("%s", email);
Here I didn't have to ignore the newline character, I had no issues
with the compiler.
From the C Standard (7.21.6.2 The fscanf function)
8 Input white-space characters (as specified by the isspace function)
are skipped, unless the specification includes a [, c, or n specifier.
So there is no problem with the above code. White space characters, including the new line character '\n,' will be skipped.
code 2:
printf("Enter the name of the student: ");
scanf("%s", name);
scanf("%*c");
printf("Enter the email of the student: ");
scanf("%s", email);
here I just added the scanf("%*c") to discard the newline character,
this had the same result as code 1.
Indeed, as it follows from the quote above, the function itself will skip newline characters. So this added statement
scanf("%*c");
does not change the expected result.
But when I replace scanf("%*c") with scanf("\n") the input is messed
up again where I have to give 3 inputs now because it doesn't properly
discard the first newline character.
From the C Standard (7.21.6.2 The fscanf function)
5 A directive composed of white-space character(s) is executed by
reading input up to the first non-white-space character (which remains
unread), or until no more characters can be read.
So for this statement
scanf("\n");
the function skips all white space characters stored in the input buffer and waits when a non-white space character will be added or the call will be interrupted, for example, by pressing 'Ctrl+z' in MS VC.
That is before executing this statement
printf("Enter the email of the student: ");
the function scanf
will wait for input that will be stored in the input buffer and then will be read by the next call of scanf
scanf("%s", email);
That is the program correctly will store data in the variables name
and email
, but the program flow will confuse users because the program will wait for the input before this statement
printf("Enter the email of the student: ");
And after this call of printf
, the data entered before this call will be automatically read by the next call of scanf
. That is, the pause of waiting for input will be not after the call of printf
but before the call of printf
.
This code snippet
char chr, s[100], sen[100];
scanf("%c", &chr);
scanf("\n");
scanf("%s", s);
scanf("\n");
scanf("%[^\n]%*c", sen);
behaves the same way as the code snippet shown above (#2). The difference is that there is no call of printf
. So the program flow does not confuse users of the code.
Pay attention to that instead of these two statements
scanf("\n");
scanf("%[^\n]%*c", sen);
you could just write
scanf(" %[^\n]", sen);
^^
placing a leading space in the format string. This leading space character allows skipping white space characters before a non-white space character is encountered.
英文:
> code 1:
printf("Enter the name of the student: ");
scanf("%s", name);
printf("Enter the email of the student: ");
scanf("%s", email);
> Here I didn't have to ignore the newline character, I had no issues
> with the compiler.
From the C Standard (7.21.6.2 The fscanf function)
> 8 Input white-space characters (as specified by the isspace function)
> are skipped, unless the specification includes a [, c, or n specifier.
So there is no problem with the above code. White space characters including the new line character '\n'
will be skipped.
> code 2:
printf("Enter the name of the student: ");
scanf("%s", name);
scanf("%*c");
printf("Enter the email of the student: ");
scanf("%s", email);
> here I just added the scanf("%*c") to discard the newline character,
> this had the same result as code 1.
Indeed as it follows from the quote above the function itself will skip new line characters. So this added statement
scanf("%*c");
does not change the expected result.
> But when I replace scanf("%*c") with scanf("\n") the input is messed
> up again where I have to give 3 inputs now because it doesn't properly
> discard the first newline character.
From the C Standard (7.21.6.2 The fscanf function)
> 5 A directive composed of white-space character(s) is executed by
> reading input up to the first non-white-space character (which remains
> unread), or until no more characters can be read.
So for this statement
scanf("\n");
the function skips all white space characters stored in the input buffer and waits when a non-white space character will be added or the call will be interrupted for example by pressing Ctrl+z
in MS VC.
That is before executing this statement
printf("Enter the email of the student: ");
the function scanf
will wait an input that will be stored in the input buffer and then will be read by the next call of scanf
scanf("%s", email);
That is the program correctly will store data in the variables name
and email
but the program flow will confuse users because the program will wait for the input before this statement
printf("Enter the email of the student: ");
And after this call of printf
the data entered before this call will be automatically read by the next call of scanf
. That is the pause of waiting an input will be not after the call of printf
but before the call of printf
.
This code snippet
char chr, s[100], sen[100];
scanf("%c", &chr);
scanf("\n");
scanf("%s", s);
scanf("\n");
scanf("%[^\n]%*c", sen);
behaves the same way as the code snippet shown above (#2). The difference is that there is no call of printf
. So the program flow does not confuse users of the code.
Pay attention to that instead of these two statements
scanf("\n");
scanf("%[^\n]%*c", sen);
you could just write
scanf(" %[^\n]", sen);
^^
placing a leading space in the format string. This leading space character allows to skip white space characters before a non white space character is encountered.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论