为什么名为 "traverse" 的函数在我的代码中不起作用?

Why does the function named "traverse" not work on my code?

在下面的例子中,我创建了一个链表,我可以成功添加数字。然而,在 执行结束,名为“遍历”的函数不起作用。我该如何解决这个错误?

这是我的代码:

 #include<stdio.h>
 #include<stdlib.h>
 #include<conio.h>
 
 struct node
{
 int data;
 struct node*prev;
 struct node*next;
};

  void add( node*head,int number )
{
node*ptr = NULL;

if( head == NULL )
{
  head = (node*)malloc(sizeof(node));
  head->data = number;
  head->next = NULL;
  head->prev = NULL;
  ptr = head;
}

     else
   {
     ptr->next = (node*)malloc(sizeof(node));
     ptr->next->prev = ptr;
     ptr = ptr->next;
     ptr->data = number;
     ptr->next = NULL;
   }
} 
  
 void traverse( node* head )
{
   while( head != NULL )
  {
    printf("%d ",head->data);
    head = head->next;
   }
} 

int main( void )
{
 node *head = NULL;
 int number;
 char response;

  printf("%s\n","Do you want to enter a number in linked list(y/n)?" );
  scanf("%c",&response);
   while( response == 'y' || response == 'Y' )
  {
   printf("\nEnter num..> ");
   scanf("%d",&number);

   add(head,number);

   printf("%s\n","Do you want to continue(y/n)?" );
   response = getche();
  }
   
      printf("\nYour doubly linked list\n");
      traverse(head);   

      getch();
      return 0;
   }
 

调用“traverse”时,控制台打印space如下图。

如果您决定使用 C,那么从注释继续,您正在尝试更新 add() 中指针 head 的本地副本。如前所述,您有两个选择,要么将 add() 的 return 类型更改为 node *add(),这样您就可以 return ptr 并分配为新的 head返回main(),或者将head的地址作为第一个参数传入,更新add().

中原指针地址存储的节点

您可以将head的地址传给add(),如下:

void add (node **head, int number)
{
    node *ptr = malloc (sizeof *ptr);
    if (!ptr)
        return;

    ptr->data = number;               /* initialized new node data */
    ptr->prev = ptr->next = NULL;     /* initialized both pointers NULL */

    if ( *head != NULL ) {            /* if not 1st node */
        (*head)->prev = ptr;          /* Forward-Chain new node */
        ptr->next = *head;
    }

    *head = ptr;                      /* set head = new node */
}

(注意: 因为你把head的地址作为参数传递,你必须从[=中的指向指针的指针中去掉一层间接18=] 通过取消引用 head(例如 *head)以更新原始指针地址处的节点。当使用 [=33 进一步取消引用指针时,您还需要使用 (*head) =] 由于 C operator precedence -- 所以你在应用 -> 之前得到原始指针地址)

请注意,add() 函数使用方法调用 Forward-Chaining 在 O(1) 时间内将每个节点添加到列表中。这也意味着该列表将以与输入相反的顺序保存数字(最后一个)。您有两个选项可以按顺序插入,(1) 每次迭代到列表的末尾并添加一个新的结束节点(对于大型列表来说效率非常低,不再是 O(1) 时间,或者 (2) 使用另一个 tail 始终指向最后一个节点的指针,以允许在 O(1) 时间内按顺序插入。

然后您可以使用

main() 中调用您的 add() 函数
    add (&head, number);

测试列表实现时,不要让自己为难。没有理由必须在添加到列表中的每个数字之前输入 'y' 然后输入数字并再次输入 'y' (这会让我发疯......)。只需通过循环将数字添加到您的列表中,您可以稍后进行输入,例如

int main (void)
{
    node *head = NULL;                /* list pointer initialized NULL */

    for (int i = 0; i < 20; i++)      /* just add 20 nodes to list */
        add (&head, i + 1);

    traverse (head);
    delete_list (head);
    head = NULL;

/* hold terminal open on windows only */
#if defined (_WIN32) || defined (_WIN64)
    getchar();
#endif
}

(注意: conio.h 已被删除,getchar() 用于在 windows 上保持终端打开。因为我在Linux,最终的 getchar() 没有编译为我的可执行文件的一部分)

您的 traverse() 函数将起作用,但要养成使用单独的单独指针迭代列表的习惯。这并不总是必需的,并且在 traverse() 中也不需要,因为您可以使用 head 的本地副本,但始终使用临时指针进行迭代并留下原始 head 地址,如果您需要它以便稍后在您的函数中使用,例如

void traverse (const node *head)
{
    const node *iter = head;          /* optional, but good practice */

    while (iter) {
        printf ("%d ", iter->data);
        iter = iter->next;
    }
    putchar ('\n');
}

另请注意 delete_list() 功能添加到 free() 为您的列表添加的所有内存。您不会总是在 main() 中声明列表,其中内存在退出时被释放。养成跟踪您分配的内存并在指针超出范围之前释放内存的习惯(否则,您将造成内存泄漏)

完整的程序是:

#include <stdio.h>
#include <stdlib.h>

typedef struct node {
    int data;
    struct node *prev, *next;
} node;

void add (node **head, int number)
{
    node *ptr = malloc (sizeof *ptr);
    if (!ptr)
        return;

    ptr->data = number;               /* initialized new node data */
    ptr->prev = ptr->next = NULL;     /* initialized both pointers NULL */

    if ( *head != NULL ) {            /* if not 1st node */
        (*head)->prev = ptr;          /* Forward-Chain new node */
        ptr->next = *head;
    }

    *head = ptr;                      /* set head = new node */
}

 void traverse (const node *head)
{
    const node *iter = head;          /* optional, but good practice */

    while (iter) {
        printf ("%d ", iter->data);
        iter = iter->next;
    }
    putchar ('\n');
}

void delete_list (node *head)
{
    node *iter = head;

    while (iter) {
        node *victim = iter;
        iter = iter->next;
        free (victim);
    }
}

int main (void)
{
    node *head = NULL;                /* list pointer initialized NULL */

    for (int i = 0; i < 20; i++)      /* just add 20 nodes to list */
        add (&head, i + 1);

    traverse (head);
    delete_list (head);
    head = NULL;

/* hold terminal open on windows only */
#if defined (_WIN32) || defined (_WIN64)
    getchar();
#endif
}

示例Use/Output

$ ./bin/llmess
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

内存Use/Error检查

在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指向内存块的起始地址,因此,(2) 当不再需要时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入 beyond/outside 您分配的块的边界,尝试读取或基于未初始化的条件跳转值,最后,确认您释放了所有已分配的内存。

对于Linux valgrind是正常的选择。每个平台都有类似的内存检查器。它们都很简单易用,只需运行你的程序就可以了。

$ valgrind ./bin/llmess
==16661== Memcheck, a memory error detector
==16661== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==16661== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==16661== Command: ./bin/llmess
==16661==
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
==16661==
==16661== HEAP SUMMARY:
==16661==     in use at exit: 0 bytes in 0 blocks
==16661==   total heap usage: 21 allocs, 21 frees, 1,504 bytes allocated
==16661==
==16661== All heap blocks were freed -- no leaks are possible
==16661==
==16661== For counts of detected and suppressed errors, rerun with: -v
==16661== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

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