为什么第五个数据不起作用?fscanf() 有问题吗?


#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,"%4st",listptr->data.goods_id);
fscanf(fp,"%4st",listptr->data.goods_name);
fscanf(fp,"%dt",&(listptr->data.goods_price));
fscanf(fp,"%st",listptr->data.goods_discount);
fscanf(fp,"%dt",&(listptr->data.goods_amount));
fscanf(fp,"%d",&(listptr->data.goods_remain));
/*          printf("%c%c%c%cn",listptr->data.goods_id[0],listptr-                
>data.goods_id[1],listptr->data.goods_id[2],listptr->data.goods_id[3]);
printf("%c%c%c%cn",listptr->data.goods_name[0],listptr-        
>data.goods_name[1],listptr->data.goods_name[2],listptr- 
>data.goods_name[3]);
printf("%dn",listptr->data.goods_price);
printf("%c%c%c%cn",listptr->data.goods_discount[0],listptr- 
>data.goods_discount[1],listptr->data.goods_discount[2],listptr- 
>data.goods_discount[3]);
printf("%dn",listptr->data.goods_amount);
printf("%dn",listptr->data.goods_remain); these are my 
testing*/
CurrentCnt++;
if (feof(fp)) break;
}
fclose(fp);
}
printf("%dn", 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

为什么职位4有效,但其他职位不行?位置1 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,"%st",listptr->data.goods_id);
fscanf(fp,"%st",listptr->data.goods_name);
fscanf(fp,"%dt",&(listptr->data.goods_price));
fscanf(fp,"%st",listptr->data.goods_discount);
fscanf(fp,"%dt",&(listptr->data.goods_amount));
fscanf(fp,"%d",&(listptr->data.goods_remain));
//if (feof(fp)) break;//position4
CurrentCnt++;
}
fclose(fp);
}
printf("%dn", CurrentCnt);

}

您对代码(1)进行分解以处理列表的方式;以及(2)要将数据添加到列表中,它非常混乱,而且缺乏验证,难怪你很难将其整理出来。

数据的读取从一开始就有缺陷。请参阅为什么while(!feof(file))总是错误?。此外,您无法验证fscanf的单个返回。如果一次读取失败,您可以通过盲目使用不确定的值来调用Undefined Behavior(从那时起的每个值都可能是不确定的)。到那时所有的赌注都结束了。

但是,值得赞扬的是,您使用#define来定义所需的常量,但由于在所有char*转换说明符中都包含字段宽度修饰符,因此无法保护数组边界。当您使用#define常量时,您可以回过头来硬编码您的文件名。不要那样做。将文件名作为参数传递给程序或提示输入。

无论何时一次处理"数据行",都应该使用面向行的输入函数,如fgets或POSIXgetline,然后从数据行中解析所需的值。这提供了允许对(1)从文件读取数据进行单独验证的好处;以及(2)对来自所得到的缓冲器的值进行解析。如果由于某种原因在格式中出现错误,您的解析将失败,您可以简单地continue读取循环并读取下一行,而不存在未定义行为的风险。

创建列表时,只需要一个append()函数,如果列表不存在,该函数将创建该列表,并根据需要将每个附加节点分配和添加到列表中。您的代码试图尝试一个简单的前向链接,将节点添加到列表中(这很好,但如果没有更多的节点,将导致列表以相反的顺序保存在内存中)

不要将读取数据与列表操作混为一谈。而是读取所需的数据并将其传递给append()函数。虽然这在很大程度上取决于您,但未能分离您的读取/解析和附加只会导致处的列表函数不可重用。

例如,与其尝试读取和解析列表函数中的数据,不如在main()中打开文件,并将数据解析到临时goodsinfo1结构,然后将列表地址和指向临时数据的指针传递给追加函数。您可以对数据的读取和解析以及将所需的值传递给您的函数执行类似于以下的操作:

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读取。另外注意,您将列表声明为指向goodslist的指针,而不是指向goodslist指针)

读取和解析数据后,append()函数只需为data分配存储空间,并为新的列表节点分配存储空间。它只有两种情况需要处理(1)列表是空的吗?——离开CCD_ 17;否则(2)将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 %9dn", 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"指针的情况下使用链接会导致列表节点按相反顺序存储)

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

内存使用/错误检查

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

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

对于Linux,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)

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

仔细看看,如果你还有问题,请告诉我。

脚注

  1. 虽然不是错误,但C通常避免使用camelCaseMixedCase变量名,而支持所有小写,同时保留大写名称用于宏和常量。这是一个风格问题——所以这完全取决于你,但如果不遵循它,可能会在某些圈子里给人留下错误的第一印象

有两个问题。主要的一个原因是你创建的链接列表不正确。

对于您读取的第一条记录,*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旁边。然后CCD_ 37将被更新以指向第一节点。然后下一次,第二个节点将指向第一个等…

另一个问题并没有那么糟糕-你总是会读取一个额外的空节点,因为你正在扩展列表,然后试图读取一些内容。你对EOF的检查太早了,因为它只会在你尝试读取内容后注册EOF。

读取每个记录的更稳健的方法是使用fgets读取每一行,并使用sscanf读取每个字段。您还可以通过检查fopen的返回值来消除对check_nullfile函数的需要。如果该文件为空,则会有一个空列表,因为不会读取或分配任何内容。

您也不需要强制转换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,"%4st%4st%dt%st%dt%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("%dn", CurrentCnt);
}

最新更新