像戈文德·帕尔玛已经建议了 https://stackoverflow.com/a/54370533/1475978,更好/更容易使用fgets()
读取整行输入,而不是使用scanf()
等人。用于人机交互输入。
The underlying reason is that the interactive standard input is line-buffered by default (and changing that is nontrivial). So, when the user starts typing their input, it is not immediately provided to your program; only when the user presses Enter.
如果我们确实使用读取每一行输入fgets()
,然后我们可以使用扫描并转换它sscanf() http://man7.org/linux/man-pages/man3/sscanf.3.html,其工作原理很像scanf()
/fscanf()
做,除了sscanf()
适用于字符串输入,而不是输入流。
这是一个实际的例子:
#include <stdlib.h>
#include <stdio.h>
#define MAX_LINE_LEN 100
int main(void)
{
char buffer[MAX_LINE_LEN + 1];
char *line, dummy;
double value;
while (1) {
printf("Please type a number, or Q to exit:\n");
fflush(stdout);
line = fgets(buffer, sizeof buffer, stdin);
if (!line) {
printf("No more input; exiting.\n");
break;
}
if (sscanf(line, " %lf %c", &value, &dummy) == 1) {
printf("You typed %.6f\n", value);
continue;
}
if (line[0] == 'q' || line[0] == 'Q') {
printf("Thank you; now quitting.\n");
break;
}
printf("Sorry, I couldn't parse that.\n");
}
return EXIT_SUCCESS;
}
The fflush(stdout);
没有必要,但也没有坏处。它基本上保证了我们拥有的一切printf()
或写信给stdout
,将刷新到文件或设备;在这种情况下,它将显示在终端中。 (这里没有必要,因为标准输出默认也是行缓冲的,所以\n
在 printf 模式中,打印换行符也会导致刷新。
我确实喜欢洒那些fflush()
调用,无论何时我需要记住这一点,重要的是所有输出实际上都刷新到输出,而不是由 C 库缓存。在这种情况下,我们绝对希望在开始等待用户输入之前提示对用户可见!
(但是,再说一次,因为printf("...\n");
在以换行符结束之前,\n
,并且我们没有改变标准输出缓冲,fflush(stdout);
那里不需要。)
The line = fgets(buffer, sizeof buffer, stdin);
该行包含几个重要的细节:
-
我们定义了宏MAX_LINE_LEN
早些时候,因为fgets()
只能读取给定缓冲区内的一行,并将在后续调用中返回该行的其余部分。
(您可以检查读取的行是否以换行符结束:如果不是,则它是输入文件中的最后一行,最后一行的末尾没有换行符,或者该行比您拥有缓冲区,因此您只收到了初始部分,该行的其余部分仍在缓冲区中等待您。)
The +1
in char buffer[MAX_LINE_LEN + 1];
是因为 C 中的字符串以 nul 字符终止,'\0'
,最后。因此,如果我们有一个 19 个字符的缓冲区,它最多可以容纳 18 个字符的字符串。
-
请注意,NUL 或 nul 带 1 个 ell,是代码为 0 的 ASCII 字符的名称,'\0'
,并且是字符串结束标记字符。
然而,NULL(或有时为 nil)是指向零地址的指针,在 C99 及更高版本中与(void *)0
。当我们想要设置一个指向可识别的错误/未使用/无值的指针,而不是指向实际数据时,它是我们使用的哨兵和错误值。
-
sizeof buffer
是变量使用的字符总数(包括字符串结尾 nul 字符)buffer
.
在这种情况下,我们可以使用MAX_LINE_LEN + 1
相反(第二个参数fgets()
是缓冲区中赋予它的字符数,包括字符串结尾字符的保留)。
我使用的原因sizeof buffer
在这里,是因为它太有用了。 (请记住,如果buffer
是一个指针而不是一个数组,它将计算出指针的大小;不是该指针指向的可用数据量。如果您使用指针,则需要自己跟踪可用内存量,通常在单独的变量中。这就是 C 的工作原理。)
也因为重要的是sizeof
is not一个函数,但一个运算符:它不评估其参数,它只考虑参数的(类型)大小。这意味着如果你做了一些愚蠢的事情,比如sizeof (i++)
,你会发现i
is not递增,并且它产生的值与sizeof i
。再说一遍,这是因为sizeof
是一个运算符,而不是一个函数,它只返回其参数的大小。
-
fgets()
返回一个指向它存储在缓冲区中的行的指针,或者NULL
如果发生错误。
这也是我将指针命名为line
,以及存储阵列buffer
。他们描述了我作为程序员的意图。 (顺便说一句,在编写注释时这一点非常重要:不要描述代码的作用,因为我们可以阅读代码;但一定要描述代码应该做什么的意图,因为只有程序员知道这一点,但它如果试图理解、修改或修复代码,了解其意图很重要。)
-
scanf() 系列函数返回成功转换的数量。要检测正确数值后跟垃圾的输入,例如1.0 x
, 我问sscanf()
忽略数字后面的任何空格(空格表示制表符、空格和换行符;'\t'
, '\n'
, '\v'
, '\f'
, '\r'
, and ' '
对于使用 ASCII 字符集的默认 C 语言环境),并尝试转换单个附加字符,dummy
.
现在,如果该行在数字后面包含除空格之外的任何内容,sscanf()
将存储该内容的第一个字符dummy
,并返回 2。但是,因为我只想要只包含数字且不包含虚拟字符的行,所以我期望返回值为 1。
-
检测q
or Q
(但仅作为该行的第一个字符),我们只需检查第一个字符line
, line[0]
.
如果我们包括<string.h>
,我们可以使用例如if (strchr(line, 'q') || strchr(line, 'Q'))
看看是否有q
or Q
提供的行中的任何位置。这strchr(string, char) http://man7.org/linux/man-pages/man3/strchr.3.html返回指向字符串中第一次出现 char 的指针,如果没有则返回 NULL;除 NULL 之外的所有指针在逻辑上都被认为是正确的。 (也就是说,我们可以等效地写if (strchr(line, 'q') != NULL || strchr(line, 'Q') != NULL)
.)
我们可以使用的另一个函数声明在<string.h>
is strstr()
。它的工作原理就像strchr()
,但第二个参数是一个字符串。例如,(strstr(line, "exit"))
只有当line
has exit
在它的某个地方。 (它可能是brexit
or exitology
, 尽管;这只是一个简单的子字符串搜索。)
在一个循环中,continue
跳过循环体的其余部分,并从头开始循环体的下一次迭代。
在一个循环中,break
跳过循环体的其余部分,并在循环后继续执行。
EXIT_SUCCESS
and EXIT_FAILURE
是标准退出状态代码<stdlib.h>
定义。最喜欢使用0
对于 EXIT_SUCCESS (因为这是大多数操作系统中的情况),但我认为像这样拼写成功/失败可以更轻松地阅读代码。