概念

提到指针,我们都知道指针是用来存储一个变量的地址。所以,当我们定义了一个指向指针的指针的时候(pointer to pointer),我们也称之为二级指针,那针对于这个二级指针来说,第一级指针存放的是指向的变量的地址,第二级指针存放的是第一级指针的地址。可以用下面这张图表示他们之间的关系。

上图所表达的意思也就是,一级指针变量 ptr1 存放的是 var 变量的地址,二级指针变量 ptr2 存放的是一级指针变量的地址。这也就是关于二级指针的相关概念。

一级指针与二级指针关系示例

#include <stdio.h>int main(void)
{int a = 10;int *p = &a;int **q = &p;printf("a   = %d\n",a);printf("&a  = %p\n",&a);printf("p   = %p\n",p);printf("&p  = %p\n",&p);printf("*p  = %d\n",*p);printf("q   = %p\n",q);printf("&q  = %p\n",&q);printf("*q  = %p\n",*q);printf("**q = %d\n",**q);
}

下图是代码运行的结果:

结果也很明显了,一级指针变量 p 存放的是变量 a 的地址,二级指针变量 q 存放的是一级指针变量 p 的地址,所以根据以上结果也能得出下面的等式:

q = &p;
*q = p = &a;
**q = *p = a;

在了解了上述一级指针和二级指针的一个关系之后,我们再来看另外一个例子:
现在有如下代码:

int main(void)
{int **ipp;int i = 5,j = 6,k = 7;int *ip1 = &i,*ip2 = &j;
}

如果这个时候,我们加了这么一句代码:

ipp = &ip1;

那么上述所涉及到的数据之间的关系是这样的:

根据上面这个图我们也可以知道,对于 ipp 的两次解引用的结果是 i 的值,也就是说 **ipp = 5 :
我想对于这个的理解并不困难,如果我继续在这个基础上添加代码,注意,是在上条代码的基础上添加如下代码:

*ipp = ip2;

在这条代码的作用下,数据关系图就发生了改变,改变如下所示:

对于上述的变化来说,我们增加的代码改变的是 *ipp 的值,也就是说 ipp 的值是不会发生改变的,既然 ipp 的值不会发生改变,那么 ipp 指向 ip1 的关系不会发生改变,我们增加的代码改变了 *ipp 的值,那么也就是说改变了一级指针指向的值,而 ip2 是指向 j 的,所以也就有了上述的变化。
紧接着我们继续在第一条增加的代码的基础上重新增加一条代码,增加的代码如下:

*ipp = &k;

那么这个时候所对应的数据关系图如下图所示:

这个原理和刚才的一样,不在这里赘述了。

二级指针的应用

那再讲述了上述的基本概念之后,我们知道二级指针变量是用于存放一级指针变量的地址的,那么在具体的实际应用中,又在什么地方可以用到二级指针呢?下面来看一个 C 语言函数传址调用的例子。
我们在刚学习指针的时候,都会碰到如下这样一个例子:

void swap(int *a,int *b)
{int temp;temp = *a;*a = *b;*b = temp;
}

之所以在定义函数时,把函数的形参定义为指针,而非如下这样的形式:

void swap(int a,int b);

是因为C 语言在进行函数调用的时候,是将实参的值复制一份,并将其副本传递到函数调用里,如果形参定义的不是指针,那么在函数内部改变数值,不会对实参本来的值发生改变。而将形参定义成了指针的话,那么传到函数里面的值虽然是实参地址的一个副本,但是地址里存的值发生了改变,也就导致实参本来的值也发生了改变。
有了上述分析的基础上,我们知道,如果要在一个函数内改变一个数的值,那么就需要将形参定义为指针。同样的,如果我们要在一个函数内改变一个指针的值,我们就需要将形参定义了二级指针,下面来看这样一个例子:

#include <stdlib.h>int allocstr(int len,char **retptr)
{char *p = malloc(len + 1);/*加 1 是为了 '\0' */if (p == NULL)return 0;*retptr = p;return 1;
}

在调用的时候,是像下面这样子进行调用的:

char *string = "hello world!"
char *copystr;
if (allostr(strlen(string),&copystr))strcpy(copystr,string);
elseprintf("out of memory!\n");

上述这个例子就是涉及到字符串拷贝的一个实际的例子,因为我们要在 allostr 里改变指针变量 copystr 的值(要使用 malloc 分配内存),那么就需要把 copystr 的地址传到函数里,那么这个时候,所定义的函数形参也就需要是二级指针了。

二级指针在单链表中的应用

首先,我们有这样一个单链表的数据结构:

typedef struct ListNode
{int data;struct ListNode *next;
}ListNode;

依据这样一个数据结构,假定我们创建了一个如下所示的一个单链表:

那么我们如果要删除链表中的一个结点的时候,第一时间采用的可能是如下所示的代码:

ListNode *find_and_delete(ListNode *head,int target)
{ListNode *pre = NULL;ListNode *entry;for (entry = head; entry != NULL; entry = entry->next) {if (entry->data == target){/* 判断删除的结点是否是第一个结点*/if (entry == head) head = entry->next;elsepre->next = entry->next;free(entry);break;}pre = entry;}return head;
}

上述代码所述的删除结点的思路遵循如下图所示的原理,首先是关于当所要删除的结点是第一个结点的时候,删除结点示意图如下所示:

如果要删除的结点不是处在第一个结点的位置,那么删除结点的原理示意图如下图所示:

上述就是一个使用一级指针操作链表的一个简单地例子,自己在理解这个例子的时候,也存在几个对我来说的难点,笔者写下来和大家分享一下,首先,

  • 第一个难点就是头指针,在图中画的头指针指向了第一个结点,图中所示的头结点并没有数据域,只是单单地指向了第一个结点,在代码中的 head 指针变量却又数据域,而且具有数据域并且就是第一个结点的数据,这个概念的理解其实是对于指针的理解,head 指向了第一个结点,一定注意在这里的 head 是头指针,并不是头结点。(这是笔者个人的理解,如果大家有不同的看法,欢迎各位朋友添加笔者微信共同探讨)。
  • 第二个难点就是上述函数中,函数有一个返回值,返回了头指针。为什么要返回呢?是因为当前传入函数的形参是一级指针,在函数内部改变 head ,在函数运行结束时,head 值并不会发生改变,所以要返回。
  • 第三个难点,那么为什么链表操作中,又能够删除中间的结点呢?是因为虽然 传进去的 head 是一级指针,但是 head 结构体成员内的 next 是一个指针,那这样的话,对于 next 成员来说它是一个二级指针,对于他的变化,在函数结束时是会产生改变的,所以可以删除中间的结点。

二级指针在单链表结点删除的应用

上面的例子中,在删除单链表的结点的时候吗,我们形参采用的是一级指针的方式,在这个过程中,还需要引入 prv 指针来解决这个问题,还有一种很巧妙的方法,利用了二级指针的特性解决了结点删除的问题,在这个过程中,运用二级指针,不需要进行删除第一个结点的判断。具体代码如下:

void find_and_delete2(ListNode **head,int target)
{for (; *head != NULL; head = &(*head)->next){if ((*head)->data == target){(*head) = (*head)->next;break;}    }
}

上述的代码没有创建任何局部变量,直接利用 head 进行遍历链表,因为其是二级指针,这样子进行遍历在函数结束后不会改变其本身的链表结构。然后,在进行删除的时候,(*head) 在函数结束后是会保持其在函数内的变化值的,所以也就完成了结点的删除。

结论

上述就是关于二级指针的相关内容,总体来说,二级指针也是指针,用指针的思想来处理这个问题就好,区别只是在于一级指针是由于存放普通变量的地址的,二级指针是由于存放指针变量的地址的。另外需要注意的就是 C 语言在进行函数调用时,实参的传递采用的是实参值的一份拷贝。如果要在函数内部改变变量的值,就要传入指针,如果要在函数内部改变指针的值,就需要传入二级指针。

为什么要使用二级指针?相关推荐

  1. 关于C语言中的数组指针、指针数组以及二级指针

    概念解释 数组指针:首先它是一个指针,它指向一个数组,即指向数组的指针:在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道.数组指针指向的是数组中的一个具体元素,而不是整个数组,所 ...

  2. C指针8:二级指针(意思就是指向指针的指针)

    指针可以指向一份普通类型的数据,例如 int.double.char 等,以下简称一级指针: 也可以指向一份指针类型的数据,例如 int *.double *.char * 等.以下简称二级指针:即如 ...

  3. C/C++二级指针概念及应用(有向图的邻接表(拓扑排序)、有向网图的邻接表、树的孩子表示)

    目录 一.概述 例1: 例2: 代码: 二.实例 1.有向图的邻接表(拓扑排序) 2.有向网图的邻接表 3.树的孩子表示 一.概述 二级指针:指向指针的指针.一般需要修改地址的时候会用到二级指针. 注 ...

  4. 深入理解C语言-二级指针三种内存模型

    二级指针相对于一级指针,显得更难,难在于指针和数组的混合,定义不同类型的二级指针,在使用的时候有着很大的区别 第一种内存模型char *arr[] 若有如下定义 char *arr[] = {&quo ...

  5. C++利用二级指针做函数形参来进行修改实参的实例分析

    在学C/C++的时候,我们都会了解到一级指针,int* i = NULL;   和二级指针int ** pp = NULL; 但是具体的一些应用我们可能很难理解,如果我们要取int*的地址,我们就需要 ...

  6. 2020-09-25C++学习笔记引用2:二级指针形参与指针形参的使用方法和区别重难点,主看综合代码(1、指针形参2、引用形参3、二级指针形参4、指针引用形参)

    2020-09-25C++学习笔记引用2:二级指针形参与指针引用搭配做形参的使用方法区别重难点,注意事项主看综合代码(1.指针形参2.引用形参3.二级指针形参4.指针引用形参) 1.指针引用 #inc ...

  7. 深入理解指针以及二级指针(指针的指针)

    前言:本文将讲解指针的定义.指针变量和普通变量的本质区别.一级指针和二级指针的关系以及如何通过二级指针修改一级指针所指向的内存.文末还附加了两个实例,帮助读者加深对二级指针的理解.本文试图通过图表的方 ...

  8. 二维数组和二级指针关系浅析

    昨天写了关于一级指针的见解,即二级数组的用法和二级指针差不多,关于数组指针和指针数组这两个是不同的概念,这一点我们一定要清楚: 数组指针我们可以理解为指向数组的指针,就是一个指针: 指针数组则可以理解 ...

  9. 表变量是什么_为什么要使用二级指针?

    笔者能力有限,如果文中出现错误的地方,欢迎各位朋友给我指出来,我将不胜感激,谢谢~ 概念 提到指针,我们都知道指针是用来存储一个变量的地址.所以,当我们定义了一个指向指针的指针的时候(pointer ...

  10. C指针--通过二级指针往回拉数据

    现在有这种需求,在main函数中建立一个二叉树的root结点的指针,在tree_create函数中malloc或者new一个二叉树结点,然后在main函数中能正常的访问这个新建的结点. 注:这里的tr ...

最新文章

  1. Oracle_052_lesson_p10
  2. Codeforces Round #732 (Div. 2) C. AquaMoon and Strange Sort 思维
  3. java线程变量值_JAVA线程中对变量值的修改
  4. 排座椅(洛谷-P1056)
  5. python从入门到实践答案第四章_python从入门到实践课后习题第四章
  6. 离散数学经典教材及资料(整理)
  7. Linux 配置vim
  8. c语言汉字属于什么类型_C语言为什么需要定义数据类型
  9. No certificate for team ‘‘ matching ‘iPhone Distribution: VOVA TECH LIMITED ()‘ Select a different s
  10. 电脑启动显示“A disk read error occurred”的解决方法
  11. 规则引擎Drools使用 第十一篇 Drools 的高级语法之LHS增强
  12. 试题 基础练习 字母图形
  13. java设计中国象棋_Java课程设计—象棋
  14. KVM虚拟机 装系统 命令行
  15. 2018年终个人总结
  16. SecureFX中文乱码,而SecureCRT中文显示正常
  17. 虚拟机ERP服务器配置,erp硬件推荐部署方案v.pdf
  18. 解决电脑权限不足问题
  19. 查看某端口的占用情况
  20. 基于物联网的健身的动作监测系统(动作识别)

热门文章

  1. 智能网联先导区道路交叉口车路协同系统设计
  2. python输入名字配对情侣网名_输入名字配置情侣网名
  3. 宇宙为什么没有单独存在的夸克,强行将夸克拉出来会怎样?
  4. Hive 正则表达式 判断字符串是否是数字(整数)
  5. matlab中给信号添加高斯白噪声的两种方法,awgn计算过程,randn函数
  6. 付费专栏热销排行榜·0315更新
  7. MySQL事务——万字详解
  8. How to make a ipcamera
  9. Mockito Verify的使用
  10. 王者服务器维护公告2月,王者荣耀2月22日体验服停机更新公告 英雄调整