gpt4 book ai didi

c - 有没有一种方法可以停止在空白之后的结构中将字符读入数组中?

转载 作者:行者123 更新时间:2023-11-30 18:10:46 24 4
gpt4 key购买 nike

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

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个字符的数组。最好的方法是什么?

最佳答案

有没有一种方法可以停止将字符读入数组中的字符
  空格后的结构?


答:是的(但不能直接与fread一起使用,您需要一个缓冲区来完成任务)

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

您还可以使用面向字符的输入函数fgetc()并从文件中读取,缓冲输入,直到找到'\n',然后对缓冲区进行所需的操作并重复。您的最后一个正常选择(但由于它的脆弱性而感到沮丧)是使用诸如fscanf()之类的格式化输入功能-但是它的滥用占了该网站上很大比例的问题。

但是,如果遇到学术挑战,则必须使用fread(),然后如注释中所述,您将希望将整个文件读取到分配的缓冲区中,然后解析该缓冲区,就像您正在读取该行一样。实际文件中的时间。如果使用sscanf进行读取,则将使用fgets(),并且在此处可以使用它从充满fread()的缓冲区中读取。唯一的技巧是跟踪缓冲区中的位置以开始每次读取-并知道从何处停止。

有了这个轮廓,您如何使用fread()将整个文件读入缓冲区?您首先需要获取文件长度,才能知道要分配多少空间。您可以通过调用statfstat并利用包含文件大小的已填充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指向的内存块中。如何将数据逐行解析到您的结构中(或实际上是一个结构数组,以便每个记录存储在单独的结构中)?实际上很简单。您只需从缓冲区读取并跟踪用于读取的字符数,直到找到 '\n',然后将该行中的信息解析为数组的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);了,所有值现在都存储在结构 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 */
}


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

使用/输出示例

这样,程序就完成了,您应该能够解析由 fread()填充的缓冲区中的数据,该缓冲区在空格之后的所有适当时间都已停止。

$ ./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


内存使用/错误检查

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

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

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

$ 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)


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

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

关于c - 有没有一种方法可以停止在空白之后的结构中将字符读入数组中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55698740/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com