从文本文件读取后,结果显示不正确

After reading from text file, results are displayed incorrectly

我编写了一个程序,它从文本文件的每一行读取四个变量(三个字符串和一个字符)。但是当我显示变量时,每行末尾都会弹出一个意想不到的字符。 (我确保变量的长度足够大)。

这是为什么? (再次溢出缓冲区?)我该如何解决这个问题?

文本文件内容:

M0001 Cool Name F 123-456789
M0002 Name Cool M 987-654321

代码:

    #include <stdio.h>
    #include <stdlib.h>

    int main() {
        FILE *text;

        char id[6], name[101], gender, contact[13];

        text = fopen("test.txt", "r");
        while (fscanf(text, "%s %[^\n]s %c %s\n", id, name, &gender, contact) != EOF)
            printf("%s %s %c %s\n", id, name, gender, contact);
        fclose(text);

        return 0;

}

我期望的输出:

M0001 Cool Name F 123-456789
M0002 Name Cool M 987-654321

我得到的是:

M0001 Cool Name F 123-456789 1⁄4
M0002 Name Cool M 987-654321 1⁄4

%[^\n]s 吃光了从那一点开始的所有东西并把它放在 name 中。所以只有 idname 被填充。 gendercontact 具有来自程序堆栈的 'random' 内容(因为它们未初始化)。

不小心你的堆栈有 1/4 in gender + contact.

在我的机器上,程序崩溃了。

在对 fscanf() 的调用中,格式字符串:“%s %[^\n]s %c %s\n”不正确。

  1. '[^\n]' 将读取到行尾(这将溢出输入缓冲区:'name'。然后下一个字符不是 's',因为下一个字符是换行符。
  2. 应该将返回值与 4 进行比较,而不是 EOF
  3. input/format 说明符 '%[...]' 和 '%s' 没有溢出输入缓冲区的问题,所以应该总是有一个比输入缓冲区的长度(那些格式说明符总是将 NUL 字节附加到输入

建议代码如下:

  1. 干净地编译
  2. 记录包含每个头文件的原因
  3. 执行所需的功能
  4. 将 'name' 拆分为 'firstname' 和 'lastname' 以便于处理并匹配输入数据的格式
  5. 正确检查 fscanf()
  6. 的返回值
  7. 正确检查来自 fopen() 的任何错误,如果返回错误,则正确输出错误消息和说明系统认为函数失败的原因的文本 stderr
  8. fscanf()printf()
  9. 的调用使用适当的格式字符串
  10. 通过 enum 语句
  11. 将 'magic' 数字替换为有意义的名称

现在建议的代码:

#include <stdio.h>   // fopen(), fclose(), fscanf(), perror(), printf()
#include <stdlib.h>  // exit(), EXIT_FAILURE


enum{
    MAX_ID_LEN = 6,
    MAX_NAME_LEN = 20,
    MAX_CONTACT_LEN = 13
};


int main( void )
{
    char id[ MAX_ID_LEN ];
    char firstname[ MAX_NAME_LEN ];
    char lastname[ MAX_NAME_LEN ];
    char gender;
    char contact[ MAX_CONTACT_LEN ];

    FILE *text = fopen("test.txt", "r");
    if( !text )
    {
        perror( "fopen to read 'test.txt' failed" );
        exit( EXIT_FAILURE );
    }

    // implied else, fopen successful

    while (5 == fscanf(text, "%5s %19s %19s %c %12s",
        id, firstname, lastname, &gender, contact) )
    {
        printf("%s %s %s %c %s\n",
            id, firstname, lastname, gender, contact);
    }

    fclose(text);
    return 0;
}

由于你名字中 space 分隔的单词的数量显然是可变的,你只能使用 %[^\n]s 来获取 "as much as possible" – 但这也会吃掉所有的以下 相关数据。一个快速的解决方案是重新设计输入格式并将名称放在最后;那么,您的 fscanf 参数将是:

"%s %c %s %s\n", id, &gender, contact, name

或者,重写代码以使用更少 fscanf 和更多 'manual' 解析:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main (void)
{
    FILE *text;
    char id[6], name[101], gender, contact[13];
    char *lookback;
    int result;
    unsigned int line_number = 0;

    text = fopen ("test.txt", "r");
    if (text == NULL)
    {
        printf ("file not found!\n");
        return EXIT_FAILURE;
    }

    do
    {
        result = fscanf(text, "%s %[^\n]s\n", id, name);
        line_number++;
        if (result == EOF)
            break;

        if (result != 2)
        {
            printf ("error in data file on line %u (expected at least 2 items)\n", line_number);
            break;
        }

        /* at this point, 'name' also contains 'gender' and 'contact' */
        lookback = strrchr (name, ' ');
        if (lookback == NULL || strlen(lookback+1) > 12)
        {
            printf ("error in data file on line %u (expected 'contact')\n", line_number);
            break;
        }
        /* lookback+1 because lookback itself points to the space */
        strcpy (contact, lookback+1);
        /* cut off at lookback */
        *lookback = 0;

        lookback = strrchr (name, ' ');
        if (lookback == NULL || strlen(lookback+1) != 1)
        {
            printf ("error in data file on line %u (expected 'gender')\n", line_number);
            break;
        }
        /* lookback now points to the space before the gender */
        gender = toupper(lookback[1]);
        if (gender != 'F' && gender != 'M')
        {
            printf ("error in data file on line %u (expected 'M' or 'F')\n", line_number);
            break;
        }
        /* cut off again at lookback; now name is complete */
        *lookback = 0;

        printf ("%s %s %c %s\n", id, name, gender, contact);
    } while (1);
    fclose(text);

    return EXIT_SUCCESS;
}

这种方法确实有一些相关的缺点。 scanf 的好处之一是它标准化了 whitespace;多个 space 和选项卡(甚至 returns)将在 扫描之前默默地转换为单个 space 。另一方面,此代码 显式 检查单个 space 字符。如果您的数据文件中的白色 space 存在差异,您也必须考虑到这一点。

处理完最后两项 'manually' 后,您可以选择完全不使用 fscanf。您可以使用 fgets(它还内置了行长度检查)一次阅读整行文本,并使用 strchrstrrchr 查找 space。为了解决可能的白色space 问题,搜索制表符和双space 行并将它们更改为单个space.