如何解决我想读取每行中包含电影信息的txt,并将其保存到动态结构数组中
我真的是C编程的新手,我尝试以此为例来读取文件并将其保存到结构的动态数组中,以获取txt信息:
Movie id:1448
title:The movie
surname of director: lorez
name of director: john
date: 3
month: september
year: 1997
结构应该像这样
typedef struct date
{
int day,month,year;
} date;
typedef struct director_info
{
char* director_surname,director_name;
} director_info;
typedef struct movie
{
int id;
char* title;
director_info* director;
date* release_date;
} movie;
我所知道的是,我应该使用fgets
阅读它,我认为这是某种方式,但是我无法弄清楚如何制作结构并保存它们
FILE *readingText;
readingText = fopen("movies.txt","r");
if (readingText == NULL)
{
printf("Can't open file\n");
return 1;
}
while (fgets(line,sizeof(line),readingText) != NULL)
{
.....
}
fclose(readingText);
解决方法
读取多行输入可能会有些挑战,将其与分配嵌套结构相结合,您将具有良好的文件I / O和动态内存分配学习经验。但是在查看您的任务之前,需要清除一些误解:
char* director_surname,director_name;
不声明两个指向char
的指针。它声明一个指针(director_surname
),然后声明一个字符(director_name
)。课程,指示指针间接级别的一元'*'
与变量NOT the type一起使用。为什么?正如您所经历的:
char* a,b,c;
不声明三个指向char
的指针,而是声明一个指针和两个char
变量。使用:
char *a,c;
说清楚。
多行阅读
当必须从多行中协调数据时,必须先验证您为组中的每一行获得了所需的信息,然后才能认为该组的输入有效。有很多方法,但是也许更简单的方法之一就是使用临时变量来保存每个输入,并保持一个计数器,该计数器在每次成功接收到输入时就递增。如果填写了所有临时变量,并且计数器反映了正确的输入数量,则可以为每个结构分配内存,并将临时变量复制到它们的永久存储中。然后,将计数器重置为零,然后重复进行直到文件中的行用完。
大多数读操作都是简单明了的,除了month
,它在给定月份被读为小写字符串,然后必须将其转换为int
才能存储在您的计算机中struct date
。可能最简单的处理方法是创建一个查找表(例如,在十二个月中的每个月中,指向字符串字面量的常量数组)。然后,在读取您的月份字符串之后,您可以使用strcmp()
在数组上循环,以将该月份的索引映射到您的结构。 (例如,添加+1
可以使january
月1
,february
月2
等。)例如,您可以使用以下内容: / p>
const char *months[] = { "january","february","march","april","may","june","july","august","september","october","november","december" };
#define NMONTHS (int)(sizeof months / sizeof *months)
其中NMONTHS
中的元素数量为12
的宏months
。
然后,要读取文件,您的基本方法是使用fgets()
读取每一行,然后使用sscanf()
解析该行中所需的信息 验证 进行中的所有输入,转换和分配。 验证 是任何成功代码的关键,对于进行转换的多行读取尤其重要。
例如,根据给定的结构,您可以声明所需的其他常量,并声明和初始化临时变量,然后打开作为第一个参数给出的文件并对其进行 验证 可通过以下方式阅读:
...
#define MAXC 1024 /* if you need a constant,#define one (or more) */
#define MAXN 128
#define AVAIL 2
...
int main (int argc,char **argv) {
char line[MAXC],tmptitle[MAXN],tmpsurnm[MAXN],tmpnm[MAXN],tmpmo[MAXN];
int good = 0,tmpid;
date tmpdt = { .day = 0 }; /* temporary date struct to fill */
movie *movies = NULL;
size_t avail = AVAIL,used = 0;
/* 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;
}
在good
变量上方将是您的计数器,对于构成输入块的七行数据中的每一行,每一次良好的读取和转换数据,您都将对其进行递增。 good == 7
确认后,您已经拥有与一部电影关联的所有数据,并且可以使用所有临时值分配并填充最终存储。
used
和avail
计数器跟踪您有多少可用的已分配struct movie
,以及其中有多少已使用。在used == avail
时,您该到realloc()
的电影中添加更多影片了。这就是动态分配方案的工作方式。您分配一些所需数量的预期对象。循环读取和填充对象,直到填充完分配的内容,然后重新分配更多内容并继续操作。
您可以根据需要每次添加尽可能多的额外内存,但是通常的方案是每次需要重新分配时将分配的内存加倍。这样可以在所需的分配数量和可用对象数量的增长之间取得良好的平衡。
(内存操作相对昂贵,您要避免为每个新的外部结构分配内存-尽管分配在扩展方面比每次创建新的副本都要好一些,但使用分配更大块的方案仍然会最终获得更有效的方法)
现在已声明了临时变量和计数器,您可以开始多行读取了。让我们以第一行id
为例:
while (fgets (line,MAXC,fp)) { /* read each line */
/* read ID line & validate conversion */
if (good == 0 && sscanf (line,"Movie id: %d",&tmpid) == 1)
good++; /* increment good line counter */
您阅读了该行,并检查good == 0
是否将其与id
的行进行协调。您尝试转换为int
并同时验证两者。如果您在临时ID中成功存储了整数,则可以增加good
计数器。
您对“标题”行的读取将类似,但这次是else if
而不是普通的if
。上方的id
行和下一行的title
读为:
while (fgets (line,&tmpid) == 1)
good++; /* increment good line counter */
/* read Title line & validate converion */
else if (good == 1 && sscanf (line,"title:%127[^\n]",tmptitle) == 1)
good++; /* increment good line counter */
(注意:每当您使用任何scanf()
系列函数将字符串读取到任何数组中时,必须都必须使用 field-width 修饰符(上面的127
)将读取限制为数组可以容纳的范围('\0'
为+1),以防止覆盖数组边界。 field-width 修饰符,则使用scanf()
函数并不比使用gets()
更安全。请参阅:Why gets() is so dangerous it should never be used!)
每行读取并成功转换并存储后,good
将递增以将下一行的值设置为适当的临时变量。
请注意,我说过您需要对month
进行读取和转换,因此需要做更多的工作,例如"september"
,但需要在结构中存储整数9
。从头开始使用查找表,您将读取并获取月份名称的字符串,然后循环查找查找表中的索引(您可能希望将+1
添加到索引中,以使{{ 1}},等等。您可以这样做:
january == 1
在最后一个 /* read Month line and loop comparing with array to map index */
else if (good == 5 && sscanf (line,"month: %s",tmpmo) == 1) {
tmpdt.month = -1; /* set month -1 as flag to test if tmpmo found */
for (int i = 0; i < NMONTHS; i++) {
if (strcmp (tmpmo,months[i]) == 0) {
tmpdt.month = i + 1; /* add 1 to make january == 1,etc... */
break;
}
}
if (tmpdt.month > -1) /* if not found,flag still -1 - failed */
good++;
else
good = 0;
}
为else if
之后,您要添加一个year
,以便该块中任何一行的任何故障都将重置else
,以便尝试执行以下操作:读取并匹配文件中下一个good = 0;
行,例如
id
动态分配
动态分配嵌套结构并不难,但是您必须牢记如何处理它。您的外部结构 /* read Year line & validate */
else if (good == 6 && sscanf (line,"year: %d",&tmpdt.year) == 1)
good++;
else
good = 0;
是您将使用struct movie
进行分配和重新分配的结构,以此类推...每次您拥有所有结构时,都必须为used == avail
和struct date
进行分配您的七个临时变量已填充并经过验证,可以放入最终存储中。您可以通过检查struct director_info
块是否已经分配(如果尚未分配)来开始分配块。如果有struct movie
,请重新分配它。
现在,每次used == avail
都使用临时指针,因此,当realloc()
返回realloc()
失败时(不是(如果不是)),您就不会丢失通过用返回的NULL
覆盖当前分配的存储的指针,从而创建内存泄漏。为您的NULL
分配或重新分配的初始处理如下所示:
struct movie
现在您有一个有效的 /* if good 7,all sequential lines and values for movie read */
if (good == 7) {
director_info *newinfo; /* declare new member pointers */
date *newdate;
size_t len;
/* if 1st allocation for movies,allocate AVAIL no. of movie struct */
if (movies == NULL) {
movies = malloc (avail * sizeof *movies);
if (!movies) { /* validate EVERY allocation */
perror ("malloc-movies");
exit (EXIT_FAILURE);
}
}
/* if movies needs reallocation */
if (used == avail) {
/* ALWAYS realloc with a temporary pointer */
void *tmp = realloc (movies,2 * avail * sizeof *movies);
if (!tmp) {
perror ("realloc-movies");
break;
}
movies = tmp;
avail *= 2;
}
块,您可以在其中直接存储一个struct movie
并分配给id
并将分配了标题的块分配给您的title
每个title
存储空间中的指针。我们首先分配两个struct movie
。当您启动struct movie
和used == 0
时(请参阅顶部的avail = 2
常量以了解AVAIL
的来源)。处理2
并为id
分配将按以下方式工作:
title
(注意:当您在一个内存块中声明多个结构并使用 movies[used].id = tmpid; /* set movie ID to tmpid */
/* get length of tmptitle,allocate,copy to movie title */
len = strlen (tmptitle);
if (!(movies[used].title = malloc (len + 1))) {
perror ("malloc-movies[used].title");
break;
}
memcpy (movies[used].title,tmptitle,len + 1);
索引每个结构时,[..]
充当指针的取消引用,因此您使用{{1} }运算符访问[..]
之后的成员,而不是'.'
运算符,因为您通常会取消引用结构指针来访问该成员([..]
已经完成了引用)>
此外,由于您知道'->'
,因此没有理由使用[..]
将len
复制到strcpy()
并让tmptitle
扫描看起来像最后是零终止字符。您已经知道字符数,因此只需使用movies[used].title
复制strcpy()
个字节。 (请注意,如果您有memcpy()
,可以在一次调用中进行分配和复制,但请注意,len + 1
不是C11中c库的一部分。
为每个strdup()
元素分配strdup()
的方法很简单。分配struct director_info
,然后使用struct movie
来获取名称的长度,然后像上面一样为每个struct director_info
分配存储空间。
strlen()
处理分配和填充新的memcpy()
更加容易。您只需分配并分配3个整数值,然后将分配的 /* allocate director_info struct & validate */
if (!(newinfo = malloc (sizeof *newinfo))) {
perror ("malloc-newinfo");
break;
}
len = strlen (tmpsurnm); /* get length of surname,copy */
if (!(newinfo->director_surname = malloc (len + 1))) {
perror ("malloc-newinfo->director_surname");
break;
}
memcpy (newinfo->director_surname,tmpsurnm,len + 1);
len = strlen (tmpnm); /* get length of name,copy */
if (!(newinfo->director_name = malloc (len + 1))) {
perror ("malloc-newinfo->director_name");
break;
}
memcpy (newinfo->director_name,tmpnm,len + 1);
movies[used].director = newinfo; /* assign allocated struct as member */
的地址分配给struct date
中的指针,例如
struct date
就是这样,当您在struct movie
中分配最后一个指针时,您会递增 /* allocate new date struct & validate */
if (!(newdate = malloc (sizeof *newdate))) {
perror ("malloc-newdate");
break;
}
newdate->day = tmpdt.day; /* populate date struct from tmpdt struct */
newdate->month = tmpdt.month;
newdate->year = tmpdt.year;
movies[used++].release_date = newdate; /* assign newdate as member */
good = 0;
}
,以便设置为使用文件中的下七行填充该块中的下一个元素。您重置used++
以准备读取循环,以从文件中读取下一个struct movie
行。
全部放入
如果完全将代码拼凑在一起,最终结果将类似于:
good = 0;
(注意:添加id
可以输出所有存储的影片,添加#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant,#define one (or more) */
#define MAXN 128
#define AVAIL 2
const char *months[] = { "january","december" };
#define NMONTHS (int)(sizeof months / sizeof *months)
typedef struct date {
int day,month,year;
} date;
typedef struct director_info {
char *director_surname,*director_name;
} director_info;
typedef struct movie {
int id;
char *title;
director_info *director;
date *release_date;
} movie;
void prnmovies (movie *movies,size_t n)
{
for (size_t i = 0; i < n; i++)
printf ("\nMovie ID : %4d\n"
"Title : %s\n"
"Director : %s %s\n"
"Released : %02d/%02d/%4d\n",movies[i].id,movies[i].title,movies[i].director->director_name,movies[i].director->director_surname,movies[i].release_date->day,movies[i].release_date->month,movies[i].release_date->year);
}
void freemovies (movie *movies,size_t n)
{
for (size_t i = 0; i < n; i++) {
free (movies[i].title);
free (movies[i].director->director_surname);
free (movies[i].director->director_name);
free (movies[i].director);
free (movies[i].release_date);
}
free (movies);
}
int main (int argc,"r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (line,tmptitle) == 1)
good++; /* increment good line counter */
/* read director Surname line & validate */
else if (good == 2 && sscanf (line,"surname of director: %127[^\n]",tmpsurnm) == 1)
good++;
/* read directory Name line & validate */
else if (good == 3 && sscanf (line,"name of director: %127[^\n]",tmpnm) == 1)
good++;
/* read Day line & validate */
else if (good == 4 && sscanf (line,"date: %d",&tmpdt.day) == 1)
good++;
/* read Month line and loop comparing with array to map index */
else if (good == 5 && sscanf (line,flag still -1 - failed */
good++;
else
good = 0;
}
/* read Year line & validate */
else if (good == 6 && sscanf (line,&tmpdt.year) == 1)
good++;
else
good = 0;
/* if good 7,2 * avail * sizeof *movies);
if (!tmp) {
perror ("realloc-movies");
break;
}
movies = tmp;
avail *= 2;
}
movies[used].id = tmpid; /* set movie ID to tmpid */
/* get length of tmptitle,len + 1);
/* allocate director_info struct & validate */
if (!(newinfo = malloc (sizeof *newinfo))) {
perror ("malloc-newinfo");
break;
}
len = strlen (tmpsurnm); /* get length of surname,len + 1);
movies[used].director = newinfo; /* assign allocated struct as member */
/* allocate new date struct & validate */
if (!(newdate = malloc (sizeof *newdate))) {
perror ("malloc-newdate");
break;
}
newdate->day = tmpdt.day; /* populate date struct from tmpdt struct */
newdate->month = tmpdt.month;
newdate->year = tmpdt.year;
movies[used++].release_date = newdate; /* assign newdate as member */
good = 0;
}
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
prnmovies (movies,used); /* print stored movies */
freemovies (movies,used); /* free all allocated memory */
}
可以释放所有分配的内存)
示例输入文件
我们不只是添加一部电影的七行代码,而是添加另一行以确保代码在文件中循环,例如
prnmovies()
使用/输出示例
使用文件名freemovies()
中的两个电影数据处理输入文件,您将拥有:
$ cat dat/moviegroups.txt
Movie id:1448
title:The movie
surname of director: lorez
name of director: john
date: 3
month: september
year: 1997
Movie id:1451
title:Election - Is the Cheeto Tossed?
surname of director: loreza
name of director: jill
date: 3
month: november
year: 2020
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)不再需要它时可以释放。
当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。
对于Linux,dat/moviegroups.txt
是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。
$ ./bin/movieinfo dat/moviegroups.txt
Movie ID : 1448
Title : The movie
Director : john lorez
Released : 03/09/1997
Movie ID : 1451
Title : Election - Is the Cheeto Tossed?
Director : jill loreza
Released : 03/11/2020
始终确认已释放已分配的所有内存,并且没有内存错误。
此答案中有很多信息(而且结果总是比我预期的要长),但是要对发生的事情做出公正的解释需要一些时间。慢慢来,了解代码的每一位在做什么,并了解如何处理分配(将需要一些时间来消化)。如果您遇到困难,请发表评论,我们很乐意进一步解释。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。