C - 链表 - 如何分配和遍历列表

Posted

技术标签:

【中文标题】C - 链表 - 如何分配和遍历列表【英文标题】:C - Linked List - how to assign and go through the list 【发布时间】:2019-02-24 02:01:26 【问题描述】:

我在使用两个结构构建链表时遇到问题 节点 - 包含数据和指向下一个的指针,列表包含指向列表头的指针。

我设法只用节点结构来实现它。

我已经在主函数中初始化了一个列表结构 比使用 malloc 为列表结构分配的内存 比我为 head 分配的内存是指向第一个节点的指针

将它发送到另一个函数,在那里输入,分配,分配, 但是我很难理解如何在不更改指向头部的指针的情况下遍历列表。

在我完成节点和分配之后如何获取头指针 指向列表的开头。

我应该使用副本吗? (节点 *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"")\n\n");
    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);
    

【问题讨论】:

如果您真的不知道从哪里开始或需要指导和支持,请考虑指导或指导尝试Codementor、Savvy、Hackhands 或 airpair 等服务。 没有必要用只有一个成员的typedef struct 使其复杂化,因为列表头可以(并且通常是)指向node 类型的简单指针。 旁注:那些双引号将这 3 个字符串连接在一起(我认为这不是你的意图)。如果要显示报价,请转义:printf("please enter the numbers:\n(to stop enter \"-1\")\n\n"); 虽然不是必需的,但我通常发现在 typedef 命名空间名称中添加 _t 后缀会很有帮助,例如typedef struct node int data; struct *node next; node_t; 至少在我看来,struct nodenode_t 类型之间有明显的区别。您还需要访问C11 Standard §5.1.2.2.1 Program startup p1 (draft n1570)。另见:See What should main() return in C and C++? 【参考方案1】:

有很多方法可以创建一个链表,从简单到令人麻木的 add-at-head(以相反的顺序结束)到一个相当标准的 add-at-tail 遍历节点以找到结束节点,并在那里添加新节点。在所有情况下,只需正确处理指针、分配存储空间(为列表父结构和每个节点)并验证所有分配,然后自行清理并在不再使用时释放已使用的内存需要。

嵌套结构,其中您有一个包含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-&gt;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 函数创建列表,然后添加节点。这一选择具有重要意义。由于我将处理列表不存在的情况,这意味着列表地址可能会在函数内更改。为了处理这种可能性,我必须将列表的 address-of 作为参数传递(例如 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(&amp;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 */

(请注意使用forwhile 循环遍历节点的两个不同示例)

将所有部分放在一起,添加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 nodes\n\n", 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)

始终确认您已释放已分配的所有内存并且没有内存错误。

链表有各种不同的实现方式。你应该知道几个基本的区别。您有使用虚拟节点的列表(例如,虚拟 headtail 节点不包含 data,但仅指向列表中的第一个/最后一个节点)。您有 circular-linked-lists,其中最后一个节点指向第一个节点(这允许从列表中的任何节点迭代到 circular中列表中的任何节点> 时尚贯穿起点和终点)。因此,当您查看“链表”代码时,要了解实现列表的方式有很多种,各有优缺点,因此您只需匹配您的列表即可。

最后,正如评论中所指定的,您的声明 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[] 编写)。 注意: maintype int 的函数,它返回一个值。请参阅:C11 Standard §5.1.2.2.1 Program startup p1 (draft n1570)。另见:See What should main() return in C and C++?

(注意:有一些嵌入式系统继续使用void main(),但这些是例外情况,而不是规则,并且不符合 C 标准)

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

【参考方案2】:

但我很难理解如何在不更改指向头部的指针的情况下遍历列表

头本身将是指向第一个节点的指针

在我完成节点和分配之后,如何让头指针指向列表的开头。

您使新节点指向第一个节点,然后将指向第一个节点的指针移动,即 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-&gt;[data||NULL]

二:temp-&gt;[data||Pointer pointing to 1st node]-&gt;(head)[data||NULL]

移动头使其指向新的第一个节点

第三:head-&gt;[data||Pointer pointing to Previous 1st node]-&gt;[data||NULL]

无正当理由投反对票不是懦弱的行为吗?

【讨论】:

你把它弄得太复杂了。 head == NULL 时无需编写不同的代码。只需要else 代码块。 @WeatherVane 不会让它变得简单,并且将其全部保存在一个循环中吗? 抱歉,代码也没有使用typedef 代替node 感谢大家的快速回答,我现在明白了“Observer”确实帮助我提供了详细的代码,我查看并实现了它! :) @idanbar 那么你想这样做:***.com/help/someone-answers

以上是关于C - 链表 - 如何分配和遍历列表的主要内容,如果未能解决你的问题,请参考以下文章

链表遍历无限

如何将数据动态存储在C中的链表中?

动态分配的指向结构(链表)的指针数组,以及如何访问每个列表?

C 编程为链表类型结构分配空间

JS中的广度与深度优先遍历

C即使分配了内存,修改链表的程序也会导致分段错误