当我尝试读取/解析输入时,我的C程序出现问题。
帮助?
这是一个FAQ条目。
StackOverflow有很多与C中读取输入有关的问题,答案通常集中在特定用户的特定问题上,而不是真正描绘整个画面。
这是一次全面覆盖一些常见错误的尝试,所以只需将这些错误标记为重复,就可以回答这一特定问题:
scanf("%d", ...)
/ scanf("%c", ...)
会失败?gets()
会崩溃?答案被标记为社区维基。随意改进并(谨慎)扩展。
答案 0 :(得分:27)
A"二进制模式"流的读取方式与写入的完全相同。但是,可能(或可能不)是在流末尾附加的实现定义数量的空字符(' \0
')。
A"文字模式"流可以进行多种转换,包括(但不限于):
'\n'
)更改为输出上的其他内容(例如,在Windows上为"\r\n"
),并在输入时返回'\n'
; isprint(c)
为真),水平制表符或换行符的字符。很明显,文本和二进制模式不会混合。以文本模式打开文本文件,以二进制模式打开二进制文件。
尝试打开文件可能由于各种原因而失败 - 缺少权限,或者找不到最常见的文件。在这种情况下,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,或&#34;为什么最后一行打印两次&#34;
如果已达到EOF,则函数feof()将返回true
。误解了什么&#34;达到&#34; EOF实际上意味着让许多初学者写下这样的东西:
// BROKEN CODE
while (!feof(fp)) {
fgets(buffer, BUFFER_SIZE, fp);
printf("%s", buffer);
}
这使得输入的最后一行打印两次,因为当读取最后一行时(直到最后一行,输入流中的最后一个字符), EOF是<强>不设置。
当您尝试阅读过去最后一个字符时,EOF才会被设置!
因此上面的代码再次循环,fgets()无法读取另一行,设置EOF 并保留buffer
未触及的内容,然后再次打印。< / p>
相反,请检查fgets
是否直接失败:
// GOOD CODE
while (fgets(buffer, BUFFER_SIZE, fp)) {
printf("%s", buffer);
}
永远不要使用gets()
There is no way to use this function safely.因此,随着C11的出现,语言已被删除。
请勿在{{1}}上使用fflush()或任何其他可供阅读的信息流
许多人希望stdin
放弃尚未阅读的用户输入。 它没有那样做。在普通的ISO C中,在输入流上调用fflush()有undefined behaviour。它确实在POSIX和MSVC中具有明确定义的行为,但这些行为都不会丢弃尚未读取的用户输入。
通常,读取清除待处理输入的正确方法并丢弃字符,包括换行符,但不得超出:
fflush(stdin)
请勿将*scanf()用于可能格式错误的输入
许多教程教您使用*scanf()来阅读任何类型的输入,因为它非常通用。
但*scanf()的目的实际上是读取大量数据,这些数据在处于预定义格式时可能会有些依赖。 (比如被另一个程序写的。)
即便如此,*scanf()也可以绊倒那些不注意的人:
int c;
do c = getchar(); while (c != EOF && c != '\n');
,[
和c
次转换)。 (见下一段。)当*scanf()无效时
*scanf()的一个常见问题是当输入流中有未读空格(n
,' '
,...)时,用户没有考虑到这一点。
读取数字('\n'
等)或字符串("%d"
)会在任何空格处停止。虽然大多数"%s"
转换说明符在输入中跳过前导空格,但*scanf()
,[
和c
却没有。因此,换行符仍然是第一个待处理的输入字符,导致n
和%c
无法匹配。
您可以通过明确阅读它来跳过输入中的换行符,例如通过fgetc(),或通过在*scanf()格式字符串中添加空格。 (格式字符串中的单个空格与输入中的任何空格数相匹配。)
我们只是建议不要使用*scanf(),除非你真的,积极地知道你在做什么。那么,作为替代品使用什么?
不是像*scanf()那样尝试一次读取和解析输入,而是将步骤分开。
通过fgets()
读取一部分输入(部分) fgets()有一个参数,用于将其输入限制为最多多个字节,从而避免缓冲区溢出。如果输入行确实完全适合您的缓冲区,缓冲区中的最后一个字符将是换行符(%[
)。如果它并不完全合适,那么你正在看一条部分读取的行。
解析内存中的行
特别适用于内存中解析的是strtol()和strtod()函数系列,它们提供与*scanf()转换说明符'\n'
,d
类似的功能,i
,u
,o
,x
,a
,e
和f
。
但是他们也告诉你完全他们停止解析的地方,并且对目标类型的数字进行了有意义的处理。
除此之外,C提供wide range of string processing functions。由于你已经在内存中输入,并且总是知道你已经解析了多远,你可以回过头来尝试理解输入。
如果所有其他方法都失败了,您可以使用整行来为用户打印有用的错误消息。
确保明确关闭您已成功打开的所有流。这会刷新任何尚未写入的缓冲区,并避免资源泄漏。
g