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

时间:2017-12-26 12:22:15

标签: c string text-files stdio

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

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

文本文件内容:

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

3 个答案:

答案 0 :(得分:5)

fscanf()的调用中,格式字符串:&#34;%s%[^ \ n] s%c%s \ n&#34;是不正确的。

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

    1. 干净地编译
    2. 记录每个头文件包含的原因
    3. 执行所需的功能
    4. 拆分&#39;名称&#39;进入&#39; firstname&#39;和&#39;姓氏&#39;为了便于处理和匹配输入数据的格式
    5. 正确检查fscanf()
    6. 的返回值
    7. 正确检查来自fopen()的任何错误,如果返回错误,请正确输出错误消息以及指示系统认为函数失败的原因的文本stderr
    8. 使用适当的格式字符串来调用fscanf()printf()
    9. 取代魔法&#39;带有意义名称的数字通过enum声明
    10. 现在建议的代码:

      #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;
      }
      

答案 1 :(得分:3)

%[^\n]s从那时开始吃掉所有内容并将其放入name。因此只填充idnamegendercontact具有来自程序堆栈的“随机”内容(因为它们未初始化)。

1/4 + gender中,您的筹码不小心contact

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

答案 2 :(得分:0)

由于您名称中以空格分隔的单词数量显然是可变的,您只能使用%[^\n]s来“尽可能多地”抓取 - 但这也会消耗掉任何以下的 / em>相关数据。一个快速的解决方案是重新设计输入格式并将名称放在最后;那么,你的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查找空格。要解决可能的空白问题,请在行中搜索制表符和双精度空格,然后将其更改为单个空格。