我在读Skeina的书。我无法理解这段代码。基本上双指针有什么用。*l = p
有什么用?任何人都可以用图表解释一下。
void insert_list(list **l, item_type x) {
list *p; /* temporary pointer */
p = malloc(sizeof(list));
p->item = x;
p->next = *l;
*l = p;
}
您不应该称其为"双指针",因为那将是指向double
的指针。 它是指向指针的指针,用于允许函数更改恰好是指针的参数的值。 如果你熟悉 C#,它就像一个out
论点。 在这种情况下,l
参数用于获取 IN 和 OUT 行为,但您可能经常看到它仅用于输出。
由于此函数返回类型void
因此可以在不使用指向指针的情况下很好地编写它,如下所示:
list * insert_list(list *l, item_type x) {
{
list *p; /* temporary pointer */
p = malloc(sizeof(list));
p->item = x;
p->next = l; // note that this is not *l here
return p;
}
此更改需要调用函数的代码更新其自己的列表句柄,因为列表的头部是要更改的内容。
此函数执行一个非常简单的任务:list
它在它接收指针的位置。 什么都没有关于双指针的特别之处,它们只是指向指针的指针。 它们保存指针的地址,指针包含对象的地址。 void **l
包含list *
指针的地址。 *l
检索此地址和*l = p
存储它。
malloc
用于分配 list
结构中,p
接收已分配结构的地址。代码有些草率,因为之前没有检查p
NULL
取消引用它。 如果malloc
由于内存不足而失败,则程序将调用未定义的行为,希望以分段错误或可能更糟的情况。
节点初始化,其next
指针设置为指向的节点通过l
参数,最后存储新节点的地址在作为l
参数传递的地址。 效果很简单:节点插入 *l
.
这种方法很聪明,它允许相同的功能在列表的任何地方插入新节点。例如:
list *head = NULL;
...
/* Insert a LIST_A node at the beginning of the list */
insert_list(&head, LIST_A);
...
/* insert a LIST_B element as the second node in the list */
insert_list(&head->next, LIST_B);
...
/* find the end of the list */
list *node;
for (node = head; node->next; node = node->next)
continue;
/* insert a LIST_Z node at the end of the list */
insert_list(&node->next, LIST_Z);
上面唯一棘手的是指针本身的概念,这里有一个简单的概述:
- 内存可以概念化为(大型)字节数组,地址是该数组中的偏移量。
- 根据定义,
char
变量是单个字节, -
int
变量占用特定于系统体系结构的字节数,在当前硬件中通常为 4 或 8 个字节。 - 将指针视为在另一个变量的内存中保存地址的变量。它们需要足够大以容纳系统中的任何有效地址,在当前具有超过 4 GB 物理和可寻址内存的系统中,它们长 64 位,占用 8 个字节。
- 有一个特殊的地址值
NULL
它不表示任何对象,用于指定给定指针不指向任何实际对象。地址0
用于此目的。malloc
将返回NULL
如果它无法分配请求的内存,则应测试返回值,因为禁止在此地址存储值,并且通常被捕获为无效访问(分段错误)。
此摘要故意过于简单。 我使用术语变量而不是对象来避免与 OOP 概念混淆。