【问题标题】:How to read a text file and store in an array in C如何读取文本文件并存储在 C 中的数组中
【发布时间】:2021-02-09 04:21:05
【问题描述】:

脚本成功打印了文本文件但是我想将文本文件中的内容存储到一个数组中,我查看了很多地方但我并不完全了解我遇到了什么信息,无论如何我可以得到一些指导?

#include <stdlib.h>

int main() 
{
       
// OPENS THE FILE
    FILE *fp = fopen("/classes/cs3304/cs330432/Programs/StringerTest/people.txt", "r");

    size_t len = 1000;
    char *word = malloc(sizeof(char) * len);
// CHECKS IF THE FILE EXISTS, IF IT DOESN'T IT WILL PRINT OUT A STATEMENT SAYING SO     
    if (fp == NULL) 
    {
        printf("file not found");
        return 0;
    }
    while(fgets(word, len, fp) != NULL) 
    {
        printf("%s", word);
    }
    free(word);
    
}

文本文件中包含以下内容(只是单词列表):

endorse
vertical
glove
legend
scenario
kinship
volunteer
scrap
range
elect
release
sweet
company
solve
elapse
arrest
witch
invasion
disclose
professor
plaintiff
definition
bow
chauvinist

【问题讨论】:

  • 为什么不用fseek()ftell() 测试文件长度并适当调整大小,而不是假设 1000 就足够了。一旦确定了大小,如果它不是巨大的,您可以使用 fread() 将整个内容读入内存并使用 \r\n 解析。
  • 谢谢!把单词列表变成一个数组怎么样?
  • 您可以将文件作为字符串扫描并查找行分隔符,例如\r\n 序列,然后根据需要拆分为单独的字符串,将指针放入您正在创建的数组中。第一次计数,第二次插入通常效果最好,因为您可以完美地调整数组的大小,而不是猜测和扩展。
  • 你是说信息已经在数组中了?我以为它不在数组中
  • 考虑在open(2) 之后使用mmap(2)。也许使用sscanf(3)

标签: arrays c string


【解决方案1】:

让我们看看我们是否无法让您理顺。首先,您的思路是正确的,应该表扬您使用fgets() 将每一行读入固定缓冲区(字符数组),然后您需要收集并存储所有行以便它们可用供您的程序使用——这似乎是车轮脱落的地方。

方法的基本大纲

总而言之,当您想要处理无限数量的行时,您需要分配和管理两种不同类型的内存块。第一个是您分配的一块内存,它将保存一定数量的指针(您将存储的每一行一个)。最初分配多少并不重要,因为您将跟踪分配的数量(可用数量)和使用的数量。当(used == available) 你将realloc() 更大的内存块来容纳更多的指针并继续前进。

您将处理的第二种类型的内存块是每一行的存储。那里没有奥秘。您将为每个字符分配存储空间(+1 用于 null-terminating 字符)并将该行从固定缓冲区复制到分配的块。

这两个内存块一起工作,因为要创建您的集合,您只需将保存数据行的内存块的地址分配给下一个可用指针。

让我们考虑一个简短的例子,我们将char **lines; 声明为指向持有指针的内存块的指针。然后假设我们最初分配两个指针,我们有可用于lines[0]lines[1] 的有效指针。我们跟踪nptrs 可用的指针数量和used 使用的数字。所以最初是nptrs = 2;used = 0;

当我们使用fgets() 读取第一行时,我们将从字符串末尾修剪'\n',然后获取字符串的长度(len = strlen(buffer);)。然后我们可以为字符串分配存储空间,将分配块的地址分配给我们的第一个指针,例如

lines[used] = malloc (len + 1);

然后将buffer的内容复制到lines[0],例如

memcpy (lines[used], buffer, len + 1);

(注意: 没有理由调用 strcpy() 并让它再次扫描字符串结尾,我们已经知道要复制多少个字符 - 包括 nul -终止字符)

最后,让我们的计数器满意所需要做的就是将used 加一。我们以相同的方式存储下一行,在第三次迭代 used == nptrs 时,我们 realloc() 更多的指针(通常每次需要 realloc() 时指针的数量增加一倍)。这是对realloc() 的调用和指针数量增长之间的一个很好的平衡——但您可以随意增加分配的任何方式——但要避免为每一行调用realloc()

所以你继续阅读行,检查是否需要realloc(),如果需要重新分配,并为每一行分配开始地址依次分配给每个指针。唯一要注意的是,当您realloc() 时,您始终使用临时指针,因此当realloc() 失败并返回NULL 时,您不会用NULL 覆盖您的原始指针,从而丢失内存块的起始地址指针——造成内存泄漏。

实施

概述中省略了详细信息,因此让我们看一个简短的示例,从文件中读取未知数量的行(每行为 1024 字符或更少)并使用 pointer-to-pointer 指向char,如上所述。不要在代码中使用 Magic-Numbers

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

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define NPTRS   2       /* initial no. of pointers to allocate (lines) */

也不要在代码中硬编码文件名,即argcargv 用于int main (int argc, char **argv)。将要读取的文件名作为第一个参数传递给程序(如果没有给出参数,则默认从stdin 读取):

int main (int argc, char **argv) {
    
    char buf[MAXC],                 /* fixed buffer to read each line */
        **lines = NULL;             /* pointer to pointer to hold collection of lines */
    size_t  nptrs = NPTRS,          /* number of pointers available */
            used = 0;               /* number of pointers used */
    /* 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;
    }

注意:你不应该仅仅为了从不同的文件名读取而重新编译你的程序)

现在分配并验证您的初始指针数量

    /* allocate/validate block holding initial nptrs pointers */
    if ((lines = malloc (nptrs * sizeof *lines)) == NULL) {
        perror ("malloc-lines");
        exit (EXIT_FAILURE);
    }

读取每一行并从末尾修剪 '\n' 并获取删除 '\n' 后剩余的字符数(您可以使用 strcspn() 一次完成所有操作):

    while (fgets (buf, MAXC, fp)) {                 /* read each line into buf */
        size_t len;
        buf[(len = strcspn (buf, "\n"))] = 0;       /* trim \n, save length */

接下来我们检查是否需要重新分配,如果需要,使用临时指针重新分配:

        if (used == nptrs) {    /* check if realloc of lines needed */
            /* always realloc using temporary pointer (doubling no. of pointers) */
            void *tmp = realloc (lines, (2 * nptrs) * sizeof *lines);
            if (!tmp) {                             /* validate reallocation */
                perror ("realloc-lines");
                break;                              /* don't exit, lines still good */
            }
            lines = tmp;                            /* assign reallocated block to lines */
            nptrs *= 2;                             /* update no. of pointers allocatd */
            /* (optionally) zero all newly allocated memory here */
        }

现在分配并验证该行的存储并将该行复制到新存储,完成后递增used - 完成您的读取循环。

        /* allocate/validate storage for line */
        if (!(lines[used] = malloc (len + 1))) {
            perror ("malloc-lines[used]");
            break;
        }
        memcpy (lines[used], buf, len + 1);         /* copy line from buf to lines[used] */
        
        used += 1;                                  /* increment used pointer count */
    }
    /* (optionally) realloc to 'used' pointers to size no. of pointers exactly here */
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);

现在您可以在程序中根据需要使用存储在lines 中的行,记住在完成后释放每一行的内存,然后最终释放指针块,例如

    /* use lines as needed (simply outputting here) */
    for (size_t i = 0; i < used; i++) {
        printf ("line[%3zu] : %s\n", i, lines[i]);
        free (lines[i]);                            /* free line storage when done */
    }
    
    free (lines);       /* free pointers when done */
}

这就是我们所需要的。现在您可以阅读 /usr/share/dict/words 中的 324,000 个单词(或者可能在您的系统上 /var/lib/dict/words 取决于发行版),这样做不会有任何问题。

输入文件

一个简短的示例文件:

$ cat dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.

使用/输出示例

$ ./bin/fgets_lines_dyn_simple dat/captnjack.txt
line[  0] : This is a tale
line[  1] : Of Captain Jack Sparrow
line[  2] : A Pirate So Brave
line[  3] : On the Seven Seas.

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后, 以确认您已释放所有已分配的内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/fgets_lines_dyn_simple dat/captnjack.txt
==8156== Memcheck, a memory error detector
==8156== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8156== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==8156== Command: ./bin/fgets_lines_dyn_simple dat/captnjack.txt
==8156==
line[  0] : This is a tale
line[  1] : Of Captain Jack Sparrow
line[  2] : A Pirate So Brave
line[  3] : On the Seven Seas.
==8156==
==8156== HEAP SUMMARY:
==8156==     in use at exit: 0 bytes in 0 blocks
==8156==   total heap usage: 9 allocs, 9 frees, 5,796 bytes allocated
==8156==
==8156== All heap blocks were freed -- no leaks are possible
==8156==
==8156== For counts of detected and suppressed errors, rerun with: -v
==8156== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

完整代码

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

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define NPTRS   2       /* initial no. of pointers to allocate (lines) */

int main (int argc, char **argv) {
    
    char buf[MAXC],                 /* fixed buffer to read each line */
        **lines = NULL;             /* pointer to pointer to hold collection of lines */
    size_t  nptrs = NPTRS,          /* number of pointers available */
            used = 0;               /* number of pointers used */
    /* 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;
    }
    
    /* allocate/validate block holding initial nptrs pointers */
    if ((lines = malloc (nptrs * sizeof *lines)) == NULL) {
        perror ("malloc-lines");
        exit (EXIT_FAILURE);
    }
    
    while (fgets (buf, MAXC, fp)) {                 /* read each line into buf */
        size_t len;
        buf[(len = strcspn (buf, "\n"))] = 0;       /* trim \n, save length */
        
        if (used == nptrs) {    /* check if realloc of lines needed */
            /* always realloc using temporary pointer (doubling no. of pointers) */
            void *tmp = realloc (lines, (2 * nptrs) * sizeof *lines);
            if (!tmp) {                             /* validate reallocation */
                perror ("realloc-lines");
                break;                              /* don't exit, lines still good */
            }
            lines = tmp;                            /* assign reallocated block to lines */
            nptrs *= 2;                             /* update no. of pointers allocatd */
            /* (optionally) zero all newly allocated memory here */
        }
        
        /* allocate/validate storage for line */
        if (!(lines[used] = malloc (len + 1))) {
            perror ("malloc-lines[used]");
            break;
        }
        memcpy (lines[used], buf, len + 1);         /* copy line from buf to lines[used] */
        
        used += 1;                                  /* increment used pointer count */
    }
    /* (optionally) realloc to 'used' pointers to size no. of pointers exactly here */
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);

    /* use lines as needed (simply outputting here) */
    for (size_t i = 0; i < used; i++) {
        printf ("line[%3zu] : %s\n", i, lines[i]);
        free (lines[i]);                            /* free line storage when done */
    }
    
    free (lines);       /* free pointers when done */
}

检查一下,如果您有任何问题,请告诉我。如果您还想读取长度未知的行(数百万个字符长),您只需循环执行相同的操作,为每一行分配和重新分配,直到找到 '\n' 字符(或 EOF)标记结束线。原则上与我们上面对指针所做的没有什么不同。

【讨论】:

  • 不客气。祝你编码顺利。
  • 非常感谢@Davud C. Rankin对代码的详细解释
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 2013-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
相关资源
最近更新 更多