我在使用两个结构构建链表时遇到了问题node-包含数据和指向下一个的指针,list包含指向列表头的指针。
我设法只用nodestruct实现了它。
我已经在主函数中初始化了一个列表的结构使用malloc为列表结构分配的内存比i为作为指向第一节点的指针的头分配的存储器
将其发送到另一个函数,在那里进行输入、分配、分配,但我很难理解如何在不改变指针指向头部的情况下浏览列表。
在我完成节点和分配之后,如何获得指向指向列表的开头。
我应该使用复印件吗?(节点*temp)??
谢谢大家!
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
typedef struct node
{
int data;
struct node *next;
}node;
typedef struct list
{
struct node *head;
}list;
void main()
{
list *list_a;
list_a = (list*)malloc(sizeof(list));
list_a->head = (node*)malloc(sizeof(node));
assignment(list_a);
}
void assignment(list *list_a)
{
int data;
printf("please enter the numbers:n(to stop enter ""-1"")nn");
scanf("%d", &data);
while (data != -1)
{
list_a->head->data = data;
list_a->head->next = (node*)malloc(sizeof(node));
list_a->head = list_a->head->next;
printf("enter a number:n");
scanf("%d", &data);
}
}
有很多方法可以创建链表,从简单得令人麻木的在头添加(以相反的顺序结束列表)到相当标准的在尾中添加,在那里迭代节点以找到结束节点,并在那里添加新节点。在任何情况下,都只需要正确处理指针,分配存储(用于列表父结构和每个节点)并验证所有分配,然后自行清理并在不再需要时释放已用内存。
嵌套结构中有一个包含head
节点的结构(希望还有其他有用的数据来证明嵌套方法的合理性)是很常见的,但列表本身不需要父结构。列表地址只是第一个节点的地址。
在学习列表时,将管理列表的任务分解为简单的单独功能确实很有帮助。这使您能够集中精力(稍微容易一点)单独处理每个列表操作。例如,对于您的列表,您需要:
- 为每个节点创建/分配,初始化
next
指针NULL
并设置data
值 - 为您的列表创建/分配,为
head
分配,并初始化列表结构中包含的任何附加信息 - 将节点添加到您的列表中,根据需要创建列表,并将节点设置数据添加到适当的值,并更新所需的任何列表信息
- 从你的列表中获取数据;以及
- 最后在不再需要时为节点和列表释放内存
在您的情况下,从我对您的问题的评论开始,您可以声明类似于以下的结构和typedef:
typedef struct node {
int data;
struct node *next;
} node_t;
typedef struct list {
struct node *head;
size_t n; /* at least make parent hold list size */
} list_t;
在这里,我们只是添加了一个计数器来跟踪列表中的节点数量,作为一个额外的、有用的数据来证明外部结构的合理性。它为您提供了节点计数,而无需每次迭代列表即可获得(如果您需要这些数据,这只是一个小的效率改进)。您可以使用简单的list->n
获得列表中的节点数。
根据我们的列表大纲,您需要一种为列表创建节点的方法。无论是第一个节点,还是最后一个节点,您都不在乎。当您需要一个节点时,create_node
函数应该处理分配/验证和初始化。不需要任何花哨的东西,例如
/** function to create node and set data value to data.
* returns new node on success, NULL otherwise.
*/
node_t *create_node (int data)
{
node_t *newnode = malloc (sizeof *newnode); /* allocate */
if (newnode == NULL) { /* validate/handle error */
perror ("create_node() malloc-newnode");
return NULL;
}
newnode->data = data; /* initialize members */
newnode->next = NULL;
return newnode; /* return pointer to new node */
}
create_list
函数只需要为list
结构进行分配(我还让它分配第一个节点并初始化值和指针0/NULL
)。你可以让它做任何你喜欢的事情,例如为第一个节点添加另一个传递data
的参数,等等。我只需要让它创建列表和第一个节点。
/** function to create list and allocates/initilizes head, set list->n = 0.
* returns new list on success, NULL otherwise.
*/
list_t *create_list (void)
{
node_t *head = NULL;
list_t *list = malloc (sizeof *list); /* allocate list */
if (!list) { /* validate/handle error */
perror ("create_list() malloc-list");
return NULL;
}
head = create_node (0); /* create the first node */
if (!head) /* validate/handle error */
return NULL;
list->head = head; /* initialize list values */
list->n = 0;
return list; /* return list */
}
您的add_node
函数可能相当简单,但出于这里的目的,我们将让add_node
函数在列表不存在的情况下创建列表,然后添加节点,而不仅仅是在列表尚未分配时停止。这一选择具有重要意义。由于我将处理列表不存在的情况,这意味着列表地址可能会在函数中更改。为了处理这种可能性,我必须将列表的地址作为参数传递(例如list_t **list
,而不是简单的list_t *list
)。通过拥有指针的实际地址,我可以更改原始指针指向的位置,并且这种更改将在调用函数中可见(而不是更改指针副本指向的位置(在调用方中看不到)。
该函数需要处理两种情况(1)"我是第一个节点吗?"和(2)"如果我不是第一个节点,那么迭代到结束并添加到那里"。你可以做类似的事情:
/** add node to list, create list if list NULL, set node->data to data.
* return new node on success, NULL otherwise.
*/
node_t *add_node (list_t **list, int data)
{
node_t *node;
if (!*list) { /* handle list doesn't exist */
*list = create_list();
if (!*list)
return NULL;
node = (*list)->head; /* (..)-> required by operator precedence */
node->data = data;
}
else { /* list already exists */
node = (*list)->head; /* set node to list->head */
/* iterate over nodes to find last and add node at end */
while (node->next)
node = node->next;
node->next = create_node (data); /* allocate next node */
node = node->next; /* change to new node */
}
(*list)->n++; /* increment number of nodes in list */
return node; /* return node */
}
通过这种方式,我可以简单地在main()
中声明指针,并将其初始化为NULL
,然后在main()
中简单地调用add_node(&list, x)
,让列表函数处理指针和分配。
您的附加列表函数只是对列表中的每个节点进行迭代的函数,对信息执行一些操作,如打印列表或释放列表中的所有节点。(注意如何在free_list
函数中处理要释放的节点(例如victim
))
/** print the value of each node in list */
void prn_list (const list_t *list)
{
/* iterate over list printing data value */
for (node_t *node = list->head; node; node = node->next)
printf (" %d", node->data);
putchar ('n'); /* tidy up with newline */
}
/** free all nodes in list and free list */
void free_list (list_t *list)
{
node_t *node = list->head; /* set node to head */
while (node) { /* iterate over each nod */
node_t *victim = node; /* setting victim to free */
node = node->next; /* change to next node */
free (victim); /* free victim */
}
free (list); /* free list */
}
(注意使用for
或while
循环在节点上迭代的两个不同示例)
将所有部分放在一起,添加25
节点,并在释放与列表相关的所有内存之前将其打印出来,您可以执行以下操作:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#if ! defined (_WIN32) && ! defined (_WIN64)
#include <stdlib.h> /* Linux has malloc/free in the stdlib header */
#endif
typedef struct node {
int data;
struct node *next;
} node_t;
typedef struct list {
struct node *head;
size_t n; /* at least make parent hold list size */
} list_t;
/** function to create node and set data value to data.
* returns new node on success, NULL otherwise.
*/
node_t *create_node (int data)
{
node_t *newnode = malloc (sizeof *newnode); /* allocate */
if (newnode == NULL) { /* validate/handle error */
perror ("create_node() malloc-newnode");
return NULL;
}
newnode->data = data; /* initialize members */
newnode->next = NULL;
return newnode; /* return pointer to new node */
}
/** function to create list and allocates/initilizes head, set list->n = 0.
* returns new list on success, NULL otherwise.
*/
list_t *create_list (void)
{
node_t *head = NULL;
list_t *list = malloc (sizeof *list); /* allocate list */
if (!list) { /* validate/handle error */
perror ("create_list() malloc-list");
return NULL;
}
head = create_node (0); /* create the first node */
if (!head) /* validate/handle error */
return NULL;
list->head = head; /* initialize list values */
list->n = 0;
return list; /* return list */
}
/** add node to list, create list if list NULL, set node->data to data.
* return new node on success, NULL otherwise.
*/
node_t *add_node (list_t **list, int data)
{
node_t *node;
if (!*list) { /* handle list doesn't exist */
*list = create_list();
if (!*list)
return NULL;
node = (*list)->head; /* (..)-> required by operator precedence */
node->data = data;
}
else { /* list already exists */
node = (*list)->head; /* set node to list->head */
/* iterate over nodes to find last and add node at end */
while (node->next)
node = node->next;
node->next = create_node (data); /* allocate next node */
node = node->next; /* change to new node */
}
(*list)->n++; /* increment number of nodes in list */
return node; /* return node */
}
/** print the value of each node in list */
void prn_list (const list_t *list)
{
/* iterate over list printing data value */
for (node_t *node = list->head; node; node = node->next)
printf (" %d", node->data);
putchar ('n'); /* tidy up with newline */
}
/** free all nodes in list and free list */
void free_list (list_t *list)
{
node_t *node = list->head; /* set node to head */
while (node) { /* iterate over each nod */
node_t *victim = node; /* setting victim to free */
node = node->next; /* change to next node */
free (victim); /* free victim */
}
free (list); /* free list */
}
int main (void)
{
list_t *list = NULL; /* just declare list and set pointer NULL */
for (int i = 0; i < 25; i++) /* add 25 nodes to list */
if (add_node (&list, i + 1) == NULL) /* validate each addition */
break;
/* print list content, beginning with number of nodes in list */
printf ("list contains: %lu nodesnn", list->n);
prn_list (list); /* followed by each node value */
free_list (list); /* and then delete list */
return 0;
}
示例使用/输出
$ /bin/llsingle_w_parent
list contains: 25 nodes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有2个责任:(1)始终为内存块保留一个指向起始地址的指针,因此,(2)当不再需要时,它可以被释放。
您必须使用内存错误检查程序来确保您不会试图访问内存或在分配的块的边界之外写入,尝试读取或基于未初始化的值进行条件跳转,最后确认您释放了所有分配的内存。
对于Linux,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序即可
$ valgrind ./bin/llsingle_w_parent
==14749== Memcheck, a memory error detector
==14749== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14749== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==14749== Command: ./bin/llsingle_w_parent
==14749==
list contains: 25 nodes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
==14749==
==14749== HEAP SUMMARY:
==14749== in use at exit: 0 bytes in 0 blocks
==14749== total heap usage: 26 allocs, 26 frees, 416 bytes allocated
==14749==
==14749== All heap blocks were freed -- no leaks are possible
==14749==
==14749== For counts of detected and suppressed errors, rerun with: -v
==14749== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
请始终确认您已经释放了分配的所有内存,并且没有内存错误。
链表有各种不同的实现方式。你应该意识到几个基本的区别。您有使用伪节点的列表(例如,伪head
和tail
节点不包含data
,但仅指向列表中的第一个/最后一个节点)。您有循环链表,其中最后一个节点指向第一个节点(这允许以环形的方式从列表中的任何节点迭代到列表中的任意节点)。因此,当你查看"链表"代码时,要明白列表的实现方式有很多,都有优点和缺点,所以你只需要为这份工作匹配你的列表。
最后,正如注释中所指定的,您的声明void main()
是不正确的,是windows早期(如DOS 3.3和windows 3.1、Trumpet WinSock和Borland Turbo C编译器时代)的古老倒退。main
的正确声明是int main (void)
和int main (int argc, char **argv)
(您将看到用等效的char *argv[]
编写)注意:main
是type int
的函数,它返回一个值。参见:C11标准§5.1.2.2.1程序启动p1(草案n1570)。另请参阅:参见main()在C和C++中应该返回什么?
(注意:有一些嵌入式系统继续使用void main()
,但这些是例外,而不是规则,不符合C标准)
仔细看看,如果你还有问题,请告诉我。
但我很难理解如何在不更改指向头部的指针的情况下浏览列表
Head本身将是指向第一个节点的指针。
以及在我完成节点和分配之后,如何使头指针指向列表的开头。
您使新节点指向第一个节点&然后移动指向第一节点的指针,即头到新添加的节点。
-
重要信息-您缺少
stdlib.h
,没有malloc
就无法使用
这是一个粗略的版本(只是为了理解):
while(/*IF YOU WANT TO ADD NODES?*/)
{
if(head == NULL)
{
head = malloc((sizeof(struct node));
head->data = //USER INPUT;
head->next=NULL;
}
else
{
temp = malloc(sizeof(struct node));
temp->data = //USER INPUT;
temp->next = head;
head=temp;
}
}
这整个过程可以从几个步骤中看出:
第一个:head->[data||NULL]
第二:temp->[data||Pointer pointing to 1st node]->(head)[data||NULL]
移动头使其指向新的第一节点
第三:head->[data||Pointer pointing to Previous 1st node]->[data||NULL]
在没有正当理由的情况下投反对票不是懦弱的行为吗