Scanf()在缓冲区中保留换行符

我有以下计划:

int main(int argc, char *argv[])
{
int a, b;
char c1, c2;
printf("Enter something: ");
scanf("%d", &a); // line 1
printf("Enter other something: ");
scanf("%d", &b); // line 2


printf("Enter a char: ");
scanf("%c", &c1); // line 3
printf("Enter another char: ");
scanf("%c", &c2); // line 4


printf("Done"); // line 5


system("PAUSE");


return 0;
}

正如我在 C 语言书中读到的那样,作者说 scanf()在缓冲区中留下了一个换行符,因此,程序不会在第4行停下来让用户输入数据,而是将新的行符存储在 c2中,然后移动到第5行。

Is that right?

但是,这只发生在 char数据类型中吗?因为我没有在第1、2、3行中看到 int数据类型的这个问题。对吗?

127837 次浏览

在尝试解析字符以外的转换之前,scanf()函数会自动跳过前导空格。字符格式(主要是 %c; 还有扫描集 %[…]ー和 %n)是个例外; 它们不跳过空格。

Use " %c" with a leading blank to skip optional white space. Do not use a trailing blank in a scanf() format string.

请注意,这仍然不会消耗任何在输入流中留下的尾随空格,甚至不会消耗到一行的末尾,所以如果在同一个输入流中也使用 getchar()fgets(),请注意这一点。我们只是让 Scanf 跳过空格 之前转换,就像它跳过 %d和其他非字符转换一样。


请注意,除了转换之外,非空格“指令”(使用 POSIX 扫描术语) ,如 scanf("order = %d", &order);中的文本也不跳过空格。字面 order必须与要读取的下一个字符匹配。

因此,如果您希望跳过前一行中的换行符,但仍然需要与固定字符串 like this question进行字面匹配,那么您可能需要 " order = %d"

使用 scanf(" %c", &c2);。这将解决你的问题。

在调用第二个 scanf()之前使用 getchar()

scanf("%c", &c1);
getchar();  // <== remove newline
scanf("%c", &c2);

另一个选项(我从 给你获得的)是使用 赋值-取消选项赋值-取消选项读取和丢弃换行。要做到这一点,我们只需将一种格式用于读取 %c之间带星号的字符:

scanf("%d%*c",&a); // line 1
scanf("%c%*c",&c1); // line 3

scanf will then read the next char (that is, the newline) but not assign it to any pointer.

然而,最终我会支持 常见问题的最后选择:

或者,根据您的需求,您也可以忘记 Scanf ()/getchar () ,使用 fgets ()从用户那里获取一行文本,然后自己解析它。

为了回应我在 关于 C + + 的另一个答案中发布的内容: 我建议抛弃 scanf(),永远不要使用它,而是使用 fgets()sscanf()

其原因是,至少在类 Unix 系统中默认情况下,CLI 程序运行的终端会在程序看到用户输入之前对其进行一些处理。它缓冲输入,直到输入换行符,并允许一些基本的行编辑,如使退格工作。

所以,你永远不可能一次只得到一个字符,或者几个字符,只能得到一整行。但是这不是例如 scanf("%d")进程,而是它只处理数字 and stops there,其余的在 C 库中缓冲,以供将来的 stdio函数使用。如你的程式有。

printf("Enter a number: ");
scanf("%d", &a);


printf("Enter a word: ");
scanf("%s", word);

然后输入行 123 abcd,它会立刻完成 都有scanf(),但是只有在给出换行符之后。当用户点击空格时,第一个 scanf()不会返回,即使那是数字结束的地方(因为此时行仍然在终端的行缓冲区中) ; 第二个 scanf()不会等待你输入另一行(因为输入缓冲区已经包含足够的内容来填充 %s转换)。

这不是用户通常期望的!

相反,他们期望点击回车完成输入,如果你点击回车,你要么得到一个默认值,或者一个错误,可能建议请真的只是给出答案。

你不能真正做到这一点与 scanf("%d")。如果用户只是点击回车,什么也不会发生。因为 scanf()还在等号码。终端向前发送该行,但您的程序看不到它,因为 scanf()会吃掉它。您没有机会对用户的错误做出反应。

这也没什么用。

因此,我建议使用 fgets()getline()一次读取一整行输入。这与终端所提供的完全匹配,并且总是在用户输入一行之后提供程序控制。如何处理输入行取决于你,如果你想要一个数字,你可以使用 atoi()strtol(),甚至 sscanf(buf, "%d", &a)来解析这个数字。sscanf()没有与 scanf()相同的不匹配,因为它从中读取的缓冲区的大小是有限的,当它结束时,它就结束了——函数不能再等了。

(fscanf() on a regular file can also be fine if the file format is one that supports how it skims over newlines like any whitespace. For line-oriented data, I'd still use fgets() and sscanf().)


So, instead of what I had above, use something like this:

printf("Enter a number: ");


fgets(buf, bufsize, stdin);
sscanf(buf, "%d", &a);

或者,实际上,也可以检查 sscanf()的返回值,这样就可以检测到空行和其他无效数据:

#include <stdio.h>


int main(void)
{
const int bufsize = 100;
char buf[bufsize];
int a;
int ret;
char word[bufsize];


printf("Enter a number: ");
fgets(buf, bufsize, stdin);


ret = sscanf(buf, "%d", &a);


if (ret != 1) {
fprintf(stderr, "Ok, you don't have to.\n");
return 1;
}


printf("Enter a word: ");
fgets(buf, bufsize, stdin);


ret = sscanf(buf, "%s", word);
if (ret != 1) {
fprintf(stderr, "You make me sad.\n");
return 1;
}


printf("You entered %d and %s\n", a, word);
}

当然,如果您希望程序坚持这样做,您可以创建一个简单的函数来遍历 fgets()sscanf(),直到用户设计好按照他们被告知的去做; 或者只是立即退出并出现一个错误。取决于你认为你的程序应该做什么,如果用户不想玩球。


你可以做一些类似的事情,比如通过循环遍历 getchar()来读取字符,直到 scanf("%d")返回后换行,从而清除缓冲区中留下的任何垃圾,但是这对于用户只是点击空行输入的情况没有任何作用。无论如何,fgets()会一直读到换行,所以你不必自己动手。

/*Take char input using scanf after int input using scanf just use fflush(stdin) function  after int input */
#include<stdio.h>
#include<conio.h>
void main()
{
int x;
char y;
clrscr();
printf(" enter an int ");
scanf("%d",&x);
fflush(stdin);
printf("\n Now enter a char");
scanf("%c",&y);
printf("\n X=%d and Y=%c",x,y);
getch();
}

两个变通方法,一个是巧妙地捕捉额外的空格。

getchar(); // (1) add `getchar()`
scanf("%c", &c1);


scanf(" %c", &c1); // (2) add whitespace before %c

另一种是使用 fgets()来代替 scanf()。注意: gets()是不安全的,参见 为什么会有危险

相比之下,我更倾向于推荐第二种方法。因为它更像 readable可以维持。例如,在第一种情况下,在添加/移动一些 scanf()之前,您必须思考一段时间。