我目前正在尝试制作一个程序,在其中我可以输入信息(在这种情况下,信息将是名称/字符串),并将所述信息动态存储在循环链接列表中。我想删除已经创建的节点,但是现在我只是停留在最初创建节点的过程中,之后再进行。我对此还比较陌生,因此概念对我来说有点抽象。我基本上在网上看到了一些代码,这些代码在我想做的事情后面都具有相同的概念,并试图弄清楚每个句子的作用,以便我更好地理解如何实现此目的,但是这样做仍然会出错 编译后一直出现的错误是“结构节点没有名为子级的成员”。但是据我所知
这是下面的代码
#include <stdio.h>
#include <stdlib.h>
#define SIZE 20 //char array size for names
struct node
{
char players[SIZE];
struct node *next;
}*firstnode;
void createList(int amount);
void displayList();
int main()
{
char children[SIZE];
int amount; //stores the number of children that will be playing
firstnode = NULL;
printf(" Welcome To The Count Out Game!\n"); //Header
printf("------------------------------------\n\n"); //Header
printf("How Many Children Will Be Playing? : ");
scanf("%d", &amount);
void createList(int amount)
{
int i;
char children [SIZE];
struct node *prevNode, *newNode;
if(amount>=1)
{
firstnode = (struct node *)malloc(sizeof(struct node));
printf("Enter The Name For Child 1: ");
scanf("%s", &children);
firstnode->children = children;
firstnode->next= NULL;
prevNode = firstnode;
for(i=2; i<=amount; i++)
{
newNode = (struct node *)malloc(sizeof(struct node));
printf("Enter the name for child %d", i);
scanf("%s", &children);
newNode->children = children;
newNode->next = NULL;
prevNode->next = newNode;
prevNode = newNode;
prevNode->next = firstnode;
}
}
}
void displayList()
{
struct node *current;
int n = 1;
if(firstnode == NULL)
{
printf("List Is Empty");
}
else
{
current = firstnode;
printf("Names Of Children In The List\n");
do
{
printf("Names %s\n", n, current->firstnode);
current = current->next;
n++;
}
while(current!= firstnode);
}
}
}
答案 0 :(得分:1)
对于初学者来说,处理单数链接(圆形)链表作为链表的介绍,比简单的Head-> Tail列表(其中last->next
指针简单)要花费更多的思考和理解。 NULL
。以非圆形列表为例:
Singly Linked-List (non-circular)
Node1 Node2 Node3
+------------+ +------------+ +------------+
| Payload | | Payload | | Payload |
+------------+ +------------+ +------------+
| Next |------->| Next |------->| Next |--->NULL
+------------+ +------------+ +------------+
在上面,只需 chaining (将next
指针设置为last->next
时,只需通过NULL
指针将节点钩在一起即可。这使得轻松添加到列表中,因为您可以简单地添加新节点作为新的第一个节点,每次更改列表地址,例如
list *newnode = malloc (sizeof *newnode); /* validate, set data values, ... */
newnode->next = list;
list = newnode;
或者您可以简单地迭代while (node->next != NULL)
,然后在末尾添加新节点,例如
node->next = newnode;
newnode->next = NULL;
非循环列表的优点是简单,但缺点是只能从列表的开头到结尾进行迭代。您不能从列表中的任何点从任何节点迭代回到恰好在其之前的节点。 (这对于大型列表可能会带来很大的效率差异)
要解决此问题,圆形列表的last->next
指针指向该列表的开头。有了这一新增功能,您可以从整个列表iter = current;
的{{1}}进行迭代,从而无需从头开始就可以从列表中的任何点迭代到列表中的任何其他点。这只会带来一点额外的复杂性。考虑一下:
while (iter->next != current)
现在,将节点添加到列表中时,有两种特殊情况可以确保您处理。例如,当添加第一个节点时,由于列表是圆形的,因此第一个节点是 self-referential (或自我参考,因为缺少更好的单词),例如
Singly Linked-List (circular)
Node1 Node2 Node3
+------------+ +------------+ +------------+
| Payload | | Payload | | Payload |
+------------+ +------------+ +------------+
+--->| Next |------->| Next |------->| Next |--->+
| +------------+ +------------+ +------------+ |
| |
+<--------------------<---------------------<----------------------+
这不会增加很多复杂性,但是需要您对如何将节点添加到列表中并确保Singly Linked-List (circular) - 1st Node is Self-Referencial
Node1
+------------+
| Payload |
+------------+
+--->| Next |--->+
| +------------+ |
| |
+<---------------------+
指针始终指向列表的开头进行更深入的思考。 。在迭代列表时,还需要多加注意,因为要迭代直到last->next
指针等于起点(通常是列表地址,但可以是任何节点)。
从循环列表开始学习列表是可以的,但是您必须始终保持指针笔直。最好的方法是简单地拔出铅笔并画出您正在处理的节点(就像我用作图表的方框一样),并且当您需要添加节点时,请确保重新连接所有指针在列表中插入新节点时正确地进行。从列表中删除节点的方法相同。用橡皮擦清除它,然后对指针的重新布线进行编码,以再次将您的列表缝在一起。
您的代码通过不使用描述性的变量名称(至少混合了 children 和 players 和 child ,使事情变得比原来更难了),等等...在我的脑海中无法正常运行)current->next
中的每个节点将容纳一个struct
,而不是多个player
。保持变量名称的单数和复数形式很长,这有助于使逻辑保持直线。重命名,例如
children
(注意:简单地将#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXNM 32 /* avoid generic defines like SIZE (maxname?) */
typedef struct _node {
char player[MAXNM];
struct _node *next;
} node;
设为node
也有帮助,无需创建指向node
的全局指针,只需创建一个方便的firstnode
以便在您的代码中使用。)
然后在typedef
中,您将同样使用名为main()
的缓冲区来保存从用户那里读取的输入,例如
player
仅需注意一点,您不需要多个int main (void) {
char player[MAXNM] = "";
int nplayers = 0;
node *list = NULL;
语句即可输出多行文本或使文本不缠绕在页面的一侧。在C中,带引号的字符串是连接在一起的。此外,虽然您的编译器可能会将更改作为自动优化进行,但是如果您的字符串中没有转换说明符,则最好使用printf
或puts
并避免除非需要,否则调用 variadic函数。例如,
fputs
(为什么在这里使用 fputs ( " Welcome To The Count Out Game!\n"
"------------------------------------\n\n"
"How Many Children Will Be Playing? : ", stdout );
而不是fputs
?-弄清楚这是一件好事...)
接下来,您必须 验证 所有用户输入并处理出现的任何错误。否则,您会误入未定义行为处理垃圾并不确定值,直到程序中发生非常糟糕的事情。虽然最好使用puts
,然后调用fgets
来解析值(或sscanf
等。),但如果您的< strong> 检查退货 。这样,您可以验证至少发生了预期的转化次数,并且变量中输入了有效的信息:
strtol
但是使用scanf
的陷阱在于它将尾随 if (scanf ("%d", &nplayers) != 1 || nplayers < 1) {
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
(由用户按下 Enter 生成)留在输入缓冲区中,并且您必须在处理之前接受scanf
之外的其他'\n'
或其他非数字或其他转换说明符的输入(它本身添加了其他一系列问题,这些问题是由于它在第一个空白处停止读取而造成的,因此,如果存在空格后的其他/意外字符,您有麻烦)
因此,您可以删除保留在输入缓冲区中的所有其他字符(此处为fgets
)。您可以这样做,就可以简单地循环并"%s"
(如果从另一个打开的文件流中读取,则可以stdin
),例如
getchar()
这将我们带入您的播放器名称输入循环,您将在其中呼叫fgetc()
(或您的 /* remove any additional characters from stdin */
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
)以开始将节点添加到列表中(并显示insertnode
的完成情况) )
createList
请注意上面调用main()
的地方。您正在将列表的地址传递给插入函数。您这样做是为了如果列表地址(即第一个节点)发生更改,则新的列表地址将在调用函数中可用。如果您无法传递列表指针的地址,那么您将需要从函数中返回列表地址,并在每次调用函数中将其分配回来。
您还需要使用有意义的返回类型声明您的函数,该类型可以指示插入操作的成功/失败。只需将指针返回成功时插入的节点便很方便,或者如果失败则返回 /* prompt for player and insert node in list */
while (nplayers-- &&
fputs ("name: ", stdout) != EOF &&
fgets (player, MAXNM, stdin)) {
player[strcspn (player, "\n")] = 0; /* trim '\n' from end */
if (!insertnode (&list, player))
break;
}
displaylist (list); /* output all players in list */
freelist (list); /* free list memory */
return 0;
}
即可。
在插入函数中,除了验证insertnode (&list, player)
不是NULL
之外,还需要确定列表是否存在,如果不存在,则将新节点添加为第一个节点(设置{{1 }}指向自身的指针),或者-您需要迭代到列表的末尾并在其中插入新节点。每次确保player
指针都指向列表地址。一个简单的实现是:
NULL
对于您要为列表编写的其他任何功能,只需拉出铅笔并计算出如何遍历列表以从列表中获取所需数据即可。例如,您可以提供打印列表或next
函数以及释放与该列表关联的内存的函数。
请注意如何处理迭代的精妙之处(在next
函数开始删除第二个节点的情况下,以确保node *insertnode (node **list, char *player)
{
/* validate player not NULL, handle error */
if (!player)
return NULL;
/* allocate/validate new node */
node *newnode = malloc (sizeof *newnode);
if (!newnode) {
perror ("malloc-newnode");
return NULL;
}
/* copy player to node, initialize next NULL */
strcpy (newnode->player, player);
newnode->next = NULL;
/* check whether list exists, or is this 1st node? */
if (!*list) {
newnode->next = newnode; /* circular list is self-referencial */
*list = newnode;
}
else { /* list exist, find last node in circular list */
node *iter = *list; /* create pointer to iterate to end */
for (; iter->next != *list; iter = iter->next) {} /* iterate */
iter->next = newnode; /* insert as last node */
newnode->next = *list; /* circular, set next to *list */
}
return newnode; /* return node as convenience & success/failure */
}
指针引用一个有效地址来指示结束)您的迭代),例如
displaylist()
将其完全放在一起,您将获得以下内容:
freelist
使用/输出示例
使用它的一个简短示例是:
last->next
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时可以释放。
当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。
对于Linux,void displaylist (node *list)
{
node *iter = list;
/* iterate from first to last node, setting iter NULL after last */
for (; iter; iter = (iter->next != list ? iter->next : NULL))
puts (iter->player);
}
void freelist (node *list)
{
node *victim = list->next, /* free 2nd node 1st, leaving valid 1st */
*next = NULL;
while (victim != list) { /* while victim isn't list address */
next = victim->next; /* save next address before free */
free (victim); /* free victim */
victim = next; /* assign next to victim */
}
free (victim); /* free 1st node */
}
是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXNM 32 /* avoid generic defines like SIZE (maxname?) */
typedef struct _node {
char player[MAXNM];
struct _node *next;
} node;
node *insertnode (node **list, char *player);
void displaylist (node *list);
void freelist (node *list);
int main (void) {
char player[MAXNM] = "";
int nplayers = 0;
node *list = NULL;
fputs ( " Welcome To The Count Out Game!\n"
"------------------------------------\n\n"
"How Many Children Will Be Playing? : ", stdout );
if (scanf ("%d", &nplayers) != 1 || nplayers < 1) {
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
/* remove any additional characters from stdin */
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
/* prompt for player and insert node in list */
while (nplayers-- &&
fputs ("name: ", stdout) != EOF &&
fgets (player, MAXNM, stdin)) {
player[strcspn (player, "\n")] = 0; /* trim '\n' from end */
if (!insertnode (&list, player))
break;
}
displaylist (list); /* output all players in list */
freelist (list); /* free list memory */
return 0;
}
node *insertnode (node **list, char *player)
{
/* validate player not NULL, handle error */
if (!player)
return NULL;
/* allocate/validate new node */
node *newnode = malloc (sizeof *newnode);
if (!newnode) {
perror ("malloc-newnode");
return NULL;
}
/* copy player to node, initialize next NULL */
strcpy (newnode->player, player);
newnode->next = NULL;
/* check whether list exists, or is this 1st node? */
if (!*list) {
newnode->next = newnode; /* circular list is self-referencial */
*list = newnode;
}
else { /* list exist, find last node in circular list */
node *iter = *list; /* create pointer to iterate to end */
for (; iter->next != *list; iter = iter->next) {} /* iterate */
iter->next = newnode; /* insert as last node */
newnode->next = *list; /* circular, set next to *list */
}
return newnode; /* return node as convenience & success/failure */
}
void displaylist (node *list)
{
node *iter = list;
/* iterate from first to last node, setting iter NULL after last */
for (; iter; iter = (iter->next != list ? iter->next : NULL))
puts (iter->player);
}
void freelist (node *list)
{
node *victim = list->next, /* free 2nd node 1st, leaving valid 1st */
*next = NULL;
while (victim != list) { /* while victim isn't list address */
next = victim->next; /* save next address before free */
free (victim); /* free victim */
victim = next; /* assign next to victim */
}
free (victim); /* free 1st node */
}
始终确认已释放已分配的所有内存,并且没有内存错误。
现在,此操作的完成时间比预期的要长得多,但是很显然,您在理解和处理单链接-循环链接列表时大失所望。希望这会给您一个基本的了解,并让您从这里继续前进。