如何解决链接列表的插入函数有错误吗? WORD_LIST.HWORD_LIST.C
WORD_LIST.H
我只想创建一个链接列表,我逐行从文件中获取数据并存储在链接列表中,但在列表末尾我调用了显示函数,因此它不打印单词而是打印单个单词CNAA 一次又一次
struct word_node {
struct word_node * next;
const char * word;
};
struct word_list {
struct word_node * head;
long num_words;
};
void reading_words(const char * filename);
void insert(struct word_list *,const char*);
void display(struct word_list *p);
WORD_LIST.C
void reading_words(const char * filename){
struct word_list *p ;
FILE* file = fopen(filename,"r");
char *line = NULL;
size_t len = 0;
ssize_t read;
p = (struct word_list*)malloc(sizeof(struct word_list));
p->head =NULL;
p->num_words =0;
while ((read = getline(&line,&len,file)) != -1) {
line[strlen(line)-1] = '\0';
p->num_words++;
insert(p,line);
}
printf("Size : %ld\n",p->num_words);
display(p);
return ;
}
void insert(struct word_list *p,const char* word){
struct word_node *test ;
test = (struct word_node *)malloc(sizeof(struct word_node));
if(test == NULL){
printf("Out of Memory!");
}
else{
test->word = word;
if(p->head == NULL){
p->head = test
p->head->next = NULL;
}
else{
test->next = p->head;
p->head = test;
}
}
}
void display(struct word_list *p){
struct word_node *temp;
temp = p->head;
while(temp!=NULL){
printf("%s\n",temp->word);
temp=temp->next;
}
}
根据 Word 文件,单词是 极客 手提袋 欧维 黄色 MTA 一位警察 再见 舞蹈 BSRT CNAA
但是在插入功能之后它只打印CNAA 10次
解决方法
当你插入单词时,你就是在调用
insert(p,line);
并将局部变量 line(指向 char 的指针)作为参数传递。稍后在插入功能中你有
test->word = word;
这只是复制指针,所以 test->word
将指向与 line
函数中的局部变量 reading_words
相同的地址。因此,列表的每个元素都指向相同的地址。
相反,您应该使用 strcpy
来复制字符串,但在此之前,您必须为字符串分配内存(稍后将其释放到某处)。类似的东西:
test->word = malloc(strlen(word)+1);
strcpy(test-word,word);
,
让我们尝试更新 reading_words
函数如下:
...
while ((read = getline(&line,&len,file)) != -1) {
line[strlen(line)-1] = '\0';
p->num_words++;
insert(p,line);
line = NULL;
len = 0;
}
...
根据 getline(3)
(link) 的文档:
如果在调用前 *lineptr 设置为 NULL 并且 *n 设置为 0,则 getline() 将分配一个缓冲区来存储该行。
,正如其他人所解释的,您正在向每个单词插入相同的指针,而 getline() 只会更改指针指向的缓冲区的内容。
我建议使用 C99 灵活数组成员而不是指针:
struct word {
struct word *next;
char word[];
};
struct word *new_word(const char *);
void insert_word(struct word **,struct word *);
struct word *read_words(FILE *);
void free_words(struct word *);
void show_words(struct word *,FILE *);
new_word()
函数从给定的字符串创建一个新的 struct word
,通过复制数据到新的动态分配结构:
struct word *new_word(const char *src)
{
const size_t srclen = (src) ? strlen(src) : 0;
struct word *w;
w = malloc(sizeof (struct word) + srclen + 1);
if (!w) {
fprintf(stderr,"Out of memory.\n");
exit(EXIT_FAILURE);
}
w->next = NULL;
if (srclen > 0)
memcpy(w->word,src,srclen);
w->word[srclen] = '\0';
return w;
}
即使在 src == NULL
时,上述方法也有效(创建一个空词)。由于 strlen(NULL)
不安全,我们使用 (expression) ? (if-true) : (if-false)
三元表达式来计算数据的长度。同样,如果没有数据,我们也不想使用 memcpy()
来复制数据。
另一个新函数 free_words()
可用于释放整个单词列表,获取指向列表中第一个单词的指针:
void free_words(struct word *list)
{
while (list) {
struct word *curr = list;
list = list->next;
/* Poison the word,so that if we accidentally
use a word after it has been freed,we'll
surely notice. This is purely a measure to
help with debugging programming bugs. */
curr->next = NULL;
curr->word[0] = '\0';
/* Free this word. */
free(curr);
}
}
因为 insert_word()
现在需要一个指向指针的指针(指向列表变量的指针)和指向要插入的结构的指针,所以它变得微不足道:
void insert_word(struct word **listptr,struct word *w)
{
/* Only do this if neither pointer is NULL. */
if (listptr && w) {
w->next = *listptr;
*listptr = w;
}
}
我们甚至可以实现insert_words()
,它在另一个列表前面插入一个列表:
void insert_words(struct word **list,struct word *first)
{
if (list && first) {
struct word *last = first;
/* Find last word in first-list */
while (last->next)
last = last->next;
last->next = *list;
*list = first;
}
}
show_words()
几乎没有变化,除了我更喜欢它将它应该打印到的流作为第二个参数。通常,它只会是 stdout
。
void show_words(struct word *list,FILE *out)
{
if (list && out) {
while (list) {
fputs(list->word,out);
fputs("\n",out);
list = list->next;
}
}
}
请注意,由于 C 按值传递参数,因此修改 list
变量仅在函数本身内可见。调用者用来传递给 show_words()
的任何变量都不会被修改,即使我们修改了 list
中的 show_words()
。
这留下了 read_words()
。同样,我更喜欢将流句柄而不是文件名传递给函数。通过这种方式,您也可以从标准输入读取输入,只需传递 stdin
。该函数返回读取的单词列表,以相反的顺序,因为我们将读取的每个单词添加到列表的开头。
struct word *read_words(FILE *in)
{
struct word *list = NULL;
struct word *curr;
char *line = NULL;
size_t size = 0;
ssize_t len;
if (!in)
return NULL;
while (1) {
len = getline(&line,&size,in);
if (len == -1)
break;
unsigned char *ptr = (unsigned char *)line;
unsigned char *end = (unsigned char *)line + len;
/* Skip leading whitespace (including newline). */
while (ptr < end && isspace(*ptr))
ptr++;
/* Trim trailing whitespace (including newline). */
while (end > ptr && isspace(end[-1]))
end--;
*end = '\0';
/* Create the word structure. */
curr = new_word(ptr);
/* Prepend to the list. */
insert_word(&list,curr);
}
/* Line buffer is no longer needed. Note,free(NULL) is safe to do. */
free(line);
return list;
}
unsigned char *ptr
和 unsigned char *end
的原因是 isspace()
将字符代码转换为无符号字符。如果我们只使用 char *
,我们必须使用 isspace((unsigned char)(*ptr))
和 isspace((unsigned char)(end[-1]))
来使 isspace() 在所有情况下都能正常工作。
您还应该包含 <locale.h>
和 <ctype.h>
,并在 main()
的开头告诉 C 库使用运行程序的用户的当前语言环境,通过>
setlocale(LC_ALL,"");
根据使用的字符集(它是语言环境的一部分),isspace()
可能会将不同的代码视为空格。特别是,如果使用 ISO Latin 1、ISO Latin 15 或 Windows-1252(8 位西欧),则代码 160(不间断空格)也被视为空格。 (如果你在 Linux 中使用这样的语言环境,你通常通过 AltGr+Space 输入它。)
添加这只是一件小事,并使您的代码在许多不同情况下都能按用户预期工作。
如果您想要原始文件顺序中的单词列表,则需要将其反转。幸运的是,这很容易做到:
struct word *reverse_word_order(struct word *list)
{
struct word *newlist = NULL;
while (list) {
struct word *curr = list;
/* Advance original list pointer */
list = list->next;
/* Prepend word to newlist */
curr->next = newlist;
newlist = curr;
}
return newlist;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。