C链表指针理解

时间:2018-10-22 12:05:58

标签: c list pointers data-structures

我试图了解C链表指针的工作方式。 我知道指向变量的指针是指向地址存储器的“链接”,并且指向指针的指针有时是指向指针本身的引用。

让我担心的是,例如,节点引用如何修改原始列表值,而不是列表本身。

我会更好地解释自己:

void insertNode(struct node** head, int value) {

    struct node* new = malloc(sizeof(struct node*));
    struct node* ref = (*head); //this is a reference. -> same address.

    //base case 
    if((*head) == NULL) {
        //do things
    } else { // not null
        while(ref->next != null) {
            ref = ref->next; //THIS: how can this not modify the head itself?
        }

        //null spot found, set up node
        new->value = 10; //some int here
        new->next = NULL;

        ref->next = new; //Instead, how can this modify the head? and why?
    }
}

这是一些代码片段,我的问题是: 是的,我持有一个通过 ref 进行引用的标题。 但是为什么

ref = ref->next;

仅修改ref本身,而

ref->next = new

还改头吗?

通过GDB,我发现两者在开始时都共享相同的地址内存,但是ref仅修改新插入内容上的引用列表。

有人可以解释吗?

2 个答案:

答案 0 :(得分:1)

ref只是一个指针;修改ref不会修改ref所指向的内容。

while循环实际上只是在寻找列表的最后一个元素。在while循环之后,ref将仅指向列表的最后一个元素。

第一行“神秘”行:

ref = ref->next; //THIS: how can this not modify the head itself?

在这里,我们只是读ref->next,所以无法修改头部。

第二条“神秘”行:

ref->next = new; //Instead, how can this modify the head? and why?

在这里,我们修改ref所指向的内容。在这一行ref指向列表的最后一个元素,或者指向标头(如果列表中只有一个元素,那么它也是列表的最后一个元素,或者是新创建的标头(在//do things中完成),如果列表为空。

答案 1 :(得分:0)

也许有些图片会有所帮助。

在调用insertNode之前,您具有一系列这样链接的节点:

+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

您有一个指针(称为h)指向列表的第一个元素:

+---+
| h |
+---+
  |
  V
+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

调用insertNode时,会将指向 h的指针作为参数传递,我们称之为head

+------+
| head |
+------+
  |
  V
+---+
| h |
+---+
  |
  V
+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

您创建一个名为ref的指针变量,该变量的值为*headh); IOW,ref指向列表的第一个元素:

+------+ +-----+
| head | | ref |
+------+ +-----+
  |        |       
  V        |
+---+      |
| h |      |
+---+      |
  |   +----+
  V   V
+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

然后,在堆上创建另一个节点,并将该指针分配给名为new的局部变量:

+------+ +-----+ +-----+      +-------+------+
| head | | ref | | new | ---> | value | next |
+------+ +-----+ +-----+      +-------+------+
  |        |       
  V        |       
+---+      |       
| h |      |    
+---+      |     
  |   +----+     
  V   V
+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

因此,需要注意的是,尽管ref*headh)具有相同的 value (位于清单),它们是不同的对象。因此,任何更改ref的值都不会影响headh

因此,如果我们执行此循环

while(ref->next != null) {
    ref = ref->next; 

第一次迭代的结果是

+------+ +-----+ +-----+      +-------+------+
| head | | ref | | new | ---> | value | next |
+------+ +-----+ +-----+      +-------+------+
  |        |       
  V        |       
+---+      |       
| h |      |    
+---+      |     
  |        +------------+     
  V                     V
+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

经过另一次迭代,我们得到了

+------+ +-----+ +-----+      +-------+------+
| head | | ref | | new | ---> | value | next |
+------+ +-----+ +-----+      +-------+------+
  |        |       
  V        |       
+---+      |       
| h |      |    
+---+      |     
  |        +----------------------------------+     
  V                                           V
+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

此时,ref->nextNULL,因此循环退出。

然后,我们为new->valuenew->next分配值,以使new->nextNULL

+------+ +-----+ +-----+      +-------+------+
| head | | ref | | new | ---> | value | next | ---|||
+------+ +-----+ +-----+      +-------+------+
  |        |       
  V        |       
+---+      |       
| h |      |    
+---+      |     
  |        +----------------------------------+     
  V                                           V
+-------+------+      +-------+------+      +-------+------+      
| value | next | ---> | value | next | ---> | value | next | ---|||
+-------+------+      +-------+------+      +-------+------+      

最后,我们将ref->next设置为new的值,从而将节点new指向列表的末尾:

+------+ +-----+ +-----+      +-------+------+
| head | | ref | | new | ---> | value | next | ---|||
+------+ +-----+ +-----+      +-------+------+               
  |        |                    ^                                
  V        |                    |                               
+---+      |                    +-------------------------------+
| h |      |                                                    |
+---+      |                                                    |
  |        +----------------------------------+                 |
  V                                           V                 |
+-------+------+      +-------+------+      +-------+------+    |  
| value | next | ---> | value | next | ---> | value | next | ---+
+-------+------+      +-------+------+      +-------+------+      

请注意,ref->next不是指向变量 new,而是指向new指向的对象。

因此,这就是更新ref不会影响head(或*headh)的原因。在列表为空的基本情况下,最终写入*headh),并将其设置为指向从堆分配的新节点。