你显然理解第一种方法。
第一种和第二种方法的根本区别在于指针用于枚举列表的位置。在第一种方法中,通过一个本地变量使用指针的值,每次更新为当前节点的next指针的值。而在第二种方法中,使用了指向指针的指针来保存“当前”指针的地址。它所寻址的指针是链表中的实际指针,而不仅仅是它的值。最初它会寻址head指针。每次步骤中它会寻址当前节点的next指针。当算法结束时,它将持有链接列表中最后一个next成员的地址,其值应为NULL。
第二种方法有明显的优点,尽管在简单枚举场景下没有任何优势。这种方法更常被用于维护场景,例如位置列表插入和删除。
示例:给定一个链表的头指针和以下节点形式,请编写一个函数将新节点添加到列表末尾:
struct Node
{
int data;
struct Node *next;
};
使用第一种方法进行枚举需要维护一个先前指针和一种特殊考虑来检测初始的NULL头指针。下面的函数可以实现这个功能,并且总是返回链表的头部:
struct Node * ll_insertTail(struct Node *head, int data)
{
struct Node *prev = NULL, *cur = head;
while (cur)
{
prev = cur;
cur = cur->next;
}
cur = malloc(sizeof(*head));
cur->data = data;
cur->next = NULL;
if (prev == NULL)
return cur;
prev->next = cur;
return head;
}
同样的操作,但利用指向指针的方法来遍历实际的指针成员(而不仅仅是它们的值)将如下所示:
struct Node* ll_insertTail(struct Node *head, Type data)
{
struct Node **pp = &head;
while (*pp)
pp = &(*pp)->next;
*pp = malloc(sizeof(**pp));
(*pp)->data = data;
(*pp)->next = NULL;
return head;
}
这可以进一步改善,通过要求调用者首先传递头指针的地址。这样做的好处是允许您利用函数的返回值来执行与列表头指针不同的其他操作。例如:
int ll_insertTail(struct Node **pp, int data)
{
while (*pp)
pp = &(*pp)->next;
if ((*pp = malloc(sizeof(**pp))) == NULL)
{
perror("Failed to allocate linked list node");
return EXIT_FAILURE;
}
(*pp)->data = data;
(*pp)->next = NULL;
return EXIT_SUCCESS;
}
调用方式:
int res = ll_insertTail(&head, data);
以上两种情况之所以可能,是因为利用了指针的地址而不是简单地使用值。对于简单枚举来说,运用指向指针的方法没有多少意义。但如果你需要在列表中搜索特定节点或位置,并保留使用指向该节点的指针的机制(可以是head,也可以是某些next成员),那么指向指针的方法会提供优雅的解决方案。
祝好运。