我正试图使用C将csv文件中的内容插入到链接列表中。然而,我收到了不少垃圾输出。源代码如下。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct product *customer_head;
struct customer
{
long long int c_id;//first 6 characters=date & next 6 characters=time & next characters=counter no & city code
/*Compulsory Fields (Cannot be Skipped)*/
char name[57];
long long int ph_no;//10 digit phone number
/*Non Compulsory Fields (Can be Skipped)*/
char address[58];
char city[25];
char state_code[2];
char pin[6];
char email[60];
struct customer *next;
};
struct customer * load()
{
FILE * cust=fopen("customer_db.csv","r");
struct customer *temp,*ptr;
customer_head=NULL;
char str[208];
char *token,*eptr1,*eptr2;
int line_cnt=0,i=0;
while(fgets(str,234,cust)!=NULL)
{
line_cnt=0;
i=0;
ptr=(struct customer *)malloc(sizeof(struct customer));
for(;str[i];i++)
{
if(str[i]=='n')
{
str[i]=' ';
i=0;
break;
}
}
token=strtok(str,",");
while(token!=NULL)
{
if(line_cnt==0)
ptr->c_id=strtoll(token,&eptr1,10);
else if(line_cnt==1)
ptr->ph_no=strtoll(token,&eptr2,10);
else if(line_cnt==2)
sprintf(ptr->name,"%s",token);
else if(line_cnt==3)
sprintf(ptr->address,"%s",token);
else if(line_cnt==4)
sprintf(ptr->city,"%s",token);
else if(line_cnt==5)
sprintf(ptr->state_code,"%s",token);
else if(line_cnt==6)
sprintf(ptr->pin,"%s",token);
else
sprintf(ptr->email,"%s",token);
line_cnt++;
token=strtok(NULL,",");
}
if(customer_head==NULL)
customer_head=ptr;
else
temp->next=ptr;
temp=ptr;
}
}
int print(struct customer *h)
{
while(h->next!=NULL)
{
printf("nCustomer ID: ");
printf("%lld",h->c_id);
printf("nName: ");
puts(h->name);
printf("Phone Number: ");
printf("%lld",h->ph_no);
printf("nAddress: ");
puts(h->address);
printf("City: ");
puts(h->city);
printf("State Code: ");
puts(h->state_code);
printf("PIN: ");
puts(h->pin);
printf("Email: ");
puts(h->email);
h=h->next;
}
printf("nCustomer ID: ");
printf("%lld",h->c_id);
printf("nName: ");
puts(h->name);
printf("Phone Number: ");
printf("%ld",h->ph_no);
printf("nAddress: ");
puts(h->address);
printf("City: ");
puts(h->city);
printf("State Code: ");
puts(h->state_code);
printf("PIN: ");
puts(h->pin);
printf("Email: ");
puts(h->email);
return 1;
}
int main()
{
load();
print(customer_head);
}
我还在这里附上csv文件。为了使程序不那么复杂,我从csv文件中删除了标题。它们按的顺序排列
客户ID、电话号码、姓名、地址、城市、州代码、PIN、电子邮件
1403201156540201,2226179183,Katherine_Hamilton,87_Thompson_St.,Fremont,IA,502645,k_hamilton@gmail.com
2204201532220103,8023631298,Marc_Knight,-,-,-,-,-
0305201423120305,8025595163,Albie_Rowland,-,Hamburg,NY,140752,-
0607201232220901,4055218053,Grant_Phelps,-,-,-,-,-
短划线(-)表示这些字段应保持为空。
输出如下:
Customer ID: 1403201156540201
Name: Katherine_Hamilton
Phone Number: 2226179183
Address: 87_Thompson_St.
City: Fremont
State Code: IA502645k_hamilton@gmail.com
PIN: 502645k_hamilton@gmail.com
Email: k_hamilton@gmail.com
Customer ID: 2204201532220103
Name: Marc_Knight
Phone Number: 8023631298
Address: -
City: -
State Code: -
PIN: -
Email: -
Customer ID: 305201423120305
Name: Albie_Rowland
Phone Number: 8025595163
Address: -
City: Hamburg
State Code: NY140752-
PIN: 140752-
Email: -
Customer ID: 607201232220901
Name: Grant_Phelps
Phone Number: 4055218053
Address: -
City: -
State Code: -
PIN: -
Email: -
正如您所看到的,内容在相当多的地方被合并。我不明白为什么。
从注释中,您知道您的字符数组声明中有一个太少的字符,至少在char state_code[2];
离开数组时没有nul终止,导致未定义行为的情况下,您应该确保所有输入都有有效的存储空间。(不要吝啬缓冲区大小)
一般来说,你会让自己的事情变得比需要的更困难。你没有试图使用strtok()
和计数字段并处理if, else if ...
的8部分链中的每个字段,而是有固定的输入字段,所以只需使用sscanf()
解析数据并验证转换次数即可确认解析成功,例如
/** fill list from csv file */
list_t *list_from_csv (list_t *list, FILE *fp)
{
char buf[MAXC];
node_t data = { .c_id = 0, .next = NULL };
while (fgets (buf, MAXC, fp)) { /* read each line in file */
/* parse and VALIDATE values from line */
if (sscanf (buf, "%lld,%lld,%63[^,],%63[^,],%31[^,],%7[^,],%7[^,],%63[^,n]",
&data.c_id, &data.ph_no, data.name, data.address, data.city,
data.state_code, data.pin, data.email) == 8) {
if (!add (list, &data)) /* validate add to list or break */
break;
}
}
return list;
}
这里,list_t
只是一个附加的"包装器"结构,它为链表保存一个head
和tail
指针。这允许您在所需的范围内声明多个列表,并允许通过使tail
指针始终指向列表中的最后一个节点(temp
)来插入相同的O(1)。这里,head
和tail
只是包装器的一部分,并作为参数传递,而不必将列表指针声明为全局指针(不良做法)。列表和包装结构中的每个节点都可以写成:
#define BYTE8 8 /* if you need a constant, #define one (or more) */
#define BYTE32 32
#define BYTE64 64
#define MAXC 1024
typedef struct node_t { /* list node */
/* 6 characters=date & 6 characters=time & counter no & city code */
long long int c_id;
/*Compulsory Fields (Cannot be Skipped)*/
char name[BYTE64];
long long int ph_no; //10 digit phone number
/*Non Compulsory Fields (Can be Skipped)*/
char address[BYTE64];
char city[BYTE32];
char state_code[BYTE8];
char pin[BYTE8];
char email[BYTE64];
struct node_t *next;
} node_t;
typedef struct { /* list wrapper with head & tail pointers */
node_t *head, *tail;
} list_t;
然后,与其将load()
编写为同时包含FILE
和列表操作,不如将列表操作分开。只需创建一个add()
函数即可将一个节点添加到您的列表中,例如
/** add node at end of list, update tail to end */
node_t *add (list_t *l, node_t *data)
{
node_t *node = malloc (sizeof *node); /* allocate node */
if (!node) { /* validate allocation */
perror ("malloc-node");
return NULL;
}
*node = *data; /* initialize members values */
if (!l->head) /* if 1st node, node is head/tail */
l->head = l->tail = node;
else { /* otherwise */
l->tail->next = node; /* add at end, update tail pointer */
l->tail = node;
}
return node; /* return new node */
}
现在,您的加载函数只需要读取文件中的每一行并解析该行,然后调用add()
,将指向数据结构的指针和列表指针作为参数进行传递。您的load()
功能简化为:
/** fill list from csv file */
list_t *list_from_csv (list_t *list, FILE *fp)
{
char buf[MAXC];
node_t data = { .c_id = 0, .next = NULL };
while (fgets (buf, MAXC, fp)) { /* read each line in file */
/* parse and VALIDATE values from line */
if (sscanf (buf, "%lld,%lld,%63[^,],%63[^,],%31[^,],%7[^,],%7[^,],%63[^,n]",
&data.c_id, &data.ph_no, data.name, data.address, data.city,
data.state_code, data.pin, data.email) == 8) {
if (!add (list, &data)) /* validate add to list or break */
break;
}
}
return list;
}
(注意:使用strtok()
或sscanf()
时,无需从输入字符串中删除尾随的'n'
——只需将其作为strtok()
的分隔符,或将其从sscanf()
的转换中排除即可)
此外,您不需要多次调用puts()
和printf()
来打印列表中每个节点的数据。看看您为打印数据而进行了多少次函数调用。您只需要一次呼叫printf()
,例如
/** print all nodes in list */
void prn_list (list_t *l)
{
if (!l->head) {
puts ("list-empty");
return;
}
for (node_t *n = l->head; n; n = n->next)
printf ("nCustomer ID: %lldn"
"Name: %sn"
"Phone Number: %lldn"
"Address: %sn"
"City: %sn"
"State Code: %sn"
"PIN: %sn"
"Email: %sn", n->c_id, n->name, n->ph_no, n->address, n->city,
n->state_code, n->pin, n->email);
}
在main()
中,只需声明list_t
包装器的一个实例,打开/验证FILE
,然后将指向列表和文件流的指针传递给list_from_csv()
(load()
),然后打印列表,最后释放分配的所有内存,就完成了。(是的,内存会在退出时释放,但要尽早养成良好的习惯——用不了多久,你就会在函数中使用分配内存,在return
之前释放内存失败会导致内存泄漏)
int main (int argc, char **argv) {
list_t list = { .head = NULL, .tail = NULL };
/* 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;
}
if (!list_from_csv (&list, fp))
return 1;
if (fp != stdin) /* close file if not stdin */
fclose (fp);
prn_list (&list);
del_list (&list);
}
总之,你会得到类似于以下的东西:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BYTE8 8 /* if you need a constant, #define one (or more) */
#define BYTE32 32
#define BYTE64 64
#define MAXC 1024
typedef struct node_t { /* list node */
/* 6 characters=date & 6 characters=time & counter no & city code */
long long int c_id;
/*Compulsory Fields (Cannot be Skipped)*/
char name[BYTE64];
long long int ph_no; //10 digit phone number
/*Non Compulsory Fields (Can be Skipped)*/
char address[BYTE64];
char city[BYTE32];
char state_code[BYTE8];
char pin[BYTE8];
char email[BYTE64];
struct node_t *next;
} node_t;
typedef struct { /* list wrapper with head & tail pointers */
node_t *head, *tail;
} list_t;
/** add node at end of list, update tail to end */
node_t *add (list_t *l, node_t *data)
{
node_t *node = malloc (sizeof *node); /* allocate node */
if (!node) { /* validate allocation */
perror ("malloc-node");
return NULL;
}
*node = *data; /* initialize members values */
if (!l->head) /* if 1st node, node is head/tail */
l->head = l->tail = node;
else { /* otherwise */
l->tail->next = node; /* add at end, update tail pointer */
l->tail = node;
}
return node; /* return new node */
}
/** print all nodes in list */
void prn_list (list_t *l)
{
if (!l->head) {
puts ("list-empty");
return;
}
for (node_t *n = l->head; n; n = n->next)
printf ("nCustomer ID: %lldn"
"Name: %sn"
"Phone Number: %lldn"
"Address: %sn"
"City: %sn"
"State Code: %sn"
"PIN: %sn"
"Email: %sn", n->c_id, n->name, n->ph_no, n->address, n->city,
n->state_code, n->pin, n->email);
}
/** delete all nodes in list */
void del_list (list_t *l)
{
node_t *n = l->head;
while (n) {
node_t *victim = n;
n = n->next;
free (victim);
}
}
/** fill list from csv file */
list_t *list_from_csv (list_t *list, FILE *fp)
{
char buf[MAXC];
node_t data = { .c_id = 0, .next = NULL };
while (fgets (buf, MAXC, fp)) { /* read each line in file */
/* parse and VALIDATE values from line */
if (sscanf (buf, "%lld,%lld,%63[^,],%63[^,],%31[^,],%7[^,],%7[^,],%63[^,n]",
&data.c_id, &data.ph_no, data.name, data.address, data.city,
data.state_code, data.pin, data.email) == 8) {
if (!add (list, &data)) /* validate add to list or break */
break;
}
}
return list;
}
int main (int argc, char **argv) {
list_t list = { .head = NULL, .tail = NULL };
/* 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;
}
if (!list_from_csv (&list, fp))
return 1;
if (fp != stdin) /* close file if not stdin */
fclose (fp);
prn_list (&list);
del_list (&list);
}
示例使用/输出
使用dat/customer_list.txt
中的输入文件,运行您将收到的程序:
$ ./bin/customer_list dat/customer_list.txt
Customer ID: 1403201156540201
Name: Katherine_Hamilton
Phone Number: 2226179183
Address: 87_Thompson_St.
City: Fremont
State Code: IA
PIN: 502645
Email: k_hamilton@gmail.com
Customer ID: 2204201532220103
Name: Marc_Knight
Phone Number: 8023631298
Address: -
City: -
State Code: -
PIN: -
Email: -
Customer ID: 305201423120305
Name: Albie_Rowland
Phone Number: 8025595163
Address: -
City: Hamburg
State Code: NY
PIN: 140752
Email: -
Customer ID: 607201232220901
Name: Grant_Phelps
Phone Number: 4055218053
Address: -
City: -
State Code: -
PIN: -
Email: -
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有2个责任:(1)始终为内存块保留一个指向起始地址的指针,因此,(2)当不再需要时,它可以被释放。
您必须使用内存错误检查程序来确保您不会试图访问内存或在分配的块的边界之外写入,尝试读取或基于未初始化的值进行条件跳转,最后确认您释放了所有分配的内存。
对于Linux,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序即可
$ valgrind ./bin/customer_list dat/customer_list.txt
==14823== Memcheck, a memory error detector
==14823== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14823== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==14823== Command: ./bin/customer_list dat/customer_list.txt
==14823==
Customer ID: 1403201156540201
Name: Katherine_Hamilton
Phone Number: 2226179183
Address: 87_Thompson_St.
City: Fremont
State Code: IA
PIN: 502645
Email: k_hamilton@gmail.com
<snipped rest>
==14823==
==14823== HEAP SUMMARY:
==14823== in use at exit: 0 bytes in 0 blocks
==14823== total heap usage: 7 allocs, 7 frees, 6,728 bytes allocated
==14823==
==14823== All heap blocks were freed -- no leaks are possible
==14823==
==14823== For counts of detected and suppressed errors, rerun with: -v
==14823== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
请始终确认您已经释放了分配的所有内存,并且没有内存错误。
虽然只需确保每个字符串都有足够的存储空间,就可以解决很大一部分问题,但需要花时间仔细考虑使用sscanf()
解析值,并且由于您控制了转换,因此无需从文件读取的行中删除尾部的'n'
。(只是不要在从输入字符串解析的值中包含换行符)如果你确实想从末尾解析'n'
,你应该使用,例如
str[strcspn (str, "n")] = 0;
最后,对于上面sscanf()
和strcspn()
使用的格式字符串,请确保您完全理解它们是如何工作的,请参阅man 3 scanf和man 3 strspn
如果您还有其他问题,请告诉我。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum operations {EXIT, ADD_BOOK, ISSUE_BOOK, RETURN_BOOK, DISPLAY_BOOKS,
SORT_BOOKS, DISPLAY_BOOK_SUBJECTWISE};
typedef enum operations MENU;
typedef struct book
{
int bookId;
char bookName[30];
char subject[30];
char author[30];
float price;
}BOOK;
typedef struct node
{
struct node *prev;
BOOK info;
struct node *next;
}NODE;
NODE *head=NULL;
NODE *issuehead=NULL;
static int counter=0;
MENU menu_function()
{
int choice;
printf(" 1. Add Bookn");
printf(" 2. Issue Bookn");
printf(" 3. Return Bookn");
printf(" 4. Display Bookn");
printf(" 5. Sort Bookn");
printf(" 6. Display Books Subject-wisen");
printf(" 0. EXITn");
printf(" Enter your choice::");
scanf("%d",&choice);
if(choice < 0 || choice > 6)
{
printf(" Error: Enter valid choicen");
}
return choice;
}
void FreeList()
{
while(CountNodes(head))
{
DeleteBook();
}
while(CountNodes(issuehead))
{
DeleteIssues();
}
}
int CountNodes(NODE *trav)
{
int count=0;
while(trav!=NULL)
{
count++;
trav=trav->next;
}
return count;
}
void DeleteBook()
{
NODE *temp=head;
head = head->next;
free(temp);
temp=NULL;
}
void DeleteIssues()
{
NODE *temp=issuehead;
issuehead = issuehead->next;
free(temp);
temp=NULL;
}
void AcceptData(BOOK *book)
{
book->bookId=++counter;
getchar();
printf(" Enter Book name::");
scanf("%[^n]s",&book->bookName);
getchar();
printf(" Enter Subject::");
scanf("%[^n]s",&book->subject);
getchar();
printf(" Enter author::");
scanf("%[^n]s",&book->author);
printf(" Enter price::");
scanf("%f",&book->price);
}
void DisplayData(BOOK book)
{
printf(" %dtt",book.bookId);
printf(" %stt",book.bookName);
printf(" %stt",book.subject);
printf(" %stt",book.author);
printf(" %gn",book.price);
}
NODE *CreateNode()
{
NODE *temp;
temp = (NODE *) malloc(sizeof(NODE));
temp->next=NULL;
temp->prev=NULL;
return temp;
}
void AddtoBooklist(BOOK book)
{
NODE *new_node;
new_node=CreateNode();
new_node->info=book;
if(head == NULL)
{
head=new_node;
}
else
{
new_node->next=head;
head->prev=new_node;
head=new_node;
}
}
void DisplayBooks(NODE *trav)
{
if(trav==NULL)
{
printf(" Book list is empty...n");
}
else
{
printf(" Available Booksn");
while(trav!=NULL)
{
DisplayData(trav->info);
trav=trav->next;
}
printf("n");
}
}
void IssueBook()
{
NODE *trav=head, *prev=NULL;
NODE *temp, *right;
int bookId;
printf(" Enter Book ID::");
scanf("%d",&bookId);
while(bookId != trav->info.bookId)
{
prev=trav;
trav=trav->next;
if(trav==NULL)
{
printf(" Book not found...n");
break;
}
}
if(trav==head)
{
temp = trav;
head->prev=NULL;
head = head->next;
trav=NULL;
IssueAtFirst(temp);
printf(" Book issued successfully...n");
}
else if(trav->next==NULL)
{
temp=trav;
prev->next=NULL;
trav->prev=NULL;
trav=NULL;
IssueAtFirst(temp);
printf(" Book issued successfully...n");
}
else
{
temp=trav;
right=trav->next;
prev->next=right;
right->prev=prev;
trav->next=NULL;
trav->prev=NULL;
trav=NULL;
IssueAtFirst(temp);
printf(" Book issued successfully...n");
}
}
void IssueAtFirst(NODE *temp)
{
if(issuehead == NULL)
{
issuehead=temp;
temp->next=NULL;
temp->prev=NULL;
temp=NULL;
}
else
{
temp->next=issuehead;
temp->prev=NULL;
issuehead=temp;
temp=NULL;
}
}
void ReturnBook()
{
NODE *trav=issuehead, *prev=NULL;
NODE *temp, *right;
int bookId;
printf(" Enter Book ID::");
scanf("%d",&bookId);
while(bookId != trav->info.bookId)
{
prev=trav;
trav=trav->next;
if(trav==NULL)
{
printf(" Book not found...n");
break;
}
}
if(trav==issuehead)
{
temp = trav;
issuehead->prev = NULL;
issuehead = issuehead->next;
trav=NULL;
ReturnAtFirst(temp);
printf(" Book returned successfully...n");
}
else if(trav->next==NULL)
{
temp=trav;
prev->next=NULL;
trav->prev=NULL;
trav=NULL;
ReturnAtFirst(temp);
printf(" Book returned successfully...n");
}
else
{
temp=trav;
right=trav->next;
prev->next=right;
right->prev=prev;
trav->next=NULL;
trav->prev=NULL;
trav=NULL;
ReturnAtFirst(temp);
printf(" Book returned successfully...n");
}
}
void ReturnAtFirst(NODE *temp)
{
if(head == NULL)
{
head=temp;
temp->next=NULL;
temp->prev=NULL;
temp=NULL;
}
else
{
head->prev=temp;
temp->next=head;
temp->prev=NULL;
head=temp;
temp=NULL;
}
}
void SortBooks()
{
NODE *trav=head,*right=head->next;
BOOK temp;
while(trav->next!=NULL)
{
right=trav->next;
while(right!=NULL)
{
if(trav->info.bookId > right->info.bookId)
{
temp = trav->info;
trav->info = right->info;
right->info = temp;
}
right=right->next;
}
trav=trav->next;
}
}
void AddBooksToFile()
{
NODE *trav=head;
FILE *fp;
fp=fopen("Booklist.dat","wb");
if(fp!=NULL)
{
while(trav!=NULL)
{
fwrite(&trav->info, sizeof(BOOK),1,fp);
trav=trav->next;
}
}
fclose(fp);
}
void AddIssuesToFile()
{
NODE *trav=issuehead;
FILE *fp=issuehead;
fp=fopen("Issuelist.dat","wb");
if(fp!=NULL)
{
while(trav!=NULL)
{
fwrite(&trav->info, sizeof(BOOK),1,fp);
trav=trav->next;
}
}
fclose(fp);
}
void ReadIssuesFromFile()
{
BOOK book;
FILE *fp;
fp=fopen("Issuelist.dat","rb");
if(fp!=NULL)
{
while((fread(&book,sizeof(BOOK),1,fp))!=0)
{
AddtoIssuelist(book);
}
}
fclose(fp);
}
void ReadBooksFromFile()
{
BOOK book;
FILE *fp;
fp=fopen("Booklist.dat","rb");
if(fp!=NULL)
{
while((fread(&book,sizeof(BOOK),1,fp))!=0)
{
AddtoBooklist(book);
}
}
fclose(fp);
}
void AddtoIssuelist(BOOK book)
{
NODE *new_node;
new_node=CreateNode();
new_node->info=book;
if(issuehead == NULL)
{
issuehead=new_node;
}
else
{
new_node->next=issuehead;
issuehead->prev=new_node;
issuehead=new_node;
}
}
void DisplaySubjectWise(NODE *head)
{
NODE *trav=head;
char subject[30];
printf(" Enter subject::");
scanf("%s",&subject);
while(trav!=NULL)
{
if(stricmp(subject, trav->info.subject)== 0)
{
DisplayData(trav->info);
}
trav=trav->next;
}
}
int main()
{
MENU choice;
int data;
BOOK book;
ReadBooksFromFile();
ReadIssuesFromFile();
while((choice=menu_function())!=EXIT)
{
switch(choice)
{
case ADD_BOOK:
AcceptData(&book);
AddtoBooklist(book);
printf(" Book added to library successfully...n");
break;
case ISSUE_BOOK:
if(head==NULL)
{
printf(" No Books are available now...n");
break;
}
else
{
IssueBook();
}
break;
case RETURN_BOOK:
if(issuehead==NULL)
{
printf(" No Books are issued...n");
break;
}
else
{
ReturnBook();
}
break;
case DISPLAY_BOOKS:
DisplayBooks(head);
DisplayBooks(issuehead);
printf("n");
break;
case SORT_BOOKS:
if(head==NULL)
{
printf(" No Books to sort...n");
break;
}
SortBooks();
break;
case DISPLAY_BOOK_SUBJECTWISE:
if(head==NULL)
{
printf(" No Books to display...n");
break;
}
DisplaySubjectWise(head);
break;
}
}
AddBooksToFile();
AddIssuesToFile();
FreeList();
return 0;
}
供您参考