有没有办法阻止fread在空格中的结构中读取字符到数组?

问题描述 投票:0回答:1

我正在尝试为我的第二学期编程课做作业,我们必须从这样的文件中读取数据:

Fred 23 2.99
Lisa 31 6.99
Sue 27 4.45
Bobby 456  18.844
Ann   7  3.45

在恐惧中使用结构。我最终必须创建一个循环来读取所有数据,然后将其转换为二进制并将其写入文件,但这是我在遇到问题之前得到的:

struct data
{
    char name[25];
    int iNum;
    float fNum;
};

int main(int argc, char *argv[])
{
    struct data mov;
    FILE *fp;

    fp = fopen(argv[1], "r");

    fread(&mov, sizeof(struct data), 1, fp);
    printf(" name: %s\n int: %d\n float: %f\n", mov.name, mov.iNum, mov.fNum);

    return 0;
}

我遇到的问题是,fread会将前25个字符读入数组而不是停留在第一个空格,因此它产生如下输出:

 name: Fred 23 2.99
Lisa 31 6.99
Sue 27 4.4
 int: 926031973
 float: 0.000000

而不是期望的结果,这将更像是:

 name: Fred
 int: 23
 float: 2.99000

从我读过的内容来看,我相信这就是fread的功能,我确信有更好的解决这个问题的方法,但是赋值需要我们在结构中使用fread和25个字符的数组。最好的方法是什么?

c fread
1个回答
-1
投票

有没有办法阻止fread在空格中的结构中读取字符到数组?

答案:是的(但不能直接使用fread,你需要一个缓冲区才能完成任务)

你使用fread从输入文件解析格式化文本的要求当然是一个学术练习(一个很好的练习),但不是你通常会做的事情。为什么?通常,当您关注从文件中读取数据行时,可以使用面向行的输入函数,例如fgets()或POSIX getline()

你也可以使用面向字符的输入函数fgetc(),只需从文件中读取,缓冲输入,直到找到'\n',用缓冲区做你需要的并重复。你最后一个正常的选择(但由于它的脆弱而不鼓励)是使用像fscanf()这样的格式化输入函数 - 但是它的误用占了这个网站上相当大比例的问题。

但是,如果对于学术挑战,你必须使用fread(),然后如评论中所提到的,你将要将整个文件读入已分配的缓冲区,然后解析该缓冲区,就好像你正在读取它一行一样。来自实际文件的时间。如果用sscanf读取,将使用fgets(),它可以在这里用来读取填充fread()的缓冲区。唯一的技巧是跟踪缓冲区中的位置以开始每次读取 - 并知道停止的位置。

有了这个大纲,你如何使用fread()将整个文件读入缓冲区?首先需要获取文件长度以了解要分配多少空间。您可以通过调用statfstat并使用包含filesize的填充st_sizestruct stat成员来执行此操作,或者使用fseek移动到文件的末尾并使用ftell()从头开始报告偏移量。

采用打开的FILE*指针的简单函数,保存当前位置,将文件位置指示器移动到末尾,使用ftell()获取文件大小,然后将文件位置指示器恢复到其原始位置可以是:

/* get the file size of file pointed to by fp */
long getfilesize (FILE *fp)
{
    fpos_t currentpos;
    long bytes;

    if (fgetpos (fp, &currentpos) == -1) {  /* save current file position */
        perror ("fgetpos");
        return -1;
    }
    if (fseek (fp, 0, SEEK_END) == -1) {    /* fseek end of file */
        perror ("fseek-SEEK_END");
        return -1;
    }
    if ((bytes = ftell (fp)) == -1) {       /* get number of bytes */
        perror ("ftell-bytes");
        return -1;
    }
    if (fsetpos (fp, &currentpos) == -1) {  /* restore file positon */
        perror ("fseek-SEEK_SET");
        return -1;
    }

    return bytes;   /* return number of bytes in file */
}

(注意:上面的每一步都经过验证,错误时会返回-1,否则会在成功时返回文件大小。请确保验证程序中的每一步,并始终从函数中提供有意义的返回,表明成功/失败。 )

有了文件大小,在调用fread()之前你需要做的就是分配一个足够大的内存块来保存文件的内容,并将该内存块的起始地址分配给一个可以与之一起使用的指针。 fread()。例如:

    long bytes;                     /* size of file in bytes */
    char *filebuf, *p;              /* buffer for file and pointer to it */
    ...
    if ((bytes = getfilesize (fp)) == -1)   /* get file size in bytes */
        return 1;

    if (!(filebuf = malloc (bytes + 1))) {  /* allocate/validate storage */
        perror ("malloc-filebuf");
        return 1;
    }

(我们稍后将讨论+ 1

现在,您的文件有足够的存储空间,并且存储器的地址被分配给指针filebuf,您可以调用fread()并将整个文件读入该内存块:

    /* read entire file into allocated memory */
    if (fread (filebuf, sizeof *filebuf, bytes, fp) != (size_t)bytes) {
        perror ("fread-filebuf");
        return 1;
    }

现在您的整个文件存储在filebuf指向的内存块中。如何将数据逐行解析到结构中(或实际上是一个struct数组,以便每个记录存储在一个单独的结构中)?这实际上非常简单。您只需从缓冲区中读取并跟踪用于读取的字符数,直到找到'\n',将该行中的信息解析为数组的struct元素,将偏移量添加到指针以准备下一次读取并增加struct数组的索引以考虑刚填充的结构。您实际上正在使用sscanf就像使用fgets()从文件中读取行一样,但是您手动跟踪缓冲区内的偏移量,以便下次调用sscanf,例如

#define NDATA 16    /* if you need a constant, #define one (or more) */
#define MAXC  25

struct data {       /* your struct with fixed array of 25-bytes for name */
    char name[MAXC];
    int iNum;
    float fNum;
};
...
    struct data arr[NDATA] = {{ .name = "" }};  /* array of struct data */
    int used;                       /* no. chars used by sscanf */
    size_t ndx = 0, offset = 0;     /* array index, and pointer offset */
    ...
    filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */

    p = filebuf;                                /* set pointer to filebuf   */
    while (ndx < NDATA &&                       /* while space in array     */
            sscanf (p + offset, "%24s %d %f%n", /* parse values into struct */
                arr[ndx].name, &arr[ndx].iNum, &arr[ndx].fNum, &used) == 3) {
        offset += used;     /* update offset with used chars */
        ndx++;              /* increment array index */
    }

这就是它。你可以使用free (filebuf);完成它并且所有的值现在都存储在struct arr的数组中。

上面有一个重要的代码行,我们还没有谈到 - 我告诉过你我们稍后会讨论它。它也是你通常不会做的事情,但它强制要求你使用sscanf处理缓冲区作为文本,sscanf是一个通常用于处理字符串的函数。你如何确保filebuf知道停止阅读并且不继续阅读超出 filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */ 的范围?

+ 1

这就是分配大小的sscanf发挥作用的地方。您通常不会终止缓冲区 - 没有必要。但是,如果要使用通常用于处理字符串的函数来处理缓冲区的内容,那么就可以了。否则,'\n'将继续读取缓冲区中的最终0进入内存,您无法有效访问,直到它在堆中的某处找到随机的#include <stdio.h> #include <stdlib.h> #define NDATA 16 /* if you need a constant, #define one (or more) */ #define MAXC 25 struct data { /* your struct with fixed array of 25-bytes for name */ char name[MAXC]; int iNum; float fNum; }; long getfilesize (FILE *fp); /* function prototype for funciton below */ int main (int argc, char **argv) { struct data arr[NDATA] = {{ .name = "" }}; /* array of struct data */ int used; /* no. chars used by sscanf */ long bytes; /* size of file in bytes */ char *filebuf, *p; /* buffer for file and pointer to it */ size_t ndx = 0, offset = 0; /* array index, and pointer offset */ FILE *fp; /* file pointer */ if (argc < 2) { /* validate at least 1-arg given for filename */ fprintf (stderr, "error: insufficient arguments\n" "usage: %s <filename>\n", argv[0]); return 1; } /* open file / validate file open for reading */ if (!(fp = fopen (argv[1], "rb"))) { perror ("file open failed"); return 1; } if ((bytes = getfilesize (fp)) == -1) /* get file size in bytes */ return 1; if (!(filebuf = malloc (bytes + 1))) { /* allocate/validate storage */ perror ("malloc-filebuf"); return 1; } /* read entire file into allocated memory */ if (fread (filebuf, sizeof *filebuf, bytes, fp) != (size_t)bytes) { perror ("fread-filebuf"); return 1; } fclose (fp); /* close file, read done */ filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */ p = filebuf; /* set pointer to filebuf */ while (ndx < NDATA && /* while space in array */ sscanf (p + offset, "%24s %d %f%n", /* parse values into struct */ arr[ndx].name, &arr[ndx].iNum, &arr[ndx].fNum, &used) == 3) { offset += used; /* update offset with used chars */ ndx++; /* increment array index */ } free (filebuf); /* free allocated memory, values stored in array */ for (size_t i = 0; i < ndx; i++) /* output stored values */ printf ("%-24s %4d %7.3f\n", arr[i].name, arr[i].iNum, arr[i].fNum); return 0; } /* get the file size of file pointed to by fp */ long getfilesize (FILE *fp) { fpos_t currentpos; long bytes; if (fgetpos (fp, &currentpos) == -1) { /* save current file position */ perror ("fgetpos"); return -1; } if (fseek (fp, 0, SEEK_END) == -1) { /* fseek end of file */ perror ("fseek-SEEK_END"); return -1; } if ((bytes = ftell (fp)) == -1) { /* get number of bytes */ perror ("ftell-bytes"); return -1; } if (fsetpos (fp, &currentpos) == -1) { /* restore file positon */ perror ("fseek-SEEK_SET"); return -1; } return bytes; /* return number of bytes in file */ } 。 (如果碰巧满足格式字符串,可能会用垃圾填充额外的附加结构)

完全放在一起,你可以这样做:

fread()

(注意:大约1/2行代码专门用于验证每一步。这是正常的,也是至关重要的,以确保在发生故障后盲目地继续在代码中继续前进,从而阻止您处理有效数据,从而不会调用未定义行为。)

示例使用/输出

有了这个,程序就完成了,你应该能够解析$ ./bin/freadinumfnum dat/inumfnum.txt Fred 23 2.990 Lisa 31 6.990 Sue 27 4.450 Bobby 456 18.844 Ann 7 3.450 填充的缓冲区中的数据,该缓冲区在空格后的所有适当时间都已停止。

valgrind

内存使用/错误检查

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

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

对于Linux,$ valgrind ./bin/freadinumfnum dat/inumfnum.txt ==5642== Memcheck, a memory error detector ==5642== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==5642== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info ==5642== Command: ./bin/freadinumfnum dat/inumfnum.txt ==5642== Fred 23 2.990 Lisa 31 6.990 Sue 27 4.450 Bobby 456 18.844 Ann 7 3.450 ==5642== ==5642== HEAP SUMMARY: ==5642== in use at exit: 0 bytes in 0 blocks ==5642== total heap usage: 2 allocs, 2 frees, 623 bytes allocated ==5642== ==5642== All heap blocks were freed -- no leaks are possible ==5642== ==5642== For counts of detected and suppressed errors, rerun with: -v ==5642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。

qazxswpoi

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

仔细看看,如果您有其他问题,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.