链表中二级指针作用(图示)

前提知识

这个问题比较像函数传递时的值传递和引用传递类似,具体可以看下面两段代码,方便理解后面双指针。可以看到输出结果左侧还是6,而右侧输出0,这就是关键所在。形参m只是实参a的一个赋值的变量,形参我们都知道是函数调用时候才分配内存单元,当函数调用完毕后,形参就会被释放掉,所以左侧程序可以这么理解:定义一个a变量,它的值为6,当把a作为实参传进test这个函数时,系统会定义一个变量m,并且把a的值“6”赋给了m,然后又执行m=0。所以,到整个程序结束,m=0,a=6,所以a的值根本就没有发生改变。而右侧代码,a和m指向同一个地址,因此在test函数里m的修改对于main函数里的a同样有效。

	void test(int m){                   void test(int *m){
        m = 0;                              *m = 0;
    }                                   }
    int main(){                         int main(){
        int a = 6;                          int a = 6;
        test(a);                            test(&a);
        cout<<a<<endl;                      cout<<a<<endl;
        return 0;                           return 0;
    }                                   }

若一个变量想通过函数来改变本身的值,将本身作为参数传递是不成功的,只有传递本身的指针(地址)才能达到这样的效果。

所以后面我们创建链表时,传递的是**双指针**,这就是为什么参数是双指针的原因。

变量内存中都有自己的地址

我们来看下面一张图,就比较容易理解,只要是变量它都会有自己的地址(指针),即使是指针变量。

然后,指针它就是用来存地址的,只有两部分,一部分是附带自己的地址,一部分是存别人的地址
在这里插入图片描述

初始化链表需要双指针

定义链表节点如下代码所示:

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x,ListNode *t) : val(x), next(t) {}
};

现在又两段初始化链表头结点的代码,执行结果完全不一样:

	void test(){                                      void test(){
        ListNode *head = NULL;                            ListNode *head = NULL;
        init(&head);                                      init(head);
        print(head);                                      print(head);
    }                                                 }
    void init(ListNode **node){                       void init(ListNode *node){
        *node = new ListNode(10,NULL);                    node = new ListNode(10,NULL);
    }                                                 }
    void print(ListNode *head){                       void print(ListNode *head){
        while(head != NULL){                              while(head != NULL){
            cout<<head->val<<"->";                            cout<<head->val<<"->";
            head = head->next;                                head = head->next;
        }                                                 }
        cout<<endl;                                       cout<<endl;
    }                                                 }

在这里插入图片描述
可以看出执行结果,左侧使用二级指针成功初始化插入节点,而右侧不行。如果传的是一级指针,那么仅仅只是head和node所指的是同一块内存区域,而node和head本身并不是同一个指针,函数执行完,node指针也被释放掉,而新生成的节点在内存中孤零零,没有人指向它。

使用双指针情况

下面来进行详细图解一下:
首先看使用双指针情况,对于在test函数中定义的head节点,定义了一个指针,指向ListNode 类型,其实也就是整个链表的头指针,只不过还没有赋值。

ListNode *head = NULL;

在这里插入图片描述
下面调用节点初始化函数,head本身就是指针,现在&head即为取地址,相当于将head指针的地址传递过去,刚好对应于初始化函数void init(ListNode **node)中的二级指针,其中**node表示指针的指针。内侧的指针*node指向链表,即对应于*head,用来初始化,外侧的**head指向链表指针,相当于上面讲的指针传递,这样才可以把init函数中node的修改作为参数传回给主程序。

init(&head);
其原型函数是 void init(ListNode **node)

图示如下:
在这里插入图片描述
其实*node就是头指针head的值了,加*号就代表指针的值,new会申请一个结点,然后返回结点的首地址,其实这个新生成的结点是没有名字的,为了方便假设为x。具体图示如下图所示

*node = new ListNode(10,NULL);

在这里插入图片描述
为了更清晰说明我们写代码打印每一个地址,代码如下以及执行结果

	void test(){
        ListNode *head = NULL;
        cout<<"1."<<&head<<endl;
        init(&head);
        cout<<"7."<<head<<endl;
        print(head);
    }
    void init(ListNode **node){
        cout<<"2."<<&(*node)<<endl;
        cout<<"3."<<*node<<endl;
        cout<<"4."<<(node)<<endl;
        *node = new ListNode(10,NULL);
        cout<<"5."<<(*node)<<endl;
        cout<<"6."<<node<<endl;
    }

在这里插入图片描述
下面使用图示对每步操作,地址变化做详细说明:
在这里插入图片描述
在这里插入图片描述

使用单指针情况

如果理解上面的情况,再分析右侧代码不使用双指针,就比较好分析了。编译器总是要为函数的每个参数制作临时副本,比如看输出的1和2,看到head和node的地址都不一样,在函数init中node指向生成的头指针,然而函数结束后,head还是原来那样没什么变化,node被销毁释放掉,至于新生成的结点,则是孤零零的在内存区里瑟瑟发抖,等待有人来指向它,这就是需要双指针原因。用了双指针,head指向了新生成的结点,node被释放掉,皆大欢喜。

	void test(){
        ListNode *head = NULL;
        cout<<"1."<<&head<<endl;
        init(head);
        cout<<"7."<<head<<endl;
        print(head);
    }
    void init(ListNode *node){
        cout<<"2."<<&node<<endl;
        cout<<"3."<<node<<endl;
        node = new ListNode(10,NULL);
        cout<<"4."<<node<<endl;
        cout<<"5."<<&node<<endl;
        cout<<"6."<<node->val<<endl;
    }

在这里插入图片描述
具体图示如下:
在这里插入图片描述

  • 25
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值