我试图了解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仅修改新插入内容上的引用列表。
有人可以解释吗?
答案 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
的指针变量,该变量的值为*head
(h
); 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
和*head
(h
)具有相同的 value (位于清单),它们是不同的对象。因此,任何更改ref
的值都不会影响head
或h
。
因此,如果我们执行此循环
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->next
是NULL
,因此循环退出。
然后,我们为new->value
和new->next
分配值,以使new->next
为NULL
:
+------+ +-----+ +-----+ +-------+------+
| 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
(或*head
(h
)的原因。在列表为空的基本情况下,将最终写入*head
(h
),并将其设置为指向从堆分配的新节点。