【问题标题】:Read Text File with blank lines读取带有空行的文本文件
【发布时间】:2019-05-16 02:23:02
【问题描述】:

文本文件可写,结构后有 1 个空行。
类似于:

1 111 1 彼得 22 22 2 约翰·莱斯 3 3 3 安妮·贝尔格斯

结构是:

struct estruturaCarro {
    int id, potencia, avariado;
    char name[11]; 
} carro;

我已将数据写入文本文件:

fprintf(fp, "%-2d %-3d %-1d %-10s \n\n", carro.id, carro.potencia, carro.avariado, carro.name);

我以这种方式读取文件数据,但我确信这不是最好的方式:

while(true){
    int result = fscanf(fp, "%d %d %d %10[^\n]", &carro.id, &carro.potencia, &carro.avariado, &carro.name);
    if(result==4){    
            printf("%-2d %-3d %-1d % -s\n", carro.id, carro.potencia, carro.avariado, carro.name);
    }else break;
}

如何在不读取空行的情况下读取文本文件、保存数据结构?

如果我想验证值读取(ID = xx,potencia = xxx,avariado = x,name = 10个字符),是在填充struct数组之前还是之后更好?文件每一行的格式为:
xx xxx x aaaaaaaaaa(x = 数字,a = 字符)
例如,如果其中一行是
9 123 4 Error 1st value
停止读取文件(通知用户),因为该行应该是:
9 123 4 Error 1st value(9 之后缺少一个空格) - 或者因为 NAME 有超过 10 个字符。

【问题讨论】:

  • while( fscanf(fp, "%d %d %d %10[^\n]", &carro.id, &carro.potencia, &carro.avariado, carro.name) == 4) { printf("%-2d %-3d %-1d %-s\n", carro.id, carro.potencia, carro.avariado, carro.name); } 似乎有效
  • 或者更好,只需使用fgets()然后sscanf进行解析。您可以检查该行中的第一个字符是否为'\n',只需continue 即可在该点读取下一行。
  • 10 个字符对于名称来说非常短。甚至我在 SO 上的名字也比这更长。

标签: c file scanf


【解决方案1】:

你没有展示true是如何在while(true)中设置的,但它必须基于你接近它的方式返回fscanf。您必须测试三种情况:

  1. fscanf 返回是EOF,读完,true 应该设置为FALSE 以打破循环;
  2. fscanf返回小于4匹配输入失败,不保证由于空行而发生;和
  3. fscanf 返回等于 4,很好的输入。

除非您同时检查这三个,否则您无法涵盖fscanf 的所有情况,此外,如果您的输入文件中有任何额外或无关的字符,您的格式化读取将失败。

这就是为什么更好的选择是使用fgets 将每一行读入缓冲区,然后使用sscanf 从缓冲区本身解析您需要的信息。这提供了允许单独验证 (1) 读取的好处; (2)信息的解析。此外,由于您一次使用一行,因此输入流中剩余的内容没有不确定性,并且解析任何一行的失败不会阻止成功读取其余部分。

使用fgets()sscanf() 将每个结构读入结构数组的简短实现,您可以执行类似于以下的操作:

#include <stdio.h>

#define MAXN   11   /* if you need a constant, #define one (or more) */
#define MAXC 1024   /*         (don't skimp on buffer size)          */

typedef struct {    /* use a simple typedef */
    int id, potencia, avariado;
    char name[MAXN]; 
} carro_t;

int main (int argc, char **argv) {

    char buf[MAXC];                         /* buffer to read line */
    carro_t carro[MAXN] = {{ .id = 0 }};    /* array of struct  */
    size_t ndx = 0;                         /* array index      */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (ndx < MAXN && fgets (buf, MAXC, fp)) {   /* read each line */
        carro_t tmp = { .id = 0 };                  /* temp struct */
        if (*buf == '\n')           /* if line empty */
            continue;               /* get next line */
        if (sscanf (buf, "%d %d %d %10[^\n]",   /* separate/validate */
                    &tmp.id, &tmp.potencia, &tmp.avariado, tmp.name) == 4)
            carro[ndx++] = tmp;     /* add to array of struct */
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < ndx; i++)
        printf ("%3d  %3d  %3d  %s\n",
            carro[i].id, carro[i].potencia, carro[i].avariado, carro[i].name);

    return 0;
}

注意:文件名作为程序的第一个参数提供,如果没有提供文件名,则默认从stdin读取)

虽然对于您的特定数据文件,您没有理由不能使用fscanf,但它很脆弱,一个字符过多(如"Anne Belgss")会导致其损坏。 fscanf 实现删除 true 并像你一样简单地循环:

    for (;;) {
        carro_t tmp = { .id = 0 };
        if (fscanf (fp, "%d %d %d %10[^\n]",    /* separate/validate */
                    &tmp.id, &tmp.potencia, &tmp.avariado, tmp.name) == 4)
            carro[ndx++] = tmp;     /* add to array of struct */
        else
            break;
    }

无论哪种方式,您都将使用“this”输入文件产生相同的输出,例如

使用/输出示例

$ ./bin/readwblanks ~/tmpd/file
  1  111    1  Peter
 22   22    2  John Lays
  3    3    3  Anne Belgs

【讨论】:

  • 大卫,你能建议我解决最后一个疑问吗?谢谢。
  • 如果我理解并且您想将这些行与测试行进行匹配,您实际上可以采用以下两种方法之一。通常,您将从文件中读取所有数据,然后您可以随意查询数据。第二个实例是您只想从文件中读取一个值的地方。如果您保证搜索中的空格"xx xxx x aaaaaaaaaa" 与文件中的空格完全匹配,则可以在读取的每一行上使用简单的strcmp。但通常情况下,这并不能保证。所以阅读结构和比较成员是最好的。文件 I/O 很慢。所以将所有内容读入结构。
【解决方案2】:

只要使用简单的阅读方式就可以了,像这样。

    while(fscanf(fp,"%d %d %d %s",&carro.id,&carro.potencia,&carro.avariado,carro.name)!=EOF)
    printf("%d %d %d %s\n",carro.id,carro.potencia,carro.avariado,carro.name);

结果是这样的 enter image description here

【讨论】:

  • 将失败,"%s" 用于读取“名称”字段,因为 "%s" 读取到第一个 空格 时,名称的第二部分未读前导下次调用fscanf时出现匹配失败
猜你喜欢
  • 1970-01-01
  • 2015-01-15
  • 1970-01-01
  • 1970-01-01
  • 2019-12-13
  • 1970-01-01
  • 2019-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多