Daniel 的技术笔记 不积跬步无以至千里,不积小流无以成江海。

二级指针实现单链表的插入、删除

今天看了coolshell上关于二级指针删除单链表节点的文章。

文章中Linus 举例:

例如,我见过很多人在删除一个单项链表的时候,维护了一个”prev”表项指针,然后删除当前表项,就像这样:

if (prev)prev->next = entry->next;elselist_head = entry->next;and whenever I see code like that, I just go “This person doesn’t understand pointers”. And it’s sadly quite common.

(当我看到这样的代码时,我就会想“这个人不了解指针”。令人难过的是这太常见了。)

People who understand pointers just use a “pointer to the entry pointer”, and initialize that with the address of the list_head.

And then as they traverse the list, they can remove the entry without using any conditionals, by just doing a “*pp = entry->next”.

(了解指针的人会使用链表头的地址来初始化一个“指向节点指针的指针”。当遍历链表的时候,可以不用任何条件判断(注:指prev

是否为链表头)就能移除某个节点,只要写)

*pp = entry->nextLinus看来,维护了一个”prev”表项指针进行删除,这是不懂指针的人的做法。那么,什么是core low-level coding呢?

那就是有效地利用二级指针,将其作为管理和操作链表的首要选项。

coolshell上这篇 Linus:利用二级指针删除单向链表 文章对二级指针操作单链表删除的精妙之处已经做了说明。

下面,我们来探讨下,为什么 使用二级指针能达到如此的效果??

我们先来个简单的列子:

#include <cstdio>#include <cstdlib>void f(int v){v = 1;}void f_(int *pv){*pv = 1;}int main(){int n = 0;f(n);printf("%d\n", n);f_(&n);printf("%d\n", n);return 0;}输出:

0

1

上面是个 简单的例子,即是我们常说的c/c++ 语言的函数 参数传递 一律为 值传递。要达到改变所传递的参数的值,我们只能想法把 存放

这个实际值的内存地址当做参数进行传递,然后我们操作内存地址,通过修改这个地址所指向的值,间接达到修改这个值的效果。如图

值传递,记住这条基本原理:形参相当于函数中定义的变量,调用函数传递参数的过程相当于定义形参变量并且用实参的值来初始化。

下面来说明下,单链表的操作。

单链表中,链表节点就是一个地址加上别的数据,这个地址指示着下一个节点的位置,只要我们有头节点head这个指针,用来指向第一个

节点。这样我们的链表中,每个节点都有一个指针指着,而且环环相扣。

为什么会想到二重指针操作的写法?因为我们意识到删除操作的本质是指针值的改变,这样用二级指针去操纵指针的值就是很自然的想法了。

说白了:显然二级指针是保存了可能会被修改的变量的地址,这就是head或prev->next。通过二级指针,head或prev->next这两个概念被统

一在一起。来个图吧。

这个图可以结合下面代码中的 void remove_if(Node **pphead, int v) 函数。

对于上面的这个图我们声明了一个二级指针 cur,我们能够发现其实 二级指针是保存了被修改的变量的地址这里为图中的 en 指针,

en指针,好吧其实 en就是某个结构体或类型对象的地址,当这个指针en(在32位系统上其实就是一个4字节的无符号数)满足我们的

条件时 我们只需要 修改这个指针所指向的内存块(即图中的struct),下面的代码中我们是是否这个内存块,但在此之前我们可以利用

en这个指针变量所占的真实内存块(注意指针en的值所在内存其实就是上一个节点prev 结构体的netx所指内存),所以我们现在所要

做的就是改变这个prev节点netx所指内存的存储的值,即*cur 的值(因为cur=&en), 所以在释放en所指内存块前,我们需要把*cur 即

prev->next 的值修改为en->next, 即 *cur=en->next.

我们可以明白了,其实二级指针删除节点的做法把 en和prev这两个概念统一在一起了。

好了,代码如下:

#include <cstdio>#include <cstdlib>typedef struct node{int data;struct node *next;}Node;int insert(Node **pphead, int v)//插入节点,采用头插法{ Node *t= (Node*)malloc(sizeof(Node)); if(NULL == t) return 0; t->data = v; t->next = *pphead;// 新头节点的next节点为保存的插入之前的头节点t *pphead = t; return 1;}void print(Node **pphead)//输出链表所有元素{for(Node *cur = *pphead; cur;){printf("%d ", cur->data);cur = cur->next;}}void remove_if(Node **pphead, int v)//删除节点值为v的所有节点{for(Node **cur = pphead; *cur;){Node *en = *cur;if(en->data == v){// *重要**cur = en->next;//用二级指针去操纵指针的值,*cur现在为待删除节点的next节点free(en);//释放待删除节点,此时存放地址en值的二级指针cur依然存在 且cur地址//所指内存已经了存储了 删除节点的下一个节点}else{cur = &en->next;}}}int main(){Node *pfirst=nullptr;//c++11,初始化头节点为空for(int i=0; i != 5; ++i)insert(&pfirst, i%2);print(&pfirst);printf("\n");remove_if(&pfirst, 1);print(&pfirst);return 0;}

输出:

0 1 0 1 00 0 0

绚丽的民族风情,悠久的历史文化。抛开尘世的纷扰,

Daniel 的技术笔记 不积跬步无以至千里,不积小流无以成江海。

相关文章:

你感兴趣的文章:

标签云: