#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
int CurrentCnt = 0;
#define MAX_ID_LEN 30
#define MAX_NAME_LEN 30
#define MAX_PRICE_LEN 30
#define MAX_DISCOUNT_LEN 30
typedef struct {
char goods_id[MAX_ID_LEN];
char goods_name[MAX_NAME_LEN];
int goods_price;
char goods_discount[MAX_DISCOUNT_LEN];
int goods_amount;
int goods_remain;
} GoodsInfo;
//--------------------------------------------------------------------
//define node
//--------------------------------------------------------------------
typedef struct node
{
GoodsInfo data;
struct node *next;
} GoodsList;
bool check_nullfile(void)
{
FILE *fp = fopen("goodsinfo.txt", "r");
//file not exist
if (!fp) {
printf("no files found.\n");
FILE *fp = fopen("goodsinfo.txt", "w");
fclose(fp);
return false;
}
//file already exist
else {
int temp;
//res for try to read file if file null feof() can't determine
whether file is null or not
int res = fscanf(fp, "%d", &temp);
fclose(fp);
if (res <= 0)
return false;
else
return true;
}
}
void info_init(GoodsList **L) {
if(check_nullfile())
{
FILE * fp;
fp=fopen("goodsinfo.txt", "r");
GoodsList *listptr;
while (1)
{
if (feof(fp)) break;
listptr=(GoodsList*)malloc(sizeof(GoodsList));
listptr->next=(*L)->next;
(*L)->next=listptr;
fscanf(fp,"%4s\t",listptr->data.goods_id);
fscanf(fp,"%4s\t",listptr->data.goods_name);
fscanf(fp,"%d\t",&(listptr->data.goods_price));
fscanf(fp,"%s\t",listptr->data.goods_discount);
fscanf(fp,"%d\t",&(listptr->data.goods_amount));
fscanf(fp,"%d",&(listptr->data.goods_remain));
/* printf("%c%c%c%c\n",listptr->data.goods_id[0],listptr-
>data.goods_id[1],listptr->data.goods_id[2],listptr->data.goods_id[3]);
printf("%c%c%c%c\n",listptr->data.goods_name[0],listptr-
>data.goods_name[1],listptr->data.goods_name[2],listptr-
>data.goods_name[3]);
printf("%d\n",listptr->data.goods_price);
printf("%c%c%c%c\n",listptr->data.goods_discount[0],listptr-
>data.goods_discount[1],listptr->data.goods_discount[2],listptr-
>data.goods_discount[3]);
printf("%d\n",listptr->data.goods_amount);
printf("%d\n",listptr->data.goods_remain); these are my
testing*/
CurrentCnt++;
if (feof(fp)) break;
}
fclose(fp);
}
printf("%d\n", CurrentCnt);
}
int main (void)
{
GoodsList **L;
L=(GoodsList**)malloc(sizeof(GoodsList*));
info_init(L);
return 0;
}
我有一个测试文件,其中包括五组文件。当我运行该程序时,第五组数据无法正确输出。我的测试数据是
1000 new1 90 0.9 90 80
1001 new2 80 0.9 80 80
1002 new3 70 0.8 10 10
1003 new4 88 0.8 70 80
1004 new5 100 0.8 70 80
为什么position4起作用,但是其他的不起作用?Position1 2 3将使CurrentCnt变成6而不是5。在最后一个循环中,程序什么也没得到,但是为什么它没有跳出循环呢? 我新的可怜程序:
void info_init(GoodsList **L) {
if(check_nullfile())
{
FILE * fp;
fp=fopen("goodsinfo.txt", "r");
GoodsList *listptr;
while (1/*feof(fp) position1*/)
{
//if (feof(fp)) break; //position2
listptr=malloc(sizeof(*listptr));
listptr->next=*L;
*L=listptr;
//if (feof(fp)) break;//position3
fscanf(fp,"%s\t",listptr->data.goods_id);
fscanf(fp,"%s\t",listptr->data.goods_name);
fscanf(fp,"%d\t",&(listptr->data.goods_price));
fscanf(fp,"%s\t",listptr->data.goods_discount);
fscanf(fp,"%d\t",&(listptr->data.goods_amount));
fscanf(fp,"%d",&(listptr->data.goods_remain));
//if (feof(fp)) break;//position4
CurrentCnt++;
}
fclose(fp);
}
printf("%d\n", CurrentCnt);
}
答案 0 :(得分:1)
将代码(1)分解为列表的方式; (2)将数据添加到列表中,它是如此混乱,并且缺乏验证,因此难怪您很难对它进行排序。
从一开始就读取数据是有缺陷的。参见Why is while ( !feof (file) ) always wrong?。此外,您无法验证fscanf
的单次返回。如果一次读取失败,则可以通过盲目使用不确定的值来调用 Undefined Behavior (并且从该点开始的每个值可能都是不确定的)。所有的赌注都结束了。
但是,使用#define
定义所需的常量值得赞扬,但是随后通过在所有{中包含 field-width 修饰符,未能保护数组边界{1}}转换说明符。在使用char*
常量时,您可以转身对文件名进行硬编码。不要那样做将文件名作为参数传递给程序或提示其输入。
每次一次处理“数据行”时,都应使用面向 line 的输入函数,例如#define
或POSIX fgets
,然后进行解析数据行中需要的值。这样做的好处是允许单独验证(1)从文件中读取数据; (2)解析结果缓冲区中的值。如果由于某种原因格式出现错误,则解析将失败,并且您可以简单地getline
读取循环并读取下一行,而不会出现未定义行为的风险。
创建列表时,您只需要一个continue
函数即可创建列表(如果不存在),并根据需要分配每个其他节点并将其添加到列表中。您的代码看起来试图通过简单的前向链接将节点添加到列表中(这很好,但是如果没有更多的链接,则会导致列表以相反的顺序保存在内存中)
请勿将读取数据与列表操作混在一起。而是读取所需的数据并将其传递给append()
函数。虽然这完全取决于您,但无法将读取/解析和追加分开只会导致列表函数不可重用。
例如,与其尝试在列表函数中读取和解析数据,不如在append()
中打开文件并将数据解析为临时main()
1 结构,并将列表地址和指向临时数据的指针传递给您的append函数。您可以执行以下操作来读取和解析数据,并将所需的值传递给函数:
goodsinfo
(注意:,该程序将文件名从中读取数据作为第一个参数,如果没有提供文件名,则默认情况下从int main (int argc, char **argv)
{
char buf[MAXC]; /* read buffer */
size_t linecnt = 0; /* line counter */
goodslist *list = NULL; /* linked list pointer */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("fopen-file");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line of data */
goodsinfo tmp = { .goods_id = "" }; /* temp data struct */
/* parse and validate data in buf (can be separate function) */
if (sscanf (buf, "%29s %29s %d %29s %d %d", tmp.goods_id,
tmp.goods_name, &tmp.goods_price, tmp.goods_discount,
&tmp.goods_amount, &tmp.goods_remain) != 6) {
fprintf (stderr, "error: invalid format line %zu.\n", linecnt+1);
continue;
}
if (!append (&list, &tmp)) /* append to list/validate */
break;
}
if (fp != stdin) /* close file if not stding */
fclose (fp);
prn_list (list); /* print list */
free_list (list); /* free list data */
return 0;
}
进行读取。另外请注意,您声明了列表作为指向stdin
的指针,而不是指向 pointer-to-pointer-to goodslist
)
读取并解析数据后,您的goodslist
函数只需为append()
分配存储,并为新的列表节点分配存储。它只有两种情况要处理(1)列表是否为空? -离开data
;否则(2)设置node->next = NULL
等于当前列表地址,然后再将新节点的地址分配为新列表地址,以将节点链接在一起,例如
node->next
将其完全放入,您可以执行以下操作:
/* function to allocate goodslist node and append allocated goodsinfo
* data to list. Takes address of list pointer and pointer to goodsinfo data
* to append to list. Returns pointer new node on success, NULL otherwise.
*/
goodsinfo *append (goodslist **l, goodsinfo *tmp)
{
goodsinfo *data = malloc (sizeof *data); /* allocate/validate data */
if (!data) {
perror ("malloc-data");
return NULL;
}
*data = *tmp; /* fill allocated data block with tmp values */
/* allocate/validate list node */
goodslist *node = malloc (sizeof *node);
if (!node) {
perror ("malloc-node");
free (data);
return NULL;
}
node->data = data; /* initialize data and set next NULL */
node->next = NULL;
if (*l) /* if list exists, chain next to list */
node->next = *l;
return ((*l = node)->data); /* assign new node as list, return data */
}
(注意:您的#include <stdio.h>
#include <stdlib.h>
#define MAX_ID_LEN 30
#define MAX_NAME_LEN MAX_ID_LEN
#define MAX_PRICE_LEN MAX_NAME_LEN
#define MAX_DISCOUNT_LEN MAX_PRICE_LEN
#define MAXC 1024 /* read buffer size (don't skimp) */
typedef struct {
char goods_id[MAX_ID_LEN];
char goods_name[MAX_NAME_LEN];
int goods_price;
char goods_discount[MAX_DISCOUNT_LEN];
int goods_amount;
int goods_remain;
} goodsinfo;
typedef struct goodslist {
goodsinfo *data; /* make data a pointer and allocate */
struct goodslist *next;
} goodslist;
/* bool check_nullfile(void)
* (poor test, if first char not 0-9, test fails)
*/
/* function to allocate goodslist node and append allocated goodsinfo
* data to list. Takes address of list pointer and pointer to goodsinfo data
* to append to list. Returns pointer new node on success, NULL otherwise.
*/
goodsinfo *append (goodslist **l, goodsinfo *tmp)
{
goodsinfo *data = malloc (sizeof *data); /* allocate/validate data */
if (!data) {
perror ("malloc-data");
return NULL;
}
*data = *tmp; /* fill allocated data block with tmp values */
/* allocate/validate list node */
goodslist *node = malloc (sizeof *node);
if (!node) {
perror ("malloc-node");
free (data);
return NULL;
}
node->data = data; /* initialize data and set next NULL */
node->next = NULL;
if (*l) /* if list exists, chain next to list */
node->next = *l;
return ((*l = node)->data); /* assign new node as list, return data */
}
/* simple print list function */
void prn_list (goodslist *l)
{
if (!l)
return;
while (l) {
printf (" %-8s %-8s %8d %-8s %8d %9d\n", l->data->goods_id,
l->data->goods_name, l->data->goods_price,
l->data->goods_discount, l->data->goods_amount,
l->data->goods_remain);
l = l->next;
}
}
/* simple free list function */
void free_list (goodslist *l)
{
if (!l)
return;
goodslist *iter = l;
while (iter) {
goodslist *victim = iter;
free (iter->data);
iter = iter->next;
free (victim);
}
}
int main (int argc, char **argv)
{
char buf[MAXC]; /* read buffer */
size_t linecnt = 0; /* line counter */
goodslist *list = NULL; /* linked list pointer */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("fopen-file");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line of data */
goodsinfo tmp = { .goods_id = "" }; /* temp data struct */
/* parse and validate data in buf (can be separate function) */
if (sscanf (buf, "%29s %29s %d %29s %d %d", tmp.goods_id,
tmp.goods_name, &tmp.goods_price, tmp.goods_discount,
&tmp.goods_amount, &tmp.goods_remain) != 6) {
fprintf (stderr, "error: invalid format line %zu.\n", linecnt+1);
continue;
}
if (!append (&list, &tmp)) /* append to list/validate */
break;
}
if (fp != stdin) /* close file if not stding */
fclose (fp);
prn_list (list); /* print list */
free_list (list); /* free list data */
return 0;
}
弊大于利,并且如果第一个非空白字符不是数字,则会失败)
使用/输出示例
(注意:,当使用链接而不保留bool check_nullfile(void)
指针时,将以相反的顺序存储列表节点)
"last"
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时可以释放。
当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。
对于Linux,$ ./bin/ll_goodslist dat/goodsinfo.txt
1004 new5 100 0.8 70 80
1003 new4 88 0.8 70 80
1002 new3 70 0.8 10 10
1001 new2 80 0.9 80 80
1000 new1 90 0.9 90 80
是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。
valgrind
始终确认已释放已分配的所有内存,并且没有内存错误。
仔细检查一下,如果还有其他问题,请告诉我。
脚注
$ valgrind ./bin/ll_goodslist dat/goodsinfo.txt
==3493== Memcheck, a memory error detector
==3493== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3493== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==3493== Command: ./bin/ll_goodslist dat/goodsinfo.txt
==3493==
1004 new5 100 0.8 70 80
1003 new4 88 0.8 70 80
1002 new3 70 0.8 10 10
1001 new2 80 0.9 80 80
1000 new1 90 0.9 90 80
==3493==
==3493== HEAP SUMMARY:
==3493== in use at exit: 0 bytes in 0 blocks
==3493== total heap usage: 11 allocs, 11 frees, 1,152 bytes allocated
==3493==
==3493== All heap blocks were freed -- no leaks are possible
==3493==
==3493== For counts of detected and suppressed errors, rerun with: -v
==3493== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
或camelCase
变量名,而保留所有小写,而使用所有小写 / em>用于宏和常量的名称。这是一个风格问题,因此完全取决于您,但是如果不遵循它,可能会在某些圈子中导致错误的第一印象。答案 1 :(得分:0)
有两个问题。最主要的原因是您创建的链表不正确。
对于您读取的第一条记录,*L
的值未定义-您仅创建了L
。因此,访问(*L)->next
将导致您的程序崩溃。
listptr->next=(*L)->next;
但这是由于您对L
首先是什么有误解。您的info_init
函数正在传递GoodList **
,因为它正在使用该参数来传回新创建的列表。您并不是要传递变量GoodList **
,而是要传递指向GoodList *
的指针。首先应将此变量初始化为NULL
。
int main (void)
{
GoodsList *L=NULL;
info_init(&L);
return 0;
}
然后代替
listptr->next=(*L)->next;
(*L)->next=listptr;
你有
listptr->next=*L;
*L=listptr;
这意味着新创建的列表节点将指向存储在*L
中的前一个节点。与main
中一样,它最初是NULL
,这意味着第一个节点将指向NULL
旁边。然后*L
将更新为指向第一个节点。然后下一次,第二个节点将指向第一个,等等。
另一个问题并不十分糟糕-您将始终读取一个额外的空节点,因为您要扩展列表,然后尝试读取某些内容。检查EOF太早了,因为它只会尝试阅读某些内容后,请注册EOF。
在每个记录中读取的一种更可靠的方法是使用fgets
在每一行中进行读取,并使用sscanf
来读取每个字段。您还可以通过检查check_nullfile
的返回值来消除对fopen
函数的需求。如果文件为空,则列表将为空,因为不会读取或分配任何内容。
您也不需要强制转换malloc
的返回值,使用sizeof(*listptr)
来计算listptr
所需的内存量会更安全,因为即使您可以更改listptr
的类型。
void info_init(GoodsList **L) {
FILE * fp;
fp=fopen("goodsinfo.txt", "r");
if(fp)
{
char line[512];
GoodsList *listptr;
while (fgets(line,512,fp))
{
listptr=malloc(sizeof(*listptr));
listptr->next=(*L);
(*L)=listptr;
sscanf(line,"%4s\t%4s\t%d\t%s\t%d\t%d",listptr->data.goods_id,
listptr->data.goods_name,
&(listptr->data.goods_price),
listptr->data.goods_discount,
&(listptr->data.goods_amount),
&(listptr->data.goods_remain));
CurrentCnt++;
}
fclose(fp);
}
printf("%d\n", CurrentCnt);
}