我们必须使用`scanf("\n")`和`scanf("%*c")`的时候

Nav*_*ddy 1 c whitespace scanf conversion-specifier

在 C 中,当我们在or之前使用 ascanf("%s")或时,在输入时,第一个输入的末尾会有一个额外的内容,并且该额外内容将被传递到第二个输入并弄乱输入流。据我所知,我已经在两个不同的系统中使用相同的 gcc 编译器尝试了这些代码。但它每次都做了不同的事情。在第一个系统中,我必须使用 a来丢弃换行符。但在第二个系统中就没有这样的需要了。它自动丢弃换行符。scanf("%c")scanf("%s")scanf("%[^\n]")'\n'scanf("\n")

然后我尝试了三个代码,

代码1:

printf("Enter the name of the student: ");
scanf("%s", name);

printf("Enter the email of the student: ");
scanf("%s", email);
Run Code Online (Sandbox Code Playgroud)

在这里我不必忽略换行符,编译器没有任何问题。

代码2:

printf("Enter the name of the student: ");
scanf("%s", name);
scanf("%*c");

printf("Enter the email of the student: ");
scanf("%s", email);
Run Code Online (Sandbox Code Playgroud)

这里我只是添加了scanf("%*c")来丢弃换行符,这与 具有相同的结果code 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);
Run Code Online (Sandbox Code Playgroud)

如果没有 s,这里的代码将无法工作scanf("\n")

我真的不明白这里发生了什么。为什么有时我必须转义换行符,而有时则不需要。

Ste*_*mit 6

\n

在 C 中,当我们在or之前使用 ascanf("%s")或时,在输入时,第一个输入的末尾会有一个额外的 \'\\n\' ,它将被传递到第二个输入并弄乱输入流。scanf("%c")scanf("%s")scanf("%[^\\n]")

\n
\n

我不会那样说。我会说:

\n

每当我们scanf任何格式说明符一起使用时,\\n指示用户键入的行结尾的信息都会保留在输入流中,这可能会导致以后出现问题。分三种情况:

\n
    \n
  1. 如果下一个输入使用除或 以外scanf任何格式说明符,则延迟将与任何其他前导空格一起自动跳过。%c%[\xe2\x80\xa6]\\n
  2. \n
  3. 如果下一个输入scanf使用%cor %[\xe2\x80\xa6],则将\\n由该格式说明符读取(尽管通常不需要)。
  4. \n
  5. 如果下一个输入不是scanf,例如fgetsgetchar,则\\n该函数将读取 (尽管通常不希望这样做)。
  6. \n
\n

(或者更简洁地说:“如果我们在之前调用过其他东西之后使用scanf("%c")或,那么剩下的东西往往会把事情搞砸。”)scanf("%[\xe2\x80\xa6]")scanf\\n

\n

通过向格式说明符添加额外的前导空格,明确指示scanf跳过前导空格(就像它对所有其他格式说明符隐式执行的那样),可以轻松修复情况 #2。

\n

通常不建议单独调用scanf("\\n"),因为它不会执行看起来所执行的操作。

\n

这些规则并不是 的完整故事scanf,但它们是一个良好的开始,并解释了大多数基本用途的行为。

\n
\n

据我所知,我已经在两个不同的系统中使用相同的 gcc 编译器尝试了这些代码。但它每次都做了不同的事情。

\n
\n

这是令人惊讶的。如果您能够准确且可重复地描述这种差异,那么探索可能会很有趣。

\n

在代码 1 中,您正在读取两个简单的字符串,因此只要它们不包含空格,就可以了。\\n读取第一个字符串后剩下一个,scanf但它被第二个字符串跳过%s

\n

在代码 2 中,调用scanf("%*c")读取并丢弃\\n. 这确实是显式丢弃杂散换行符的一种方法,但我不认为这是最好的方法。(问题是它会读取任何字符 \xe2\x80\x94 不一定只是换行符 \xe2\x80\x94 并且如果你在没有杂散字符要消耗的地方调用它,它会阻塞,等待它可以消耗的角色。)

\n

在你的代码 3 中,你说“如果没有 s,代码就无法工作scanf("\\n")”,这部分是正确的。使用 读取字符串后,输入流上仍然%s存在。\\n于是接下来%[^\\n]就过早地被那流浪满足了\\n。所以你的调用scanf("\\n")是剥离它的一种方法。(但是你不需要scanf("\\n")%c%s调用。)

\n

顺便说一句,我相信在以下说明符scanf("\\n")中添加一个前导空格会更干净,而不是调用。%[\xe2\x80\xa6]scanf(" %[^\\n]%*c", sen);

\n

%*c(而且你最后不一定需要它。我知道它的用途,但我想说你不需要或不应该需要它。下面有更多信息。)

\n
\n

底线是这scanf是一个非常有问题的输入函数。在简单的入门程序中,对于非常简单的输入来说,它非常好用且简单。但这并不是很好的 \xe2\x80\x94 这是一个令人沮丧的麻烦,通常比它的价值 \xe2\x80\x94 对于任何花哨的东西来说更麻烦。

\n

“非常简单的输入”是什么意思?这是一个非常简短的列表:

\n
    \n
  • int如果您想用%d, 或floatdoubleor%f来阅读单曲%lf,也可以。
  • \n
  • 如果你想用 读取一个简单的字符串%s,那很好,但要注意该字符串不会包含任何空格。您还必须确保已正确分配scanf将读取字符串的内存。使用宽度修饰符也是一个好主意,%29s这样scanf可以避免内存溢出。
  • \n
  • 如果你想读取单个字符,那可能没问题,但记住使用" %c",而不是 plain"%c"
  • \n
\n

就是这样。如果你想做比这更奇特的事情scanf,那就开始变成南瓜吧。尤其:

\n
    \n
  • 如果您想读取可能包含空格的字符串,%s则无法工作,并且没有好的、简单的替代方法。(有%[\xe2\x80\xa6],但并不简单。)
  • \n
  • 如果您预计用户可能会输入错误的内容,例如当您希望输入数字时输入一个字母,或者当您希望输入一个字符时输入两个字符,并且如果您想检测到这一点并打印一条漂亮的消息,例如“输入不正确,请重试",scanf不是适合该工作的工具。
  • \n
\n

而且,作为更一般的规则,如果您尝试使用 做某事scanf,但它不起作用,并且您开始相信需要“刷新”一些不需要的输入,请立即停止。事实上,“同花顺”这个词已经进入你的脑海,这是一个几乎完全可靠的指标,它scanf不是你想要做的工作的正确工具,你应该强烈考虑放弃并scanf转而选择其他东西。(无论您想要做什么,尽管使用它可能几乎不可能scanf一些笨拙的输入刷新机制几乎不可能做到,但它肯定会比如果你用了别的东西。)

\n

请参阅此处此处,了解一些类似的指南,了解使用 时尝试做什么和不做什么scanf。当您准备尝试其他方法时,请阅读我可以使用什么来代替输入转换scanf

\n
\n

最后,再多说几句关于您对 的调用scanf("%[^\\n]%*c", sen);,特别是%*c最后的额外说明符。我说我以为你不需要它。这就是原因。

\n

那有什么%*c作用?好吧,%[^\\n]读取一行文本,但包括换行符,因此%*c读取并丢弃换行符,大概这样它“就不会留在输入流上并在以后引起问题”。但让我们再想一想。

\n

当您认真对待它时,scanf总是在输入流上留下换行符。这是 的默认行为scanf。而且,如果下一次调用总是小心地跳过杂散的换行符,那么一切都会顺利进行。

\n

假设你打电话

\n
scanf("%d", &num1);\nscanf("%d", &num2);\nscanf("%d", &num3);\n
Run Code Online (Sandbox Code Playgroud)\n

扫描后留下了一个换行符num1,扫描后也留下了一个换行符num2,但是它们都没有造成任何问题,因为下一个%d干净地跳过了它。

\n

在 scanf 调用之后总是有一个杂散换行符并不是真正的问题,除非下一个输入调用不会跳过它。如果 (a) 下一个输入调用scanf使用%cor %[\xe2\x80\xa6],或者 (b) 下一个输入调用scanf根本不是,而是类似fgetsor ,就会发生这种情况getchar就会发生这种情况。

\n

因此,处理杂散换行符的一种方法(在我看来是最好和最干净的方法)scanf就是声明它总是是下一个调用的工作来跳过它们:

\n
    \n
  1. 如果下一个调用涉及像%dor %for 这样的说明符%s的说明符,它会自动跳过前导空格,所以我们没问题。
  2. \n
  3. 如果下一个调用涉及说明符%c%[\xe2\x80\xa6],请添加显式前导空格以强制跳过杂散换行符:" %c"" %[\xe2\x80\xa6]"
  4. \n
  5. 如果下一次通话不是scanf……那么,那是个坏主意。我的建议是永远不要scanf与同一程序中的其他输入功能混合。
  6. \n
\n

有一个循环不变式的概念,这很有用,你可以说它对于每次循环都是如此,就像\n“变量i始终是我们将填充的下一个单元格的索引\nin" 或 "len始终是迄今为止构建的字符串的长度。"

\n

类似地,当一次读取一行输入时,对于如何处理换行符有一个约定很有用。\n我们可以采用以下任一方法:

\n
    \n
  1. “当读取每一行输入时,\\n也会读取它。输入流位于要读取的下一行的开头。” 或者,
  2. \n
  3. “读取每一行输入后, 会\\n留在输入流中。首先跳过该行始终是下一行读取函数的工作\\n。”
  4. \n
\n

如果这些约定一致的话,两者都是。如果您正在使用 读取输入行fgets,那么您显然正在使用约定#1。\n但如果您正在使用 读取输入scanf,则scanf\n总是对约定#2 感到满意。

\n

我相信,为了理智起见,如果您正在使用scanf,\n您应该全心全意地接受第 2 条约定。\\n读完一行后,不要尝试删除。不要使用类似于%*cafter 的措辞%[\xe2\x80\xa6],这样“换行符稍后不会引起问题”。只需让杂乱的换行符继续存在,并确保\n下次 您始终跳过它们即可。%d并且\n自动%f执行%s此操作。 实际上,如果您显式添加前导空格,则可以这样做%c%[\xe2\x80\xa6]并且不要尝试在您也使用 的程序中使用fgetsor\n 。(或者,如果\n您必须混合使用和,那么在调用\n之前,执行一些操作来跳过换行符(如果存在)。那是\n可能需要调用的地方。)getcharscanfscanffgetsfgetsscanf("\\n")

\n