如何确定解决scanf输入流问题

How to definitely solve the scanf input stream problem

假设我想要 运行 以下 C 代码段:

scanf("%d" , &some_variable);
printf("something something\n\n");

printf("Press [enter] to continue...")
getchar(); //placed to give the user some time to read the something something

此片段不会暂停!问题是 scanf 将在输入流 1 中留下“enter”(\n)字符,弄乱它后面的所有内容;在这种情况下,getchar() 将吃掉 \n 而不是等待实际的新角色。

自从我被告知不要使用 fflush(stdin)(我真的不明白为什么)我能想到的最好的解决方案就是在我的开始时重新定义扫描函数代码:


void nsis(int *pointer){ //nsis arconim of: no shenanigans integer scanf
     scanf("%d" , pointer);
     getchar(); //this will clean the inputstream every time the scan function is called
}

然后我们简单地使用nsis代替scanf。这个应该飞但它似乎是一个真正自制的、用胶带组合在一起的解决方案。专业的 C 开发人员如何处理这个烂摊子?他们根本不使用 scanf 吗?他们只是接受使用脏输入流吗?这里的标准是什么?

我无法在任何地方找到明确的答案!我能找到的每个来源都提到了不同的(粗略的)解决方案...


编辑: 回应所有评论某些版本的“只是不要使用 scanf”:好的,我可以做到,但是 [=12 的目的是什么=] 然后呢?它只是一个无用的损坏功能,永远不应该使用吗?为什么它首先出现在图书馆中?

这看起来真的很荒谬,尤其是考虑到所有初学者都被教导使用 scanf...


[1]: 留下的\n是用户在输入变量some_variable的值时输入的那个,而not 的那个现在变成printf.

but what is the purpose of scanf then?

一个很好的问题。

Is it simply an useless broken function that should never be used?

几乎没用。可以说,它已经完全崩溃了。它几乎不应该被使用。

Why is it in the libraries to begin with then?

我个人认为这是一个实验。它试图与 printf 相反。但事实证明,这在实践中并不是一个好主意,而且该功能从未被广泛使用,而且几乎失宠,除了一个特定的用例......

This seems really absurd, especially considering all beginners are taught to use scanf...

你完全正确。实在是太荒唐了。

教所有初学者使用 scanf 是有原因的。在你第一次 C 编程的第 1 周 class,你可能会编写这个小程序

#include <stdio.h>

int main()
{
    int size = 5;
    for(int i = 0; i < size; i++) {
        for(int j = 0; j < size; j++)
            putchar('*');
        putchar('\n');
    }
}

打印一个正方形。在第 1 周,要制作不同大小的正方形,只需编辑行 int size = 5; 并重新编译。

但是很快——比如说,在第 2 周——你想要一种方法让用户输入正方形的大小,而不必重新编译。您可能还没有准备好处理 argv。您可能还没有准备好使用 fgets 读取一行文本并使用 atoi 将其转换回整数。 (您可能甚至还没有准备好认真考虑整数 5 和字符串 "5" 之间的巨大差异。)所以 — 在您第一次 C 编程的第二周 class — scanf 似乎只是门票。

这就是我所说的“一个特定用例”。如果您在第一次 C 编程 class 的第二周只使用 scanf 将小整数读入简单的 C 程序,事情就不会那么糟糕。 (你仍然会忘记 &,但这或多或少是可以管理的。)

问题是(虽然这又是我个人的看法)它并不止于此。实际上,每位 C classes 的讲师都会教学生使用 scanf。不幸的是,很少或根本没有这些讲师明确告诉学生 scanf 是一个权宜之计,可以在第二周临时使用,并在以后的几周内重点毕业。而且,更糟糕的是,许多教师继续分配问题,涉及 scanfscanf 绝对不是一个好的解决方案,例如尝试进行稳健或“用户友好”的输入验证。

scanf 的唯一优点是它 看起来 是一种从用户那里获取小整数和其他简单输入到您的早期程序中的好而简单的方法。但问题——实际上是一大堆令人不寒而栗的 17 个独立问题——是 scanf 变得非常复杂,充满异常且难以使用,这与你想要的恰恰相反初学者的东西很容易。 scanf只对初学者有用,对初学者几乎完全没用。它被描述为像儿童自行车上的方形训练轮。

How do professional C developers handle this mess?

很简单:完全不使用 scanf。一方面,很少有生产 C 程序将提示打印到基于行的屏幕并要求用户键入一些内容,然后是 Return。对于那些以这种方式工作的程序,专业的 C 开发人员会毫不犹豫地使用 fgets 或类似的方法将一行输入读取为文本,然后使用其他技术分解该行以提取必要的信息。


在回答您最初的问题时,没有好的答案。 scanf 用法的基本规则之一(顺便说一下,没有任何老师教过的一组规则)是你永远不应该尝试混合使用 scanfgetchar(或 fgets) 在同一个程序中。如果有一种好方法可以让您的 "Press [enter] to continue..." 代码在调用 scanf 后正常工作,我们就不需要该规则了。

如果你确实想尝试刷新额外的换行符,以便稍后调用 getchar 可以工作,这里有几个问题和一些很好的答案:

  • scanf() leaves the newline character in the buffer
  • Using fflush(stdin)

还有一个不相关的点最终对你的问题非常重要。发明C的时候,还没有多重windows的GUI。因此,没有一个 C 程序员遇到过在他们可以读取之前让输出消失的问题。因此,没有一个 C 程序员觉得有必要先写 printf("Press [enter] to continue...");,然后再写 getchar()。我相信(另一个个人信念)对于任何基于 GUI 的 C 编译器的供应商来说,进行装配以使输出在程序退出时消失是非常糟糕的行为。持久输出 windows 应该是默认设置,为了初学者的利益,有一些非默认选项可以为那些不想要它的人关闭该行为。

scanf坏了吗?不它不是。当您想要解析预计很少有错误的自由格式输入数据时,它是一个出色的输入函数。自由形式在这里意味着新行与在普通屏幕上 read/write 非常长的段落不完全相关。当您从文件中读取时,预期的错误很少。

scanf 系列函数还有另一个优点:从标准输入流、文件流或字符串读取时,您具有相同的语法。它可以轻松解析简单的常见类型,并提供一个最小的 return 值,让谨慎的程序员知道是否可以解码全部或部分预期数据。

话虽这么说,但它有很大的缺点:首先是一个C函数,它不能直接控制程序员是否传递了符合格式规范的类型,其次,作为初学者,当他们忘记时并不总是被撞到头上控制它的return值,用它做完全坏掉的程序实在是太容易了。

但规则是:

  • 如果预期输入是面向行的,首先使用fgets获取行然后sscanftestingreturn两者的值
  • 仅当输入预计为自由格式(不相关的换行符)时,才应直接使用 scanf。但是 永远不会 不测试它的 return 值,除了琐碎的测试。

另一个缺点是初学者希望它聪明点。它确实可以解析简单的输入格式,但只是一个穷人的解析器:不要将它用作通用解析器,因为那不是它的用途。

只要遵守这些规则,它就是一个与大多数 C 语言及其标准库一致的好工具:一个可以做简单事情的简单工具。构建更丰富的工具取决于程序员或库实现者。

我才用了30多年C语言,从来没有被scanf咬过(嗯,我是初学者,但我现在知道 是罪魁祸首)。简单地说,我已经尝试了几十年,只将它用于它能做的事情...