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

问题描述 投票:2回答:3

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

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

文本文件内容:

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
c string text-files stdio
3个回答
5
投票

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

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

以下提议的代码:

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

3
投票

从那时起,%[^\n]s吃掉所有东西,然后把它放在name。所以只填充idnamegendercontact具有来自程序堆栈的“随机”内容(因为它们未初始化)。

偶然你的堆栈在1/4 + gendercontact

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


0
投票

由于你名字中以空格分隔的单词的数量显然是可变的,你只能使用%[^\n]s来“尽可能多地”获取 - 但这也将占用任何和所有后续相关数据。一个快速的解决方案是重新设计输入格式并将名称放在最后;那么,你的fscanf论证将是:

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

或者,重写代码以使用更少的fscanf和更多“手动”解析:

#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的一个好处是它使空白正常化;在扫描之前,多个空格和制表符(甚至返回)将以静默方式转换为单个空格。另一方面,此代码显式检查单个空格字符。如果数据文件中的空格存在差异,则还必须考虑到这一点。

最后两项“手动”处理后,您可以选择不使用fscanf。您可以使用fgets(内置行长检查)一次读取整行文本,并使用strchrstrrchr查找空格。要解决可能的空白问题,请在行中搜索制表符和双空格,然后将这些更改为单个空格。

© www.soinside.com 2019 - 2024. All rights reserved.