【问题标题】:Open multiple files and store them in array of structures打开多个文件并将它们存储在结构数组中
【发布时间】:2017-11-29 23:10:46
【问题描述】:

我需要一些帮助来完成我的任务。我必须打开未知数量的文件并将它们存储在程序开始的数据结构中。
第一个文件包含其他两个文件的名称,依此类推(这在第一个示例下有更多解释文件)。 每个文件都有相同的结构:

[文件名]
[文件名 X]
[文件名 Y]
[文本]

,对于示例,第一个文件将如下所示:

File 1
file_8.txt
file_25.txt
Text: "this is some example text, lenght is unknown so
so i will have to use malloc and realloc to
dynamicaly store it."


启动程序时,用户在标准输入中键入第一个文件的名称
(例如:./task1 page_1.txt)

第一行存储文件的标题。
第二行和第三行各包含一个我必须读取/存储的下一个文件的文件名。如果第二行和第三行没有其他文件名,则这两行都将带有“- \n ".
文本从第四行开始(可以像上面的例子那样有多行)

我的 struct 现在:

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

typedef struct 
{
    char title[1000];    // should use malloc and realloc and not this way
    char file_x[1000];  // dynamically
    char file_y[1000];  // dynamically
    char text[10000]; 

} Story;

我的 ma​​in 看起来像这样:

int main (int argc,char *argv[])
{
    char c[100];
    char buffer[100];
    FILE *input = fopen(argv[1], "r");
    Story *temp = (Story*) malloc(sizeof(Story) * 8);
if(input)
{
    int flag = 0;
    while (fgets(c, sizeof(buffer),input) != NULL)      
    {
        if(flag == 0)
        {
            sscanf(c, "%s", temp->title);
        }       
        else if(flag == 1)
        {
            sscanf(c, "%s", temp->file_x);
        }
        else if(flag == 2)
        {
            sscanf(c, "%s", temp->file_y);
        }
        else
        {
            while(!feof(input))
            {
                fread(temp->text, sizeof(Story),1,input);
            }
        }
        flag++;
    }
    printf("%s\n%s\n%s\n", temp->title,
    temp->file_x, temp->file_y);
}
else if (input == NULL)
{
    printf("ERROR MESSAGE HERE \n");
    return 1;
}
free(temp);
fclose(input);
return 0;

}

现在我设法打开第一个文件并将其存储到结构中。我需要一个想法如何打开和存储所有其他文件,并且还必须使用动态内存分配来实现它。
非常感谢任何建议。

【问题讨论】:

  • 如果您要最小化每个结构 story 的大小,那么您可以为文件名分配,否则标准宏 PATH_MAX 将为每个结构提供足够的存储空间(通常为 4096 字节)。如果你没有很多,不要担心为文件名动态分配存储空间,只为每个文件的text。无需动态分配temp,只需story temp = { .title = "" ); 将在堆栈上声明temp 并将所有字节初始化为零。如果你声明char *text;,那么你可以为text动态分配。
  • 您如何知道何时到达最后一个文件?最后一个文件第 3 行会是空白吗?
  • 第二行和第三行分别包含我必须读取/存储的下一个文件的文件名。如果第二行和第三行没有其他文件名,则这两行都将包含“-\n”。例如:TITLE:title 25 SECOND LINE: - THIRD LINE: - FOURTH LINE: ` text:" 这是结尾,最后一个文件..."`
  • 有道理。几分钟后我会举一个例子。
  • 感谢您的宝贵时间,非常感谢

标签: c file-io


【解决方案1】:

我怀疑您的课程涉及递归,因为story 数组中的每个元素都需要分支未知次数来读取file_x 和file_y(每个都可以包含额外的file_x和file_y 内)。您的程序选项是跟踪所有file_x,然后返回到每个file_y,重复该过程,直到到达file_x 和file_y 为空的每个链中的最终文件。

在确定您将采用哪种方法之前,您只需要一种方法来读取一个文件,提取title、file_x、file_y 并分配和存储text。这是一个相当简单的过程,您的主要任务是验证每个步骤,以便您有信心处理实际数据并且不通过读取实际上未打开的文件或尝试写入(或读取)超出存储范围的文件来调用未定义的行为。

这是一个简短的示例,它使用指向 story 的指针来填充并从 filename 中读取。您会注意到涉及的重复过程。 (使用fgets 读取字符串,获取长度,验证最后读取的字符是'\n',表示您读取了整行,最后通过使用nul-terminating覆盖来修剪'\n' > 字符,这样您就不会在存储的字符串的末尾悬挂换行符,或者在将行连接在一起的text 的情况下用' '(空格)覆盖。

注意:在下面,realloc 永远不会直接在指向 text 的指针上调用。相反,tmp 指针与realloc 一起使用,以在将新块分配给text 之前验证realloc 是否成功。 (否则,如果realloc 失败,您将丢失指向text 的指针——因为它返回NULL)

/* read values into struct story 's' from 'filename' */
int read_file (story *s, char *filename)
{
    size_t len = 0,                 /* var for strlen */
        text_size = 0,              /* total text_size */
        nul_char = 0;               /* flag for +1 on first allocation */
    char buf[TITLE_MAX] = "";       /* read buffer for 'text' */
    FILE *fp = fopen (filename, "r");   /* file pointer */
    
    if (!fp)        /* validate file open for reading */
        return 0;   /* or return silently indicating no file_x or file_y */
    
    if (fgets (s->title, TITLE_MAX, fp) == 0) { /* read title */
        fprintf (stderr, "error: failed to read title from '%s'.\n",
                filename);
        fclose(fp);
        return 0;
    }
    len = strlen (s->title);                /* get title length */
    if (len && s->title[len - 1] == '\n')   /* check last char is '\n' */
        s->title[--len] = 0;                /* overwrite with nul-character */
    else {  /* handle error if line too long */
        fprintf (stderr, "error: title too long, filename '%s'.\n",
                filename);
        fclose(fp);
        return 0;
    }
    
    if (fgets (s->file_x, PATH_MAX, fp) == 0) { /* same for file_x */
        fprintf (stderr, "error: failed to read file_x from '%s'.\n",
                filename);
        fclose(fp);
        return 0;
    }
    len = strlen (s->file_x);
    if (len && s->file_x[len - 1] == '\n')
        s->file_x[--len] = 0;
    else {
        fprintf (stderr, "error: file_x too long, filename '%s'.\n",
                filename);
        fclose(fp);
        return 0;
    }
    
    if (fgets (s->file_y, PATH_MAX, fp) == 0) { /* same for file_y */
        fprintf (stderr, "error: failed to read file_y from '%s'.\n",
                filename);
        fclose(fp);
        return 0;
    }
    len = strlen (s->file_y);
    if (len && s->file_y[len - 1] == '\n')
        s->file_y[--len] = 0;
    else {
        fprintf (stderr, "error: file_y too long, filename '%s'.\n",
                filename);
        fclose(fp);
        return 1;
    }
    
    while (fgets (buf, TITLE_MAX, fp)) {    /* read text in TITLE_MAX chunks */
        len = strlen (buf);
        if (len && buf[len - 1] == '\n')    /* check for '\n' */
            buf[len - 1] = ' ';             /* overwrite with ' ' for concat */
        if (text_size == 0)
            nul_char = 1;       /* account for space for '\0' when empty, and  */
        else                    /* use a flag to set new block to empty-string */
            nul_char = 0;
        void *tmp = realloc (s->text, text_size + len + nul_char); /* allocate */
        if (!tmp) {         /* validate realloc succeeded */
            fprintf (stderr, "error: realloc failed, filename '%s'.\n",
                    filename);
            break;
        }
        s->text = tmp;          /* assign new block to s->text */
        if (nul_char)           /* if first concatenation */
            *(s)->text = 0;     /* initialize s->text to empty-string */
        strcat (s->text, buf);  /* concatenate buf with s->text */
        text_size += (len + 1); /* update text_size total */
    }
    
    fclose (fp);                /* close file */
    
    return 1;
}

有了这个,您将需要设计一种方法来处理所有file_x 和file_y 文件名。如上所述,这可能适用于递归函数,或者您可以沿着file_x 树向下工作并返回并拾取所有file_y 添加的内容。请注意,每次关注file_x 或file_y 时,您都需要考虑新添加的story。

下面是一个简短的示例,它遵循所有file_x 添加并返回并遵循仅第一个 file_y 分支。它旨在向您展示如何处理来自file_x 和file_y 的调用和填充,而不是为您编写最终代码。如果您在read_file 函数上方添加以下内容,您将获得一个工作示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> /* for PATH_MAX */

enum { STORY_MAX = 12, TITLE_MAX = 1024 };

typedef struct 
{
    char title[TITLE_MAX],
        file_x[PATH_MAX],
        file_y[PATH_MAX],
        *text; 

} story;

int read_file (story *s, char *filename);

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

    int n = 0, storycnt = 0;
    story stories[STORY_MAX] = {{ .title = "" }};
    char *filename = argv[1];

    /* read all file_x filenames */
    while (n < STORY_MAX && read_file (&stories[n], filename)) {
        filename = stories[n++].file_x;
    }
    storycnt = n;   /* current story count of all file_x */
    
    for (int i = 0; i < storycnt; i++)  /* find all file_y files */
        while (n < STORY_MAX && read_file (&stories[n], stories[i].file_y)) {
            filename = stories[i++].file_y;
            n++;
        }
    
    for (int i = 0; i < n; i++) {   /* output stories content */
        printf ("\ntitle : %s\nfile_x: %s\nfile_y: %s\ntext  : %s\n", 
                stories[i].title, stories[i].file_x, 
                stories[i].file_y, stories[i].text);
        free (stories[i].text);     /* don't forget to free memory */
    }
    
    return 0;
}

输入文件示例

$ cat file_1.txt
File 1
file_8.txt
file_25.txt
Text: "this is some example text, lenght is unknown
so i will have to use  malloc and realloc to
dynamicaly store it."

$ cat file_8.txt
file_8


This is the text from file 8. Not much,
just some text.

$ cat file_25.txt
file_25


This is the text from file 25. Not much,
just some text.

使用/输出示例

$ ./bin/rdstories file_1.txt

title : File 1
file_x: file_8.txt
file_y: file_25.txt
text  : Text: "this is some example text, lenght is unknown so i will 
        have to use  malloc and realloc to dynamicaly store it."

title : file_8
file_x:
file_y:
text  : This is the text from file 8. Not much, just some text.

title : file_25
file_x:
file_y:
text  : This is the text from file 25. Not much, just some text.

内存使用/错误检查

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

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

$ valgrind ./bin/rdstories file_1.txt
==9488== Memcheck, a memory error detector
==9488== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==9488== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==9488== Command: ./bin/rdstories file_1.txt
==9488==

title : File 1
file_x: file_8.txt
file_y: file_25.txt
text  : Text: "this is some example text, lenght is unknown so i will 
        have to use  malloc and realloc to dynamicaly store it."

title : file_8
file_x:
file_y:
text  : This is the text from file 8. Not much, just some text.

title : file_25
file_x:
file_y:
text  : This is the text from file 25. Not much, just some text.
==9488==
==9488== HEAP SUMMARY:
==9488==     in use at exit: 0 bytes in 0 blocks
==9488==   total heap usage: 13 allocs, 13 frees, 3,353 bytes allocated
==9488==
==9488== All heap blocks were freed -- no leaks are possible
==9488==
==9488== For counts of detected and suppressed errors, rerun with: -v
==9488== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

检查一下,如果您有任何问题,请告诉我。

【讨论】:

  • 二叉树将是一个很好的解决方案。虽然我仍然相信一个递归函数下降 file_x 分支直到 file_x 为空,然后在退出时递归 file_y 分支也可以工作。即使使用 btree,挑战仍然是“如何遍历每个文件的 file_x 和 file_y 分支?”您可以像上面一样处理 file_x 分支,但是您需要移动到第一个fils_y 并再次遵循file_x 分支,然后下一个file_y 并再次遵循file_x 分支,直到耗尽所有文件(参见模式)?
  • 给我一点时间来完成一些实际工作,我将重新审视代码,看看是否不能再添加一些指针。
  • 我省略了我的任务的重要部分(我认为这对这部分不重要)。用户运行程序后(`/.ass Chapter_1.txt) i have to print the first file in stdout (so with printf) and then user can type in choice A`或B(分别是file_x和file_y)然后我输出相应的文件等等。所以我现在明白了,我有制作二叉树,其中每个结构(例如第 1 章)都包含对选项 A 和 B(file_y 和 file_x)的 reference。除了您的示例之外,我还实现了二叉树。
  • 我在二叉树中存储结构的代码还没有完成,但我已经做了基础。你有时间检查我的代码吗?这对我来说非常重要,我很感激任何帮助。
猜你喜欢
  • 2021-08-10
  • 2021-12-08
  • 2022-01-14
  • 2013-07-22
  • 1970-01-01
  • 2020-10-15
  • 1970-01-01
  • 1970-01-01
  • 2021-06-09
相关资源
最近更新 更多