使用 C 语言中的 scanf 解析输入

10

我一直在努力解决如何使用scanf()的问题,它似乎与整数配合使用得很好,这相对简单:scanf("%d", &i)

我的问题在于使用循环读取输入时,使用scanf()会遇到问题。例如:

do {
  printf("counter: %d: ", counter);
  scanf("%c %c%d", &command, &prefix, &input);
} while (command != 'q');
  1. 当我输入一个有效结构的输入,例如c P101,它似乎会在提示之前再次循环。即使只有单个输入也会发生这种情况:

scanf("%c", &c) 

在一个 while 循环中。它会在提示我之前执行两次循环。是什么让它循环两次,我该如何停止它?

当我输入的内容比程序预期的字符或数字少时,例如 q,按下回车似乎会提示我再输入更多内容。如何让 scanf() 处理单个和双字符输入?

5个回答

19
当你输入"c P101"时,程序实际接收到的是"c P101\n"。大多数转换说明符会跳过前导空格,包括换行符,但%c不会。第一次循环读取直到"\n"为止,第二次循环将"\n"读入command,将"c"读入prefix,并留下"P",这不是数字,因此转换失败,"P101\n"仍留在流中。下一次,"P"被存储到command中,"1"被存储到prefix中,并且从剩余的"01"中存储1到input中,并将"\n"保留在流中以供下一次使用。您可以通过在格式字符串开头添加一个空格来解决此问题,这将跳过任何前导空格,包括换行符。
在第二种情况下也发生了类似的事情,当你输入"q"时,"q\n"被输入到流中,第一次读取"q",第二次读取"\n",只有在第三次调用时才读取第二个"q"。您可以通过在格式字符串开头添加一个空格字符来避免出现问题。
更好的方法是使用类似fgets()的函数按行处理,然后使用sscanf()进行解析。

2
非常好的解释,我怀疑它一定是在读取\n,但这确实让我原本认为很简单的解析变得更加复杂。我会查看fgets和sscanf()。 - zxcv
你是否考虑添加一段关于检查scanf()返回值以确保您得到了预期结果的内容。除非您知道早期的问题,否则这可能会成为此问题的规范问答,但规范答案应涵盖所有合理的观点,我认为从scanf()返回是一个合理的观点。在完成后向我发出评论;然后我将删除此评论并标记您的评论以进行删除(或者您可以将其标记为过时,但我想知道您已经进行了更改)。谢谢。 - Jonathan Leffler

1

真是太糟糕了!我不知道它坏了

#include <stdio.h>

int main(void)
{
    int counter = 1;
    char command, prefix;
    int input;

    do 
    {
        printf("counter: %d: ", counter);
        scanf("%c %c%d", &command, &prefix, &input);
        printf("---%c %c%d---\n", command, prefix, input);
        counter++;
    } while (command != 'q');
}

counter: 1: a b1
---a b1---
counter: 2: c d2
---
 c1---
counter: 3: e f3
---d 21---
counter: 4: ---e f3---
counter: 5: g h4
---
 g3---

输出结果似乎与罗伯特的回答相符。


1

对于问题1,我怀疑你的printf()存在问题,因为没有终止符"\n"。

printf的默认行为是缓冲输出直到有完整的一行。除非你明确地改变了stdout上的缓冲。

对于问题2,你遇到了scanf()最大的问题之一。除非你的输入完全匹配你指定的扫描字符串,否则你得到的结果将与你期望的完全不同。

如果你有选择的话,通过忽略scanf()并进行自己的解析,你将获得更好的结果(并减少安全问题)。例如,使用fgets()将整个行读入一个字符串,然后处理字符串的各个字段——甚至可以使用sscanf()


嗯,我认为不是这样的。即使加上一个 \n,它也只会在 scanf 再次提示之前打印两次。我甚至尝试在 do-while 循环中的 scanf 之前和之后放置 printf,它们在 scanf 再次提示之前循环两次。这是使用单个字符输入,例如 s 和回车键。 - zxcv

1

0
也许使用 while 循环而不是 do...while 循环会有所帮助。这样,在执行代码之前就会测试条件。 尝试以下代码片段:
 while(command != 'q')
    {
        //statements
    }

此外,如果您提前知道字符串长度,使用“for”循环比使用“while”循环更容易处理。还有一些更棘手的方法可以动态确定长度。

最后要说的是:scanf()并不“糟糕”。它只做它该做的事情。

gets()函数非常危险(虽然对于无风险应用程序来说很方便),因为它本身不会检查输入。它被广泛认为是一个利用点,特别是缓冲区溢出攻击,覆盖未分配给该变量的寄存器空间。因此,如果您选择使用它,请花些时间进行一些可靠的错误检查和纠正。

然而,几乎总是应该使用fgets()或POSIX getline()来读取行 - 注意这两个函数都将换行符包含在输入字符串中,不像gets()。您可以使用string[strcspn(string, "\n")] = '\0';fgets()getline()读取的string中删除尾随的换行符 - 这样可以可靠地工作。


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