如何读取/解析C中的输入? FAQ

当我尝试读取/解析输入时,我的C程序出现问题。

帮帮我?


这是一个FAQ条目。

StackOverflow有很多与阅读C语言输入有关的问题,答案通常集中在特定用户的特定问题上,而不是真正绘制整个图片。

这是一个全面涵盖一些常见错误的尝试,所以这个特定的问题家族可以简单地通过将它们标记为这个问题的重复来回答:

  • 为什么最后一行打印两次?
  • 为什么我的scanf("%d", ...) / scanf("%c", ...)失败?
  • 为什么gets()崩溃?
  • ...
  • 答案被标记为社区维基。 随意改进和(谨慎地)扩展。


    初学者的C输入入门

  • 文本模式与二进制模式
  • 检查fopen()是否失败
  • 陷阱
  • 检查您呼吁成功的任何功能
  • EOF或“为什么最后一行打印两次”
  • 不要使用gets(),永远不要
  • 不要在stdin或任何其他打开阅读的流上使用fflush()
  • 不要将* scanf()用于可能格式错误的输入
  • 当* scanf()不能按预期工作时
  • 阅读,然后解析
  • 通过fgets()读取(部分)输入行
  • 分析内存中的行
  • 清理

  • 文本模式与二进制模式

    “二进制模式”数据流的读入方式与写入时完全相同。 但是,可能(或可能不)是在流末尾附加的实现定义数量的空字符(' ')。

    “文本模式”流可以进行多种转换,包括(但不限于):

  • 在行结束之前立即移除空格;
  • 在输出上改变换行符( 'n' )为其他内容(例如Windows上的"rn" )并返回到'n' ;
  • 添加,修改或删除非打印字符( isprint(c)为true)的字符,水平制表符或换行符。
  • 很明显,文本和二进制模式不会混合。 以文本模式打开文本文件,以二进制模式打开二进制文件。

    检查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 filen", argv[0]);
            return 1;
        }
    
        FILE *fp = fopen(argv[1], "rb");
        if (!fp) {
            // alternatively, just `perror(argv[1])`
            fprintf(stderr, "cannot open %s: %sn", 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);
        puts(buffer);
    }
    

    这会使输入的最后一行打印两次,因为在读取最后一行时(直到最后的换行符,输入流中的最后一个字符),EOF 设置。

    当您尝试读取最后一个字符时EOF才会被设置!

    因此,上面的代码再次循环,fgets()无法读取另一行,设置EOF并保留buffer的内容不变,然后再次打印。

    相反,请检查fgets是否直接失败:

    // GOOD CODE
    while (fgets(buffer, BUFFER_SIZE, fp)) {
        puts(buffer);
    }
    

    不要使用gets(),永远不要

    无法安全地使用此功能。 正因为如此,随着C11的出现,它已经从语言中移除了。

    不要在stdin或任何其他打开阅读的流上使用fflush()

    许多人希望fflush(stdin)放弃尚未读取的用户输入。 它不这样做。 在普通的ISO C中,在输入流上调用fflush()具有未定义的行为。 它在POSIX和MSVC中的确具有明确定义的行为,但这些行为都不会丢弃尚未读取的用户输入。

    通常,清除未决输入的正确方法是读取并放弃直到并包括换行符的字符,但不能超出:

    int c;
    do c = getchar(); while (c != EOF && c != 'n');
    

    不要将* scanf()用于可能格式错误的输入

    许多教程指导您使用* scanf()读取任何类型的输入,因为它非常灵活。

    但是* scanf()的目的实际上是读取可能依赖于预定义格式的批量数据。 (如由另一个程序写入。)

    即使那样* scanf()可以跳过不受保护的:

  • 使用某种格式的字符串可能会受到用户的影响,这是一个巨大的安全漏洞。
  • 如果输入与预期格式不匹配,* scanf()会立即停止解析,而剩下的参数将保持未初始化状态。
  • 它会告诉你它已经成功完成了多少任务 - 这就是为什么你应该检查它的返回码 (参见上面的内容),而不是停止解析输入的地方,从而使错误恢复变得困难。
  • 它跳过输入中的任何主要空格,除非没有( [cn转换))。 (见下一段。)
  • 它在某些角落案件中有些奇特的行为。
  • 当* scanf()不能按预期工作时

    * scanf()的一个常见问题是输入流中有未读空白( ' ''n' ,...),用户没有考虑这个空白。

    读取一个数字( "%d"等)或一个字符串( "%s" )可以停止任何空格。 虽然大多数*scanf()转换说明符会忽略输入中的前导空格,但[cn不会。 所以换行符仍然是第一个挂起的输入字符,使得%c%[不匹配。

    您可以跳过输入中的换行符,例如通过fgetc()显式读取它,或者向* scanf()格式的字符串添加空格。 (格式字符串中的单个空格与输入中的任意数量的空白匹配。)

    阅读,然后解析

    我们只是建议不要使用* scanf(),除非你真的积极地知道你在做什么。 那么,用什么来替代?

    正如* scanf()试图做的那样,不是一次读取和解析输入,而是分离这些步骤。

    通过fgets()读取(部分)输入行

    fgets()有一个参数,用于限制其输入至少多个字节,避免缓冲区溢出。 如果输入行完全适合您的缓冲区,缓冲区中的最后一个字符将成为换行符( 'n' )。 如果不是,你正在看一个部分阅读的线。

    分析内存中的行

    特别适用于内存分析的是strtol()和strtod()函数系列,它们提供与* scanf()转换说明符diuoxaefg相似的功能。

    但他们也会告诉你他们停止解析的位置,并且对目标类型的数字进行有意义的处理。

    除此之外,C还提供了广泛的字符串处理功能。 既然你在内存中有输入,并且始终知道你已经分析了多少,你可以多次尝试理解输入。

    如果一切都失败了,您可以使用整行来为用户打印有用的错误消息。

    清理

    确保你明确地关闭了你已经(成功)打开的任何流。 这会刷新所有尚未写入的缓冲区,并避免资源泄漏。

    fclose(fp);
    
    链接地址: http://www.djcxy.com/p/49997.html

    上一篇: How to read / parse input in C? The FAQ

    下一篇: Algorithm's name