您好,我正在用C创建一个链接列表,以打印从名为dict.txt的文本文件中给出的唯一标记。我是C的新手,不确定如何正确地从文件中读取每个单词,将其存储到Node中,然后打印结果链表。以下是我的以下方法,在我的add方法中省略了我的contains方法或其实现。我还不关心那部分,只是尝试打印给定文本文件中的每个单词。
struct Node{
char *data;
struct Node *next;
}
typedef struct Node *node;
Node createNode(){
Node new;
new = (Node)malloc(sizeof(struct Node));
*new.next = NULL;
return new;
}
void add(Node head, char data){
Node temp,p;
temp = createNode();
*temp.data = data;
if(head == NULL) head == temp;
else{
p = head;
while(*p.next != NULL) {
//TODO contains method to catch duplicates
p = *p.next;
}
*p.next = temp;
}
return head;
}
int main(){
File *filep;
filep = fopen("dict.txt", "r");
Node head = NULL;
while(fscanf(filep, "%s", word) != EOF){
int i = 0;
if (i == 0) Node parent = add(head, word); //add the first word
else{
//how should I add the other nodes?
}
i++
}
在while循环中,鉴于前一个节点,我正在努力添加一个Node。有什么建议么?我的实现不正确吗?如果它更适合链接列表数据结构,我愿意改变我的方法。谢谢
答案 0 :(得分:2)
首先,让我们从基础开始,您必须将word
读入有效缓冲区,因此声明一个足够大以容纳您的单词的缓冲区(无删节词典中的最长单词(非医学)为29 -个字符)。不要使用魔术数字,因此声明一个足够大的常量以创建具有自动存储功能的缓冲区(不要跳过缓冲区大小),例如>
#define MAXC 1024u /* if you need a constant, define one (or more) */
...
int main (int argc, char **argv) {
char word[MAXC]; /* buffer to hold each word */
注意使用main()
形式来为程序提供参数。 使用它们!请勿对文件名进行硬编码-这就是参数的用途。只需将文件名作为第一个参数传递给您的程序,并确认您至少有2个参数(第一个参数始终是程序名称),例如
if (argc < 2) { /* check that at least 2 arguments available */
fprintf (stderr, "error: insufficient arguments.\n"
"usage: %s filename\n", argv[0]);
return 1;
}
filep = fopen (argv[1], "r"); /* open file given as 1st argument */
现在验证文件已打开以供读取:
if (!filep) { /* validate file is open for reading */
perror ("fopen-filep");
return 1;
}
现在,您可以查看如何处理列表添加。首先,您可以自由编写一个create_node()
函数,该函数对于复杂的结构初始化很有帮助,但是对于单个字符串data
来说,确实没有必要。每次调用addnode()
时只需分配一个新节点,然后只需检查它是否为添加的第一个节点(只需将节点地址分配为列表地址)即可,否则,迭代到末尾您的列表并在其中添加节点以进行有序插入。
(注意:,您可以简单地使用正向链接并不需要查找列表结尾-但最终将以相反的顺序显示列表- -除非您还保留指向列表末尾的指针-这是留给您研究的主题
在查看addnode()
注释之前,如果要分配addnode()
中的第一个节点,则必须将列表指针的地址传递给addnode()
因为列表的地址将设置为第一个节点。您也可以返回指向插入的节点的指针(也可以用作插入成功/失败的指示),并将返回值分配为第一个节点的列表地址-由您决定。但是,当您开始按顺序插入列表或从列表中删除节点时,您同样需要传递列表的地址作为参数,因为更改第一个节点或删除第一个节点会导致您的列表地址要更改。
请记住,您的addnode()
可能类似于:
node_t *addnode (node_t **head, char *word)
{
size_t len = strlen (word); /* length of word */
node_t *node = malloc (sizeof *node), /* allocate new node */
*iter = *head; /* temp iterator for in-order insert */
if (!node) { /* validate EVERY allocation */
perror ("malloc-node"); /* handle error */
return NULL;
}
node->next = NULL;
node->data = malloc (len + 1); /* allocate storage for word */
if (!node->data) { /* ditto! */
free (node); /* free node - word allocation failed */
perror ("malloc-node->data");
return NULL;
}
memcpy (node->data, word, len + 1); /* copy with nul-character */
if (!*head) /* are we the first node? */
return (*head = node); /* just set *head = node */
while (iter->next) /* while next is not NULL */
iter = iter->next; /* advance iter to next node */
return (iter->next = node); /* new node at end */
}
您只需通过传递列表的地址和要添加的单词(例如,在main()
中,
while (fscanf (filep, "%s", word) != EOF) /* read each word */
addnode (&head, word); /* add to list */
fclose (filep); /* close file, done reading */
那是您的addnode()
概括地说,将节点/单词添加到列表中。但是,每个创建列表的程序也应具有删除列表的能力,并且free()
分配给列表的所有内存都可以删除。在节点上进行一次简单的迭代保存指向要删除的节点的指针并在删除victim
节点之前前进到下一个节点是关键,例如
void free_list (node_t *node)
{
while (node) { /* iterate over each node */
node_t *victim = node; /* save node to free as victim */
node = node->next; /* advance to next before freeing current */
free (victim->data); /* free node word */
free (victim); /* free node */
}
}
将其完全放入,您读取文件中的每个单词并将每个单词添加到列表中的节点可能是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1024u /* if you need a constant, define one (or more) */
typedef struct Node {
char *data;
struct Node *next;
} node_t;
node_t *addnode (node_t **head, char *word)
{
size_t len = strlen (word); /* length of word */
node_t *node = malloc (sizeof *node), /* allocate new node */
*iter = *head; /* temp iterator for in-order insert */
if (!node) { /* validate EVERY allocation */
perror ("malloc-node"); /* handle error */
return NULL;
}
node->next = NULL;
node->data = malloc (len + 1); /* allocate storage for word */
if (!node->data) { /* ditto! */
free (node); /* free node - word allocation failed */
perror ("malloc-node->data");
return NULL;
}
memcpy (node->data, word, len + 1); /* copy with nul-character */
if (!*head) /* are we the first node? */
return (*head = node); /* just set *head = node */
while (iter->next) /* while next is not NULL */
iter = iter->next; /* advance iter to next node */
return (iter->next = node); /* new node at end */
}
void prn_list (node_t *node)
{
puts ("\nlinked list:\n");
while (node) { /* iterate over each node */
puts (node->data); /* outputting node->data */
node = node->next; /* advance to next node */
}
}
void free_list (node_t *node)
{
while (node) { /* iterate over each node */
node_t *victim = node; /* save node to free as victim */
node = node->next; /* advance to next before freeing current */
free (victim->data); /* free node word */
free (victim); /* free node */
}
}
int main (int argc, char **argv) {
char word[MAXC]; /* buffer to hold each word */
FILE *filep; /* FILE not File */
node_t *head = NULL; /* node to beginning of list */
if (argc < 2) { /* check that at least 2 arguments available */
fprintf (stderr, "error: insufficient arguments.\n"
"usage: %s filename\n", argv[0]);
return 1;
}
filep = fopen (argv[1], "r"); /* open file given as 1st argument */
if (!filep) { /* validate file is open for reading */
perror ("fopen-filep");
return 1;
}
while (fscanf (filep, "%s", word) != EOF) /* read each word */
addnode (&head, word); /* add to list */
fclose (filep); /* close file, done reading */
prn_list (head); /* print list */
free_list (head); /* free all allocated memory */
}
(注意:,您必须验证每个分配,重新分配和输入,以确保您不会在编写的每个程序中调用 Undefined Behavior ,而不仅仅是列出程序)>
以上原则通常适用于您将要编写的所有链表程序。如上所述,在如何使用正向链接或使用tail
指针按顺序进行链接优化添加节点方面,存在各种变化,但是要添加的信息的基本传递和无论如何,节点的分配和数据的存储将基本上相同。
示例输入文件
$ cat ../dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
使用/输出示例
$ ./bin/ll_words ../dat/captnjack.txt
linked list:
This
is
a
tale
Of
Captain
Jack
Sparrow
A
Pirate
So
Brave
On
the
Seven
Seas.
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时可以释放。
当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。
对于Linux,valgrind
是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。
$ valgrind ./bin/ll_words ../dat/captnjack.txt
==16920== Memcheck, a memory error detector
==16920== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==16920== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==16920== Command: ./bin/ll_words ../dat/captnjack.txt
==16920==
linked list:
This
is
a
tale
Of
Captain
Jack
Sparrow
A
Pirate
So
Brave
On
the
Seven
Seas.
==16920==
==16920== HEAP SUMMARY:
==16920== in use at exit: 0 bytes in 0 blocks
==16920== total heap usage: 33 allocs, 33 frees, 884 bytes allocated
==16920==
==16920== All heap blocks were freed -- no leaks are possible
==16920==
==16920== For counts of detected and suppressed errors, rerun with: -v
==16920== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认已释放已分配的所有内存,并且没有内存错误。
仔细检查一下,如果还有其他问题,请告诉我。
答案 1 :(得分:0)
您似乎并没有始终使用指针。您可能需要刷新指针和值之间的差异。您应该自己进行锻炼,以确保您了解概念。
针对此特定问题:
1)首先更改createNode以创建一个新节点并初始化数据:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:local="clr-namespace:GasStations"
x:Class="GasStations.MainPage">
</ContentPage>
(注意,我们返回了指针。此外,我们还分配了存储数据的空间。)
2)另外,更改添加以接受指针并返回指针
Waze
请注意,上面的函数可以处理空列表,而不能处理空列表,因此您的主体不必这样做。
3)在您的主要帐户中:
Node * createNode(char *data) {
Node *node = (Node *)malloc(sizeof(struct Node));
node->next = NULL;
node->data = (char *)malloc(strlen(data));
strcpy(node->data, data);
return node;
}