【问题标题】:Pointer to pointer in linked list指向链表中指针的指针
【发布时间】:2020-08-18 21:10:38
【问题描述】:

谁能解释一下为什么这段代码给了我一个空列表:

typedef struct str_node{
int data;
struct str_node *next;
}node;


void begin(node *head);
void display_list(node *head);


int main(){

node *head;
int i;

head = NULL;

for(i=0;i<5;i++) {
    begin(head);
}
display_list(head);




return 0;
}

void begin(node *head){
node *new;
int value;
new = (node*) malloc(sizeof(node));
printf("Insert the element to add at the beginning of the list: ");
scanf("%d",&value);
new->data = value;
new->next = head;
head = new;
}

但是,如果我用指向指针的指针更改 begin() 函数,它会给我正确的列表吗?

void begin(node **head){
node *new;
int value;
new = (node*) malloc(sizeof(node));
printf("Insert the element to add at the beginning of the list: ");
scanf("%d",&value);
new->data = value;
new->next = *head;
*head = new;
}

你能解释一下为什么当我将主节点头传递给函数开始时,我必须将它作为“&head”传递吗?不再是“头”

【问题讨论】:

  • OT:为了便于阅读和理解;请始终缩进代码。在每个左大括号“{”后缩进。在每个右大括号 '}' 之前取消缩进。建议每个缩进级别为 4 个空格。

标签: c pointers linked-list pass-by-reference function-declaration


【解决方案1】:

在这段代码的第一个程序中sn -p

head = NULL;

for(i=0;i<5;i++) {
    begin(head);
}

指针head按值传递给函数begin。即main中声明的指针head的值的副本被创建并分配给与函数begin同名的参数

void begin(node *head);

因此,在函数中,参数head 最初保存了更改后的原始指针head 的副本。原指针head的值分配给参数没有被改变。

要更改在 main 中声明的原始指针头,您必须通过指向指针头的指针间接通过引用将其传递给函数,就像在第二个程序中所做的那样。

所以函数应该这样声明

void begin(node **head);

你必须通过指向它的指针间接传递指针头

begin( &head );

在这种情况下,取消引用传递的指针,函数将直接访问在 main 中声明的原始指针头并可以更改它(不是它的值的副本,因为它发生在第一个函数定义中)

new->next = *head;
*head = new;

为了更清楚,请考虑这个简单的演示程序。

#include <stdio.h>

typedef int T;

void f( T t )
{
    t = 10;
}

int main(void) 
{
    T t = 0;
    
    printf( "Before calling f t is equal to %d\n", t );
    
    f( t );
    
    printf( "After  calling f t is equal to %d\n", t );

    return 0;
}

它的输出是

Before calling f t is equal to 0
After  calling f t is equal to 0

由于函数 f 处理传递参数值的副本,因此 main 中声明的变量 t 的值没有改变。

所以你需要通过指针引用传递原始变量t

#include <stdio.h>

typedef int T;

void f( T *t )
{
    *t = 10;
}

int main(void) 
{
    T t = 0;
    
    printf( "Before calling f t is equal to %d\n", t );
    
    f( &t );
    
    printf( "After  calling f t is equal to %d\n", t );

    return 0;
}

现在程序输出是

Before calling f t is equal to 0
After  calling f t is equal to 10

在这些演示程序中,名称T 用作int 类型的别名,而在main 中,对象t 具有这种类型。

现在假设名称 T 是类型 int * 的别名。

typedef int * T;

在这种情况下,例如 main 中的声明

T t = NULL;

表示变量t的指针类型为int *。也就是等价于

int * t = NULL;

所以要将它传递给一个必须改变原始变量 t 的函数,我们需要像这样通过引用传递它

f( &t );

这意味着相应的函数应具有声明的参数类型

void f( T *t );

但由于Tint * 的别名,因此这意味着该函数具有int ** 类型的参数。

void f( int * *t );

【讨论】:

    【解决方案2】:

    因为head (实际上)是一个局部变量,所以更改它在函数之外没有任何影响,而更改*head 会更改head 指向的内容,因此会这样做。

    如果您希望函数能够更改int 变量(例如x)中的值,您可以将一个指向x 的指针传递给它,它的类型为int*,然后您将使用&amp;x 获得指向x 的指针。无论x 是什么类型,都一样。

    【讨论】:

      【解决方案3】:

      声明可能会引起一些混乱

          node        *head;
      

      而不是

          node*       head;
      

      你声明headhead 是变量,它是一个指针。它不是一个节点。另请注意,节点不是链表:链表是节点的集合,可能还有其他东西,以便有一个有用的实现。后面会详细介绍。

      事实上你在main() 中声明了head,只是一个node*。节点本身甚至还不存在。您将begin() 声明为

          void begin(node *head);
      

      我想你会更清楚地看到它

          void begin(node*  parameter);
      

      parameternode*

      begin() 中,您将获得指针的副本,更改指针不会更改main() 中的原始指针。 在您的情况下,它将在 main() 中永远指向 NULL

      重要的是指针就像任何变量一样:指针有一个地址。和一个内容。当您按值传递时,就像您所做的那样,begin() 中的指针以 NULL 开头,即来自 main() 的 VALUE。但它们之间的联系在调用中结束:初始值。

      当您将指针传递给begin(),使用运算符'address of'并写入&amp;head时,事情会发生变化:您将使用运算符'*'对其进行更改,这意味着您将更改它指向的地址,所以它会在main() 中改变。由于headnode*,指向它的指针将被声明为node**

      但请考虑使用以下方法更改链表的 begin() 声明:

          node* begin(node* node);
      

      逻辑是插入一个节点可以改变链表的头部,所以你返回新的地址,如

      node* _insert_begin(int value, node* pNode)
      {
          node* new = (node*)malloc(sizeof(node));
          new->data = value;
          new->next = pNode;
          return new;
      }
      

      是一种常用的写法。另一种是使用node**

      按照我这里描述的方式,任何可以改变列表头部的操作都必须

      • 归还新头
      • 接收并更新指向头部指针的指针

      再次查看插入列表开头的这段代码:

      node* _insert_begin(int value, node* pNode)
      {   // insert 'value' at the start of the list
          node* new = (node*)malloc(sizeof(node));
          (*new).data = value;
          new->next = pNode;
          return new;
      }
      

      返回new,您将获得head 更新。你可以写main()

      node* another = NULL;
      display_list(another);
      
      // inserts 5 to 0 at the beginning
      for (int i = 5; i >= 0; i -= 1)
          another = _insert_begin(i, another);
      printf("inserted 5..0 at the beginning\n");
      display_list(another);
      

      注意another = _insert_begin(i, another); 行,您会看到main() 中的指针是如何更新的。

      这是输出

      empty list
      inserted 5..0 at the beginning
             0        1        2        3        4
             5
      list has 6 elements
      

      使用display_list() 的这个实现,每行打印 5 个值:

      int display_list(node* p)
      {
          if (p == NULL)
          {
              printf("empty list\n");
              return 0;
          };
          int count = 0;
          // not empty
          do
          {
              printf("%8d ", p->data);
              count++;
              if (count % 5 == 0) printf("\n");
              p = p->next;
          } while (p != NULL);
          if (count % 5 != 0) printf("\n");
          printf("list has %d elements\n", count);
          return count;
      };
      

      另一个例子:在末尾插入

      注意最后插入也可以改变头部,在列表为空的情况下,所以我们还是需要返回头部地址

      node* _insert_end(int value, node* pNode)
      {   // insert value at the end of the list
          node* new = (node*)malloc(sizeof(node));
          new->data = value;
          new->next = NULL;
          if (pNode == NULL) return new;
          node* p = pNode;
          while (p->next != NULL) p = p->next;
          p->next = new;
          return pNode;
      }
      

      另一种用法:按升序插入

      当然,按升序插入也可以换头,如

      node* _insert_ordered(int value, node* pNode)
      {   // insert value at ascending order in the list
          node* new = (node*)malloc(sizeof(node));
          new->data = value;
          new->next = NULL;
          if (pNode == NULL) return new;
      
          node* p = pNode;
          node* prev = NULL; // previous node: list if forward only
          while (p->next != NULL)
          {
              if (new->data < p->data)
              {
                  // insert before first greater than value
                  if (prev == NULL)
                  {
                      // new head
                      new->next = p;
                      return new;
                  };  // if()
                  prev->next = new;
                  new->next = p;
                  return pNode; // no change in head
              };
              prev = p; p = p->next; // updates pointers
          };  // while()
          // we are at the end: new will be the last?
          if (new->data < p->data)
          {
              if (prev == NULL)
                  pNode = new;
              else
                  prev->next = new;
              new->next = p;
          }
          else
          {
              p->next = new;
          };
          return pNode;
      }   // _insert_ordered()
      

      删除列表

      删除一个列表也应该返回一个node*,以使头指针无效。这是正常的。当您习惯它的机制时,这可以确保不会留下无效的指针。

      请注意,此逻辑是协作的:您必须在每次可以更改头部的调用时重新分配头部指针

      node* delete_list(node* H)
      {
          if (H == NULL) return NULL;
          if (H->next == NULL)
          {   // single node
              free(H);
              return NULL; 
          };
          // more than one node
          do
          {   node* p = H->next;
              free(H);
              H = p;
          } while (H != NULL);
          return NULL;
      };
      

      一个正在运行的程序

      示例程序的输出

      empty list
      inserted 5..0 at the beginning
             0        1        2        3        4
             5
      list has 6 elements
      inserted 6 to 10 at the end
             0        1        2        3        4
             5        6        7        8        9
            10
      list has 11 elements
      inserted 0 to 10, ordered
             0        0        1        1        2
             2        3        3        4        4
             5        5        6        6        7
             7        8        8        9        9
            10       10
      list has 22 elements
      inserted -1 to -10, ordered
           -10       -9       -8       -7       -6
            -5       -4       -3       -2       -1
             0        0        1        1        2
             2        3        3        4        4
             5        5        6        6        7
             7        8        8        9        9
            10       10
      list has 32 elements
      inserted 11 to 20, ordered
           -10       -9       -8       -7       -6
            -5       -4       -3       -2       -1
             0        0        1        1        2
             2        3        3        4        4
             5        5        6        6        7
             7        8        8        9        9
            10       10       11       12       13
            14       15       16       17       18
            19       20
      list has 42 elements
      about to delete list
      empty list
      

      示例 C 程序

      #include <stdio.h>
      #include <stdlib.h>
      
      typedef struct str_node
      {
          int             data;
          struct str_node* next;
      }   node;
      
      void    begin(node* pNode);
      node*   delete_list(node*);
      int     display_list(node*);
      node*   _insert_begin(int, node*);
      node*   _insert_end(int, node*);
      node*   _insert_ordered(int, node*);
      
      int main()
      {
          node* another = NULL;
          display_list(another);
      
          // insert 5 to 0 at the beginning
          for (int i = 5; i >= 0; i -= 1)
              another = _insert_begin(i, another);
          printf("inserted 5..0 at the beginning\n");
          display_list(another);
      
          // insert 6 to 10 at the end
          for (int i = 6; i <= 10; i += 1)
              another = _insert_end(i, another);
          printf("inserted 6 to 10 at the end\n");
          display_list(another);
      
          // insert 0 to 10 ordered
          for (int i = 0; i <=10; i += 1)
              another = _insert_ordered(i, another);
          printf("inserted 0 to 10, ordered\n");
          display_list(another);
      
          // insert -1 to -10 ordered
          for (int i = -1; i >= -10; i -= 1)
              another = _insert_ordered(i, another);
          printf("inserted -1 to -10, ordered\n");
          display_list(another);
      
          // insert 11 to 20 ordered
          for (int i = 11; i <= 20; i += 1)
              another = _insert_ordered(i, another);
          printf("inserted 11 to 20, ordered\n");
          display_list(another);
      
          printf("about to delete list\n");
          another = delete_list(another);
          display_list(another);
          return 0;
      }
      
      node* delete_list(node* H)
      {
          if (H == NULL) return NULL;
          if (H->next == NULL)
          {   // single node
              free(H);
              return NULL; 
          };
          // more than one node
          do
          {   node* p = H->next;
              free(H);
              H = p;
          } while (H != NULL);
          return NULL;
      };
      
      node* _insert_begin(int value, node* pNode)
      {   // insert 'value' at the start of the list
          node* new = (node*)malloc(sizeof(node));
          (*new).data = value;
          new->next = pNode;
          return new;
      }
      
      node* _insert_end(int value, node* pNode)
      {   // insert value at the end of the list
          node* new = (node*)malloc(sizeof(node));
          new->data = value;
          new->next = NULL;
          if (pNode == NULL) return new;
          node* p = pNode;
          while (p->next != NULL) p = p->next;
          p->next = new;
          return pNode;
      }
      
      node* _insert_ordered(int value, node* pNode)
      {   // insert value at ascending order in the list
          node* new = (node*)malloc(sizeof(node));
          new->data = value;
          new->next = NULL;
          if (pNode == NULL) return new;
      
          node* p = pNode;
          node* prev = NULL; // previous node: list if forward only
          while (p->next != NULL)
          {
              if (new->data < p->data)
              {
                  // insert before first greater than value
                  if (prev == NULL)
                  {
                      // new head
                      new->next = p;
                      return new;
                  };  // if()
                  prev->next = new;
                  new->next = p;
                  return pNode; // no change in head
              };
              prev = p; p = p->next; // updates pointers
          };  // while()
          // we are at the end: new will be the last?
          if (new->data < p->data)
          {
              if (prev == NULL)
                  pNode = new;
              else
                  prev->next = new;
              new->next = p;
          }
          else
          {
              p->next = new;
          };
          return pNode;
      }   // _insert_ordered()
      
      int display_list(node* p)
      {
          if (p == NULL)
          {
              printf("empty list\n");
              return 0;
          };
          int count = 0;
          // not empty
          do
          {
              printf("%8d ", p->data);
              count++;
              if (count % 5 == 0) printf("\n");
              p = p->next;
          } while (p != NULL);
          if (count % 5 != 0) printf("\n");
          printf("list has %d elements\n", count);
          return count;
      };
      

      可以说是更有用的链表结构

      考虑以下

      struct no
      {
          void*       item;
          struct no*  next;
          struct no*  prev;
      };  // no
      typedef struct no Node;
      
      typedef struct
      {   // example, more flexible
          char*       name;
          unsigned    size;
          unsigned    capacity;
          Node*       head;
          Node*       tail;
      }   Linked_list;
      

      这样链表被定义为节点的容器。

      • 它甚至还有一个可选的name
      • size 始终可用并且是最新的
      • 大小限制可以实现为capacity
      • 在末尾插入在开头不需要您跟随所有其他节点,因为列表封装了指向头和尾的指针
      • 节点具有指向下一个和前一个节点的指针,因此可以更轻松地迭代某些数据,例如 播放列表 或类似的集合。
      • 一个程序可以有任意数量的列表,因为每个列表都封装了所有这些元数据。
      • 列表可以包含任何内容,因为数据是指向 void 的指针,void*
      • empty() 或 size() 等函数可以轻松实现
      • 所有函数都使用指向列表的指针
          Linked_list  ll_one;
          Linked_list  many_ll[20];
          Linked_list* pLL = &ll_one;
      

      【讨论】:

        【解决方案4】:

        关于:

        void begin(node *head){
        

        更改head 只会更改调用堆栈'head',需要更改调用者函数中的'head' 指向的位置。为此,调用者必须传递“head”的地址。 'head' 本身就是一个指针这一事实无助于明确需要做什么,

        【讨论】:

          猜你喜欢
          • 2012-08-11
          • 2018-07-28
          • 2013-08-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-22
          • 1970-01-01
          相关资源
          最近更新 更多