如何从文件中获取替代行并将其作为字符串存储到结构中?

我有一个文件需要通过代码读取。 该文件如下所示。 文件的第一行包含一个整数,表示文件中的日记条目数。 我需要编写一个C程序来读取文件并将内容存储在动态分配的结构数组中。

4 12/04/2010 Interview went well I think, though was told to wear shoes. 18/04/2010 Doc advised me to concentrate on something... I forget. 03/05/2010 Was asked today if I was an art exhibit. 19/05/2010 Apparently mudcakes not made of mud, or angry wasps. 

我能够strtok()将日期,月份和年份存储在我的结构中,但是我坚持将字符串保存到我的结构中。 这是我的strtok()代码,

 FILE* file=fopen("struct.txt","r"); if (file==NULL){ perror("Error opening file\n.");} else { fscanf(file,"%d",&size); res=(Diary*)malloc(size*sizeof(Diary)); fscanf(file,"%*[^\n]"); while(fgets(day,1024,file)!= NULL){ oken=strtok(day,"/"); h[i]=atoi(oken); */h[i] is my day oken=strtok(NULL,"/"); fre[i]=atoi(oken); */fre[i] is the month oken=strtok(NULL,"/"); re[i]=atoi(oken); */ re[i] is the year okena=strtok(day,"\n"); strcpy(rese[i],okena); */i had declared rese as char rese[1024] printf("%s",okena); i++; } 

程序没有使用strcpy(),当我运行它时,它会继续崩溃。 但是,如果我删除strcpy(),它将打印如下:

 12 Interview went well I think, though was told to wear shoes. 18 Doc advised me to concentrate on something... I forget. 03 Was asked today if I was an art exhibit. 19 Apparently mudcakes not made of mud, or angry wasps. 

这不是我想要存储在我的结构中的字符串。 我陷入了如何将字符串存储到结构中的问题。 我的结构是

 typedef struct journal{ int day; int month; int year; char entry[1024]; } Diary; 

任何好心灵都能告诉我出了什么问题?

以下提议的代码:

  1. 执行所需的function
  2. 为“魔法”数字提供有意义的名称
  3. 将struct定义与该struct的typedef分开
  4. 更新/编辑了最新的问题详情

现在建议的代码:

 #include  #include  #define MAX_LINE_LEN 1024 struct journal { int day; int month; int year; char entry[ MAX_LINE_LEN ]; }; typedef struct journal Diary; int main( void ) { FILE* file=fopen("struct.txt","r"); if ( !file ) { perror("fopen failed");} exit( EXIT_FAILURE ); } // implied else, fopen successful char line[ MAX_LINE_LEN ]; int size; if( fgets( line, sizeof line, file ) ) { if ( sscanf( line, "%d", size ) != 1 ) { fprintf( stderr, "scanf for data count failed\m" ); exit( EXIT_FAILURE ); } // implied else, input of data count successful } else { perror( "fgets for data count failed" ); exit( EXIT_FAILURE ); } // implied else, fgets successful Diary myDiary[ size ]; // uses VLA (variable length array feature of C size_t i = 0; char *token = NULL; while( i < size && fgets( line, sizeof( line ), file) ) { token = strtok( line, "/" ); if( token ) { myDiary[i].day = atoi( token ); token = strtok( NULL, "/" ); if( token ) { myDiary[i].month = atoi( token ); token = strtok( NULL, "/" ); if( token ) { myDiary[i].year = atoi( token ); // input data directly into struct instance fgets( myDiary[i].entry, MAX_LINE_LEN, file ); } } } i++; } } 

你的问题提出了一个经典的问题:“如果我事先不知道有多少东西,我如何阅读和分配X数?” 这实际上是问题的一个更简单的变体,因为您可以将X数字作为数据文件的第一行。

(这会在读取第一行后将问题简化为单个X结构分配 – 否则您需要跟踪当前分配的结构数并根据需要重新分配)

首先,我建议不要创建char entry[1024]; 在你的结构中有两个原因 – 首先,在堆栈上创建entry的自动存储,并且一个大的日记可以很容易地StackOverflow ……其次,它只是浪费。 如果目标是动态分配,则仅分配每个entry所需的存储。 您可以声明1024字符的单个缓冲区用作读缓冲区,但是然后仅分配strlen (buf) + 1 char来保存条目(在从条目中修剪包含的'\n' )。

问题的其余部分是任何可靠代码的基础,只需validation每个读取,每个解析和每个分配,以确保您处理有效数据并在整个代码中拥有有效存储。 这适用于您编写的每个代码段,而不仅仅是此问题。

将这些部分组合在一起,并在下面的评论中提供更多内容,您可以执行以下操作:

 #include  #include  #include  typedef struct journal { int day, month, year; char *entry; /* declare a pointer, allocate only need No. of chars */ } diary_t; #define MAXLENGTH 1024 /* max read buf for diary entry */ int main (int argc, char **argv) { size_t entries = 0, i, n = 0; char buf[MAXLENGTH] = ""; diary_t *diary = NULL; FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin; if (!fp) { /* validate file open for reading */ fprintf (stderr, "error: file open failed '%s'.\n", argv[1]); return 1; } /* read first line, parse number of entries */ if (!(fgets (buf, MAXLENGTH, fp)) || /* validate read */ sscanf (buf, "%zu", &entries) != 1) { /* validate conversion */ fputs ("error: failed to read 1st line.\n", stderr); return 1; } /* allocate/validate entries number of diary_t */ if (!(diary = calloc (entries, sizeof *diary))) { perror ("calloc-diary_pointers"); return 1; } for (i = 0; i < entries; i++) { /* loop No. entries times */ size_t len = 0; if (!fgets (buf, MAXLENGTH, fp)) { /* read/validate date */ fprintf (stderr, "error: failed to read date %zu.\n", i); return 1; } if (sscanf (buf, "%d/%d/%d", /* parse into day, month, year */ &diary[i].day, &diary[i].month, &diary[i].year) != 3) { fprintf (stderr, "error failed to parse date %zu.\n", i); return 1; } if (!fgets (buf, MAXLENGTH, fp)) { /* read entry */ fprintf (stderr, "error: failed to read entry %zu.\n", i); return 1; } len = strlen (buf); /* get length */ if (len && buf[len - 1] == '\n') /* check last char is '\n' */ buf[--len] = 0; /* overwrite with nul-character */ else if (len == MAXLENGTH - 1) { /* check entry too long */ fprintf (stderr, "error: entry %zu exceeds MAXLENGTH.\n", i); return 1; } /* allocate/validate memory for entry */ if (!(diary[i].entry = malloc ((len + 1)))) { perror ("malloc-diary_entry"); fprintf (stderr, "error: memory exausted, entry[%zu].\n", i); break; /* out of memory error, don't exit, just break */ } strcpy (diary[i].entry, buf); /* copy buf to entry */ n++; /* increment successful entry read */ } if (fp != stdin) fclose (fp); /* close file if not stdin */ for (i = 0; i < n; i++) { /* output diary entries */ printf ("entry[%2zu]: %2d/%2d/%4d - %s\n", i, diary[i].day, diary[i].month, diary[i].year, diary[i].entry); free (diary[i].entry); /* don't forget to free entries */ } free (diary); /* don't forget to free diary */ return 0; } 

注意:您可以通过使用POSIX getline()来进一步简化代码,而不是使用固定的buf ,您可以使用strdup()简化每个条目的分配和复制到结构中,但不保证所有编译器都可以使用- 如果您的编译器支持它们并且无处不在,那么使用它们。另请注意,GNU gcc使用%zu作为size_t的格式说明符。如果您使用的是windoze,请将每个更改为%lu

示例输入文件

 $ cat dat/diary.txt 4 12/04/2010 Interview went well I think, though was told to wear shoes. 18/04/2010 Doc advised me to concentrate on something... I forget. 03/05/2010 Was asked today if I was an art exhibit. 19/05/2010 Apparently mudcakes not made of mud, or angry wasps. 

示例使用/输出

 $ ./bin/diary  

内存使用/错误检查

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

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

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

 $ valgrind ./bin/diary  

注意:所有日记条目(整个日记)所需的存储空间仅为309-bytes ,小于声明char entry[1024];所需存储空间的1/10th char entry[1024];

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


MS Windows

由于您似乎在Windows上遇到问题,以下是上面的代码,只有%lu替换%zu (因为windows将%zu视为文字),在Win7上使用旧版本的VS编译器编译:

 > cl /? Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. 

 > cl /nologo /Wall /wd4706 /wd4996 /Ox /Foobj/diary /Febin/diary /Tc diary.c 

注意:我将.obj文件放在./obj中的子目录和我的二进制可执行文件中以保持我的源目录清洁。这就是上面的/Foobj/diary/Febin/diary的目的)

示例使用/输出

 > bin\diary.exe dat\diary.txt entry[ 0]: 12/ 4/2010 - Interview went well I think, though was told to wear shoes. entry[ 1]: 18/ 4/2010 - Doc advised me to concentrate on something... I forget. entry[ 2]: 3/ 5/2010 - Was asked today if I was an art exhibit. entry[ 3]: 19/ 5/2010 - Apparently mudcakes not made of mud, or angry wasps. 

您必须确保将每个%zu更改为%lu否则您无法获得正确的输出。 您说您已将所有内容更改为int ,但您在下面的评论中发布的代码段包含%zu - 这在Windows上无法使用。

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