【问题标题】:How to read and scan records of sctruture to and from file in C如何在C中读取和扫描文件的结构记录
【发布时间】:2020-12-12 01:33:51
【问题描述】:

几天前我开始了一个小项目,我应该做两件事:

  1. 创建一个函数来读取文件信息并将其显示在屏幕上。
  2. 创建一个将新记录写入文件的函数。 (添加新记录)

我的结构

struct cofeeShop
{
char cofeeName [20]
char cofeeColor [20]
chat cofeeWorld [20]
int CofeePockets
int onePocketSize 
}

包含此信息的文本文件“records.txt”:

Lavazza Gray Europe 433 10 
Machito Black Europe 433 10 
Machito White Asia 24 18 
Chiley Black Asia 198 17 
Hucki White America 11 11

我是 C 编程新手,因此我需要一些帮助。

我知道 C 语言有 4 种不同的扫描方法(fscanf、fgets、fgetc、freada),但我不知道在我的情况下哪种方法最适合结构扫描。主要区别是什么?

我需要一个数组来在屏幕上显示文件中的所有数据吗?如何将文件txt中的所有数据显示到屏幕上?

有什么建议吗?

感谢您的宝贵时间!

【问题讨论】:

  • 密钥是使用fgets()将您的输入读入一个足够大的缓冲区(字符arra),然后使用sscanf()将行中的值解析为临时struct cofeeShop,并验证转换通过检查sscanf() 的返回,您可以简单地将临时结构中的值添加到结构数组中。完成后,循环遍历数组,使用printf() 将值作为格式化输出写入屏幕,并使用fprintf() 将值写入文件。
  • Spliting each line by spaces in C 可能会有所帮助。但是,如果咖啡的名称可以不是由空格分隔的 2 个单词 - 这会使将输入行分隔为单独的结构值变得复杂。

标签: c


【解决方案1】:

如果您想要一个简短的示例来帮助您入门,那么在将字符串读入字符数组(缓冲区)时首先要记住的是不要吝啬缓冲区大小!。使用1020 可能会减少您显示的一小部分数据,但是当您尝试阅读Mandheling Black 时会发生什么?使用您预期最长的字符串,并且在使用固定数组时至少将字符数加倍(如果它是一个要重复使用的缓冲区,例如读取每一行,1K 缓冲区大小就可以了,2K 也可以)

尽量避免使用 MagicNumbers,例如 1020,而是:

#include <stdio.h>

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

typedef struct cofeeshop {      /* typedef avoid writing 'struct name' in code */
    char cofeeName [MAXNM],
        cofeeColor [MAXNM],
        cofeeWorld [MAXNM];
    int CofeePockets,
        onePocketSize; 
} cofeeshop;
...

如果您在上面注意到,struct cofeeshop 中的 typedef 将以名称 cofeeshop 创建。通过创建typedef,您可以简单地将cofeeshop 用作代码中任何需要它的类型,而不必每次都编写struct cofeeshop

您需要从文件中实际读取的是一个足够大以容纳每一行的缓冲区(字符数组),一个用于跟踪您已读取多少咖啡类型的计数器,然后是一个 cofeeshop 数组保存每行中包含的值。您可以这样做,同时将要读取的文件名作为程序的第一个参数(如果没有给出参数,则从 stdin 读取)如下:

int main (int argc, char **argv) {
    
    char line[MAXC];                                        /* buffer to hold each line */
    cofeeshop cofee[MAXCT] = {{.cofeeName = ""}};           /* array of chofeeshop */
    size_t n = 0;                                           /* cofeetype counter */
    /* 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;
    }
    ...
    

请注意,您始终验证所使用的任何输入/输出函数的返回值,包括 fopen() 是成功还是失败,在您尝试从文件中读取之前

然后您可以使用fgets() 将文件中的每一行读入缓冲区line,然后将line 传递给sscanf() 以将line 的各个部分分隔到结构数组中的下一个可用元素中,cofee,例如

    ...
    /* while array not full, read each line in file and add to array */
    while (n < MAXCT && fgets (line, MAXC, fp)) {
        /* separate into name, color, world, pockets, size & VALIDATE return */
        if (sscanf (line, "%s %s %s %d %d", cofee[n].cofeeName, cofee[n].cofeeColor, 
                    cofee[n].cofeeWorld, &cofee[n].CofeePockets,
                    &cofee[n].onePocketSize) == 5) {
            n++;    /* increment cofeetype counter */
        }
    }
    ...

注意:您验证 sscanf() 的返回值以检查 format-string 中每个 format-specifier 的转换@ 987654345@ 成功。 format-string 中有五个 format-specifiers,因此在考虑使用来自 line 的有效输入填充变量之前,请检查 return == 5

此时您已完成对输入的读取,因此如果未从 stdin 读取,只需关闭文件并将存储在数组中的所有值输出到 stdout,例如

    ...
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    for (size_t i = 0; i < n; i++)                          /* output stored values */
        printf ("%-12s  %-12s  %-12s  %4d  %4d\n", cofee[i].cofeeName, cofee[i].cofeeColor,
                cofee[i].cofeeWorld, cofee[i].CofeePockets, cofee[i].onePocketSize);
}

剩下的就是将值以您选择的格式写入输出文件——这留给您。

总而言之(即上面的完整代码),您将拥有:

#include <stdio.h>

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

typedef struct cofeeshop {      /* typedef avoid writing 'struct name' in code */
    char cofeeName [MAXNM],
        cofeeColor [MAXNM],
        cofeeWorld [MAXNM];
    int CofeePockets,
        onePocketSize; 
} cofeeshop;

int main (int argc, char **argv) {
    
    char line[MAXC];                                        /* buffer to hold each line */
    cofeeshop cofee[MAXCT] = {{.cofeeName = ""}};           /* array of chofeeshop */
    size_t n = 0;                                           /* cofeetype counter */
    /* 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 array not full, read each line in file and add to array */
    while (n < MAXCT && fgets (line, MAXC, fp)) {
        /* separate into name, color, world, pockets, size & VALIDATE return */
        if (sscanf (line, "%s %s %s %d %d", cofee[n].cofeeName, cofee[n].cofeeColor, 
                    cofee[n].cofeeWorld, &cofee[n].CofeePockets,
                    &cofee[n].onePocketSize) == 5) {
            n++;    /* increment cofeetype counter */
        }
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    for (size_t i = 0; i < n; i++)                          /* output stored values */
        printf ("%-12s  %-12s  %-12s  %4d  %4d\n", cofee[i].cofeeName, cofee[i].cofeeColor,
                cofee[i].cofeeWorld, cofee[i].CofeePockets, cofee[i].onePocketSize);
}

编译

每次编译您的程序时启用完整警告 -- 并且在编译没有警告之前不要接受代码,例如

gcc -Wall -Wextra -pedantic -Wshadow -std=c11 -Ofast -o bin/cofeeshop cofeeshop.c

-Wall -Wextra -pedantic 启用 gcc/clang 的完整警告,添加 -Wshadow 以捕获任何阴影变量(例如 i 在代码中的两个不同范围内声明和使用,这可能会导致问题)。对于 VS,使用 /W3 获得完整警告。对于其他编译器,只需阅读选项文档以确定需要什么。

使用/输出示例

使用文件dat/cofeeshop.txt 中的示例数据,您将拥有:

$ ./bin/cofeeshop dat/cofeeshop.txt
Lavazza       Gray          Europe         433    10
Machito       Black         Europe         433    10
Machito       White         Asia            24    18
Chiley        Black         Asia           198    17
Hucki         White         America         11    11

如果您想测试从stdin 读取,您可以将输入文件重定向到stdin,例如:

$ ./bin/cofeeshop < dat/cofeeshop.txt
Lavazza       Gray          Europe         433    10
Machito       Black         Europe         433    10
Machito       White         Asia            24    18
Chiley        Black         Asia           198    17
Hucki         White         America         11    11

查看一下,如果您还有其他问题,请告诉我。

(注意:cofee 通常拼写为 coffee——但会与您所拥有的 :) 一起使用

【讨论】:

  • 非常感谢您提供此代码,但我仍有一些问题。是否可以设置每种咖啡和国家名称的长度?因为在这个程序中,我将允许人们自己输入新的咖啡信息,以及如何避免输入过长的名称。另外,如果我想从两个单词中获得咖啡名称,如何解决问题?
  • 是的,您将数组更改为指针,然后为每个字符串分配内存,例如字符 *cofeeName,*cofeeColor,*cofeeWorld;然后使用sscanf() 你会读入临时数组,比如char name[128], color[128], world[128]; 在验证返回是5 之后,你会分配,例如size_t len = strlen(name); cofee[n].cofeeName = malloc (len + 1); 然后 memcpy (cofee[n].cofeeName, name, len + 1); (您将在 memcpy 之前验证分配)所以其他指针也是如此。然后,您的存储空间已完全适合您的数据。
  • 如果我这样做,我会使用char name[1024], color[1024], world[1024];(屏幕上大约有 12 - 20 行整行文本(或踩在键盘上的猫)。Linux 提供了 4- Meg 堆栈,Windows 1-Meg 堆栈。所以 3 个 1K 缓冲区,这只是可用堆栈空间的0.0029(或0.29%)——在 Windows 上。如果你在微控制器上,那么128 字节缓冲区更有意义。
猜你喜欢
  • 2021-11-10
  • 2015-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多