使用scanf()从键盘读取时如何读取前一个输入的换行符?

14

这本来应该很简单,但我在读取连续的键盘输入时遇到了麻烦。

这是代码:

#include <string.h>
#include <stdio.h>

int main()
{
    char string[200];
    char character;
    printf ("write something: ");
    scanf ("%s", string);
    printf ("%s", string);
    printf ("\nwrite a character: ");
    scanf ("%c", &character);
    printf ("\nCharacter %c  Correspondent number: %d\n", character, character);

    return 0;
}

正在发生什么

当我输入一个字符串(例如:computer),程序会读取换行符 ('\n') 并将其放入 character 中。下面是显示的样式:

 write something: computer
 computer
 Character:
    Correspondent number: 10

此外,该程序无法处理超过一个单词的字符串。我该如何解决这些问题?


1
scanf 是不安全的,应该使用 fgets - John
@John 在这个使用案例中是的,但并不是普遍适用的! - fuz
2
可能是重复的问题[为什么使用scanf输入字符时会出现奇怪的情况?](http://stackoverflow.com/questions/26547850/why-scanf-works-wierd-while-taking-a-character) - ericbn
@ericbn 你说得对 - 你发布的问题基本上与我的类似。然而,hackks和alexis添加了相关信息,可能对其他用户有用。 - El Cid
1
@ElCid,当然可以!http://meta.stackoverflow.com/questions/252929/which-question-is-the-better-reference-for-a-duplicate - ericbn
1
关于这行代码:'scanf ("%s", string);' 用户可能会溢出输入缓冲区,导致未定义的行为并导致段错误事件。建议:'scanf ("%199s", string);' - user3629249
6个回答

9

scanf首先读取输入的字符串并在输入缓冲区中留下\n。下一次调用scanf会读取该\n并将其存储到character中。
试一试

scanf (" %c", &characte);   
     // ^A space before %c in scanf can skip any number of white space characters. 

如果字符串超过一个字符,程序将无法正常工作,因为scanf一旦找到空格字符就停止读取。您可以改用fgets

 fgets(string, 200, stdin);

2
OP对printf()的使用是正确的。像char character这样的可变参数会按照通常的比例提升为int(或很少情况下为unsigned),因此不需要进行强制转换为intprintf ("%c %d",...需要两个int参数,所以OP使用了正确的格式说明符。 "d,i int参数被转换为..." "c 如果没有l长度修饰符,则int参数..." C11 §7.21.6.1 8 - chux - Reinstate Monica
我同意使用fgets()而不是scanf()来读取一行文本。然而,第二个输入预计会输入'newline'和'newline'是空格,所以建议使用:scanf(" c", &character);将无法工作,因为scanf将跳过新行。也许这就是想要的,但问题在这个细节上不太清楚。 - user3629249
@user3629249;在scanf中使用"%c"总是安全的。如果用户按两次Enter/Return键会怎么样呢? - haccks

5

通常情况下,解决OP的第一个问题是在格式前加上一个空格。这将包含空白字符,包括前一行的'\n'

// scanf("%c", &character);
scanf(" %c", &character);

此外,该程序无法处理包含多个单词的字符串。我该如何解决这些问题?
对于第二个问题,让我们更加准确地了解“字符串”以及“%s”的含义。
一个字符串是由连续字符组成的序列,包括第一个空字符和结束符。OP并没有输入一个字符串,尽管报告中写着“我输入了一个字符串(例如:计算机)”。OP实际上输入了一行文本。8个字符“计算机”后面跟着Enter。这里没有“空字符”。相反,是9个char,“computer\n”。 scanf("%s", string);中的"%s"有三个作用:
1)扫描但不保存任何前导空格。
2)扫描并将所有非空格内容保存到string中。
3)当遇到空格或EOF时停止扫描。该char被放回到stdin'\0'被追加到string,使其成为C中的“字符串”。
要读取包括空格在内的一行文本,请不要使用scanf("%s",...。请考虑使用fgets()
fgets(string, sizeof string, stdin);
// remove potential trailing \r\n as needed
string[strcspn(string, "\n")] = 0;

混合使用 scanf()fgets() 是个问题,因为像 scanf("%s", string); fgets(...) 这样的调用会让 '\n' 保留在 stdin 中,被 fgets() 当作只有 "\n" 的一行读取。建议改用 fgets()(或 *nix 系统上的 getline())来读取 所有 用户输入。然后再解析所读取的行。
fgets(string, sizeof string, stdin);
scanf(string, "%c", &character);

如果代码必须使用scanf()读取包括空格在内的用户输入:

scanf("%*[\n]"); // read any number of \n and not save.
// Read up to 199 `char`, none of which are \n
if (scanf("%199[^\n]", string) != 1) Handle_EOF();

最后,代码应该使用错误检查和输入宽度限制。测试所有输入函数的返回值。


3
您看到的是您调用函数时的正确行为:
  • scanf将从输入中读取一个单词,并立即在读取之后移动输入指针。如果您键入computer<RETURN>,那么要读取的下一个字符就是换行符。

  • 要读取整行,包括最后的换行符,请使用fgets。仔细阅读文档:fgets将返回一个字符串,该字符串包括它所读取的最后的换行符。(gets由于许多原因不应被使用,它会读取并丢弃最后的换行符。)

我还应该补充一点,虽然scanf有其用途,但在交互式使用中会导致非常混乱的行为,就像您发现的那样。即使在想逐个单词读取的情况下,如果预期使用是交互式的,请使用其他方法。


2
您可以利用%*c
#include <string.h>
#include <stdio.h>

int main()
{
    char string[200];
    char character;
    printf ("write something: ");
    scanf ("%s%*c", string);
    printf ("%s", string);
    printf ("\nwrite a character: ");
    scanf ("%c%*c", &character);
    printf ("\nCharacter %c  Correspondent number: %d\n", character, character);

    return 0;
}

%*c会接受并忽略换行符或任何空格。


1
注意:%*c将接受并忽略任何后续的char,通常是换行符,但也可能是任何空格,如' ' - chux - Reinstate Monica
@chux,没错。我会精确地将你的注释添加到我的答案中。 - Jahid
1
这里存在缓冲区溢出;你需要限制输入。 - M.M

0

scanf行后面,你也可以加上getchar()。它会完成任务的 :)


-2

需要刷新流。在连续输入时,标准输入流stdin会缓冲键盘上的每个按键。因此,当您键入“计算机”并按下回车键时,输入流也吸收了换行符,即使只有字符串“计算机”被分配给string。因此,当您稍后扫描字符时,已经加载的换行符是被扫描并分配给character的。

此外,stdout流也需要刷新。考虑以下内容:

...
printf("foo");
while(1)
{}
...

如果尝试执行类似这样的代码,则控制台上不会显示任何内容。系统缓冲了stdout流,即标准输出流,没有意识到它将遇到一个无限循环,一旦发生这种情况,就再也没有机会将流卸载到控制台。
显然,以类似的方式,每当scanf阻塞程序并等待stdin,即标准输入流时,它都会影响其他正在缓冲的流。无论如何,如果事情开始混乱,最好正确地刷新流。
对您的代码进行以下修改似乎可以产生所需的输出。
#include <string.h>
#include <stdio.h>

int main()
{
char string[200];
char character;

printf ("write something: ");

fflush(stdout);

scanf ("%s", string);

fflush(stdin);

printf ("%s", string);
printf ("\nwrite a character: ");

fflush(stdout);

scanf ("%c", &character);
printf ("\nCharacter %c  Correspondent number: %d\n", character, character);

return 0;
} 

输出:
输入一些内容: 计算机
计算机
输入一个字符: a

字符 a 对应的数字为: 97


1
你的代码存在缓冲区溢出问题,而 fflush(stdin) 会导致未定义的行为。 - M.M
1
如果他们输入了200个字母,那么你就会溢出string。有关fflush的信息,请参阅当前C标准的第7.21.5.2/2节:“如果流指向输出流或更新流,其中最近的操作不是输入,则fflush函数会导致该流的任何未写入数据被传递到主机环境以写入文件;否则,行为是未定义的”。 stdin不是输出或更新流。 - M.M
@M.M 关于缓冲区溢出,那不是我想要表达的重点。显然,如果您尝试在其容量之外存储数据,数组将会溢出。我既没有解决这个问题,也没有在我的代码中考虑到这个问题。 - ps_
fflush的行为并不像我预测的那样,并且它不应该对输入流执行某些特定的操作。你错误地假设在一个编译器上观察到的行为是标准行为。 "更新流"意味着可以用于读写的流(使用+标志打开)。 - M.M
明白了!谢谢!拜拜。 - ps_
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接