当我尝试读取/解析输入时,我的C程序出现问题。
救命?
这是一个常见问题解答条目。
StackOverflow有许多与读取C语言中的输入有关的问题,答案通常集中在特定用户的特定问题上,而没有真正描绘出整个画面。
这是一种尝试全面涵盖许多常见错误的尝试,因此可以通过将这些特殊问题标记为该问题的重复项来回答这些特定的问题:
为什么最后一行打印两次?
为什么我的scanf("%d", ...)
/ scanf("%c", ...)
失败?
为什么gets()
崩溃?
...
答案被标记为社区Wiki。随时改进和(谨慎地)扩展。
最佳答案
初学者的C输入入门
文本模式与二进制模式
检查fopen()是否失败
陷阱
检查您调用的任何功能是否成功
EOF或“为什么最后一行打印两次”
永远不要使用gets()
永远不要在stdin
上使用fflush()或任何其他可供阅读的流
不要将*scanf()用于可能格式错误的输入
当*scanf()不能按预期工作时
阅读,然后解析
通过fgets()读取(部分)输入行
在内存中解析行
清理
文本模式与二进制模式
完全按写入方式读取“二进制模式”流。但是,可能会(或可能不会)在流的末尾附加实现定义的数量的空字符('\0
')。
“文本模式”流可以进行许多转换,包括(但不限于):
在行尾之前删除空格;
将换行符('\n'
)更改为输出中的其他内容(例如Windows中的"\r\n"
),然后将其更改为输入中的'\n'
;
添加,更改或删除既不是打印字符(isprint(c)
为真),水平制表符也不是换行符的字符。
很明显,文本模式和二进制模式不会混合使用。在文本模式下打开文本文件,在二进制模式下打开二进制文件。
检查fopen()是否失败
尝试打开文件可能由于各种原因而失败-缺少权限,或者找不到文件是最常见的文件。在这种情况下,fopen()将返回NULL
指针。在尝试读取或写入文件之前,请始终检查fopen
是否返回了NULL
指针。
当fopen
失败时,通常会设置全局errno变量以指示失败的原因。 (从技术上讲,这不是C语言的要求,但是POSIX和Windows都保证可以做到这一点。)errno
是可以与errno.h
中的常量进行比较的代码,但是在简单的程序中,通常您所需要的要做的就是将其转换为错误消息并使用perror()
或strerror()
打印。错误消息还应包括您传递给fopen
的文件名;如果您不这样做,当文件名不是您想的那样时,您会感到非常困惑。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: %s file\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
// alternatively, just `perror(argv[1])`
fprintf(stderr, "cannot open %s: %s\n", argv[1], strerror(errno));
return 1;
}
// read from fp here
fclose(fp);
return 0;
}
陷阱
检查您调用的任何功能是否成功
这应该是显而易见的。但是,请务必检查调用的任何函数的文档以获取其返回值和错误处理,并检查这些条件。
这些错误很容易在您早日发现病情时出现,但如果不及时发现,则会导致很多麻烦。
EOF或“为什么最后一行打印两次”
如果已达到EOF,则函数feof()返回
true
。对“达到” EOF实际上意味着什么的误解使许多初学者写了这样的东西:// BROKEN CODE
while (!feof(fp)) {
fgets(buffer, BUFFER_SIZE, fp);
printf("%s", buffer);
}
这将使输入的最后一行打印两次,因为读取最后一行(直到最后一个换行符,即输入流中的最后一个字符)时,未设置EOF。
仅当您尝试读取最后一个字符时才设置EOF!
因此,上面的代码再次循环,fgets()无法读取另一行,设置了EOF,并且保持
buffer
的内容不变,然后再次打印。相反,请检查
fgets
是否直接失败:// GOOD CODE
while (fgets(buffer, BUFFER_SIZE, fp)) {
printf("%s", buffer);
}
永远不要使用gets()
There is no way to use this function safely.因此,随着C11的出现,它已从语言中删除。
永远不要在
stdin
上使用fflush()或任何其他可供阅读的流许多人期望
fflush(stdin)
放弃尚未读取的用户输入。它不会那样做。在普通ISO C中,在输入流上调用fflush()具有undefined behaviour。它在POSIX和MSVC中确实具有明确定义的行为,但是没有一个使它放弃尚未读取的用户输入。通常,清除未决输入的正确方法是读取并丢弃直到换行符(包括换行符)的字符,但不能超过:
int c;
do c = getchar(); while (c != EOF && c != '\n');
不要将*scanf()用于可能格式错误的输入
许多教程都教您使用*scanf()读取任何类型的输入,因为它用途广泛。
但是*scanf()的目的实际上是读取在某种程度上依赖于预定义格式的批量数据。 (例如由另一个程序编写。)
即使这样,*scanf()也会使看不见的人绊倒:
使用以某种方式可能会受到用户影响的格式字符串是一个巨大的安全漏洞。
如果输入与预期格式不匹配,*scanf()将立即停止解析,而保留所有剩余参数未初始化。
它会告诉您已成功完成了多少个分配-这就是为什么您应检查其返回码(请参见上文)的原因-但不能确切地知道它停止解析输入的位置,从而难以进行正常的错误恢复。
它会跳过输入中的所有前导空格,除非不这样做(
[
,c
和n
转换)。 (请参阅下一段。)在某些极端情况下,它的行为有些特殊。
当*scanf()不能按预期工作时
*scanf()的一个常见问题是输入流中存在用户未解决的未读空格(
' '
,'\n'
,...)。读取数字(
"%d"
等)或字符串("%s"
)在任何空白处停止。虽然大多数*scanf()
转换说明符会跳过输入中的前导空格,但[
,c
和n
不会。因此,换行符仍然是第一个待处理的输入字符,从而使%c
和%[
都不匹配。您可以通过显式读取它来跳过输入中的换行符,例如通过fgetc()或在*scanf()格式字符串中添加空格。 (格式字符串中的单个空格匹配输入中任意数量的空格。)
阅读,然后解析
我们只是建议不要使用*scanf(),除非您真正肯定地知道自己在做什么。那么,用什么代替呢?
不必像*scanf()那样一口气读取和解析输入,而是将步骤分开。
通过fgets()读取(部分)输入行
fgets()有一个参数,用于将其输入限制为最多字节,从而避免缓冲区溢出。如果输入行确实完全适合您的缓冲区,则缓冲区中的最后一个字符将是换行符(
'\n'
)。如果不是全部都适合,那么您正在看的部分阅读的行。在内存中解析行
strtol()和strtod()函数系列对于内存中解析特别有用,它们提供与*scanf()转换说明符
d
,i
,u
,o
,x
, a
,e
,f
和g
。但是它们还会告诉您确切的停止解析的位置,并对目标类型太大的数字进行有意义的处理。
除此以外,C提供了一个wide range of string processing functions。由于您将输入存储在内存中,并且始终准确地知道您已经对其进行了解析,因此您可以随意走很多次,以尝试理解输入。
如果所有其他方法都失败,则您可以使用整行来为用户打印一条有用的错误消息。
清理
确保您明确关闭(成功)打开的所有流。这将刷新所有尚未写入的缓冲区,并避免资源泄漏。
fclose(fp);
关于c - 如何在C中读取/解析输入?常见问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47419842/