【每天学一点系列~】还在困惑数据结构(尤其是链表)里指针的看这里!!!
一定要搞定指针啊!!!
- Part I、说在前面儿
- Part II、指针它是个啥?
- 1、地址
- 2、指针的指向作用
- 2、地址与数据
- 3、解引用
- 4、二级指针
- 5、传址调用
- Part III、数据结构里的指针
- 1、不带头单链表
- 2、带头链表
- Part IV、总结一下(都是精华啊)
Part I、说在前面儿
正在学习数据结构的同学,尤其是正在学习链表的同学往往会有这样的疑问:为什么有的地方传一级指针,有的地方传二级指针?为什么带头跟不带头差别那么大…对于C语言的基础,尤其是指针的基础不是那么好的同学,数据结构简直就是劝退的拦路虎。
说到这里不知道你的DNA是不是动了一下呢?头又晕了?没关系,今天我们就来克服指针恐惧症!
Part II、指针它是个啥?
我们想理清指针,就先从它的本质说起。别划过,基础不牢,地动山摇哈。
1、地址
电脑的内存可以按照字节划分,为了区分这些字节,我们用十六进制数字给它们进行编号,这些编号就是所谓的地址。
关键字提炼:地址对应内存上的一个字节
2、指针的指向作用
指针,就是个箭头。生活中我们见过路牌,见过指南针,它们都差不多,都是引路者。路牌和指南针都能带我们回家,而指针的作用,也是告诉我们地址。
试问:路牌没了,路还在吗?
答案是不是很明显?当然存在!
所以这里就要明白第一个点:指针只能告诉我们地址,指引我们找到一块内存,但是当这个指针被我们销毁,或者说,让它指向别的地方了,这块内存上的东西是不是还在?是!
2、地址与数据
我们知道,指针里存储的是一块地址,那这块地址上存储的内容是啥呢?
举个例子,假设我们有一个int类型的变量a,里面存储着数据1,十六进制表示就是0x00000001。那么他在内存中的存储形式就是这样的:
a占四个字节,而一个变量只对应了一个指针,这个指针保存的是第一个字节。
可以看到:指针p保存的是地址,而地址对应字节上的内容。
3、解引用
一个东西的存在让指针有了新的能力——解引用符号*
如果说指针是路牌,那解引用就是一辆车。有了指向,有了车,我们就可以直达那块“地址”!
既然到了这块地址,那么我们能做的就多了:利用这块地址上的东西,或者改变这块地址上的东西。
提炼:指针存储的是地址,对指针解引用其实就是对地址解引用。地址对应内存,地址上的东西也就是内存上存储的数据。解引用的操作就是赋予我们访问和改变这些数据的能力!
4、二级指针
认识了解引用,我们就能学习下一个东西了:二级指针。二级指针又是个啥呢?
首先我们需要清楚一点:指针是不是数据?
指针当然是数据!这个数据就是地址。而地址的本质其实是十六进制的数据。这些数据存放在一种叫指针的变量当中。
既然要存储数据,那么指针是不是也占内存?既然是内存,那么它是不是也有一个地址?这个地址就可以存放在一个二级指针当中。
如果我们对二级指针解引用,就可以获得一级指针上存储的内容(也是一个地址),然后再解引用,就能获得一级指针存储的地址上存储的数据。(有点绕哦,对应这上面的图理一下)
ok,继续!
5、传址调用
这个很重要!!!这个很重要!!!这个很重要!!!
为什么有传址调用这个东西?因为形参与实参之间几乎没有任何联系。实参对应一个地址,形参对应另外一个地址。当我们改变形参,只是把它的地址上存的数据改了,但是实参对应的地址上的东西还是没变。
那如果我们传的是实参的地址呢?此时形参就是一级指针,这个指针存储的是实参的地址,对它进行解引用,就能访问或修改实参的地址上存储的数据了!
典型的例子就是交换两个数的Swap函数。
void Swap(int* x , int*y)
{int tmp = *x;*x = *y;*y = tmp;
}
认识了这些,就让我们直接进入数据结构的正题。
Part III、数据结构里的指针
1、不带头单链表
链表的节点的定义一上来就会给我们一个下马威:
typedef struct Node
{int data;struct Node* next;
}Node;
这里有一个自引用next,它指向的也是一个Node类型的指针。
意思就是说:
一个节点存一个数据,然后又存了一个指针,这个指针的内容是下一个节点的地址,换句话说,这个指针指向下一个节点,因此链表才能真正意义上“链接”起来。
我们定义了一个pList指针专门指向链表的第一个节点,也就是头(head)。因为它指向一个节点,所以它是一个Node类型的指针,即Node*。
由于一开始链表没有元素,自然也没有头结点,所以我们令pList = NULL,让它指向空。
紧接着我们就会写头插头删和尾插尾删函数,于是,诡异的二级指针就来了。
我们不妨看一个头插函数的原型:
void SListPushFront(Node** ppList, int x);
而我们调用它时就要这样:
void SListPushFront(&pList,0);
为什么要取地址呢?换句话说,为什么要传址呢?
原因很简单:因为我们的操作会改变这里的pList。(注意改变二字)
头插意味着链表的头不再是当前pList指向的那个节点,而是另一个新插入的newnode。我们让newnode的next指向当前的pList,然后再让pList指向newnode。
但是,如果传的是pList而不是&pList,那么由于传值调用,形参的改变不影响实参——尽管你让形参指向了newnode,但是pList存储的还是之前的那个节点的地址,然后出现这样的窘境:
pList指向链表的第二个结点而非第一个。
以后我们遍历链表都是从pList指向的位置开始的,但是pList由于是传值,它永远都指向同一块地方,不会改变。因此我们能遍历到的,也永远只有从当前位置开始,到NULL结束。
那么为什么尾插尾删这些函数也要传二级指针呢?设想:如果链表为空,尾插的那个元素就是新的头结点,是不是就会改变pList?如果链表只有一个元素,此时它就是链表的头,那尾删是不是也会改变pList?
总而言之,对于不带头的链表来说,可能改变链表的头的函数,都要用二级指针。只有传二级指针,也就是pList的地址,才能在函数的内部改变pList,让pList指向新的头!
2、带头链表
同学们好不容易弄清链表需要传二级指针,突然又遇到了一种不用传二级指针的链表,啥情况??不让人活了?别慌,让我们一起看看。
带头的链表与不带头的链表的区别就是:不带头的链表用一个pList指针来维护,而带头的链表用一个哨兵位来维护。什么是哨兵位?其实哨兵位也是一个结点,但是它并不存储任何的有效数据。
与不带头的单链表相比,带哨兵位的好处多多,这个在各位刷到链表相关的算法题之后就能察觉到,它能带来很多代码上的优化,可以少考虑很多边界情况。
不过当前, 它的最大的优点就是:不用传二级指针。为啥?
ListNode* list = ListCreate();
我们先创建一个哨兵位list。
可以看到,哨兵位是头结点前的一个节点,头结点的地址就是哨兵位的next指针存储的内容。
当我们进行传参时,直接把list这个指针传给形参,形参作为实参的临时拷贝,里面存储的内容就跟list指针完全一样,也就是说:
形参里也有一个指针,这个指针存储的内容跟哨兵位的next指针存储的内容一样,都是头结点的地址!有了这个地址,我们就可以修改这个地址上的内容,也就是说:修改头结点。
看到这里,我们应该明白:1、形参和实参的next指针肯定是两个不同的变量,但是它们存储的内容是相同的,都是头结点的地址。2、只要有地址,我们就能修改地址上的内容,无所谓存储这个地址的指针是谁。
刚刚不带哨兵位的情况下,pList需要传二级指针,因为它需要始终保持指向头指针,而这里的list永远指向的是哨兵位,head的改变只会影响list的next。当我们直接传list时,形参部分接收的就是list的内容(包括那个指向头结点的指针),通过它我们就可以直接访问list里的next,从而改变它。
Part IV、总结一下(都是精华啊)
想玩转指针,最重要的是什么?是要知道:
1、指针存储的是地址
2、地址对应内存
3、只要我们能得到这个地址,就能通过解引用访问或修改对应内存上的数据
4、如果传址,形参接收实参的地址,对形参解引用得到的就是实参这个实体。
5、如果是传值,形参会复刻实参的内容,如果内容中存在指针,那么我们同样可以通过这个指针修改对应内存上的数据。
6、如果我们想通过形参达到修改实参的目的,必须要传址!
最重要的一点:对于实参是指针的情况,如果我们仅仅想操作它存储的地址上的内容,那么传值就足够了。如果还想额外地改变实参,让实参存储别的地址,那么就需要传地址了!
不知道读者听懂了嘛,有任何问题可以在评论区反馈给我哦
关注笔者,一起学习,一起进步!
【每天学一点系列~】还在困惑数据结构(尤其是链表)里指针的看这里!!!相关推荐
- 【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?
学透字符串的旋转 Part I.前言 Part II.左/右旋 1.定义 2.共同特点 Part III.初阶解法 解法一:创建新数组 解法二:原地算法(直接法) Part IV.进阶解法 1. 三步 ...
- 教老婆学java系列之奇妙的数据结构四
教老婆学java系列之奇妙的数据结构四 数据结构最后一节 思考题:后台处理一件事,耗时较长,怎么将信息显示在页面上 问题解析: 1.前端触发,后端处理时间较长,前端不能等处理完成. 2.如果不等处理完 ...
- 什么都学一点系列之鸿蒙开发Java版简易备忘录
本文地址:https://blog.csdn.net/qq_40785165/article/details/123908357,转载请附上此链接 .生活不会惯着你,想要不被抛弃,必须自己争气. 大家 ...
- 教老婆学java系列之奇妙的数据结构三
教老婆学java之奇妙的数据结构三 Map与其他 发难环节:如何将这些数据发送给另一个方法或前端 学生 {姓名:张三, 性别:男,爱好:打球} {姓名:李四 ,性别:女,爱好:打球} 老师 {姓名:碟 ...
- html如何插入avi视频,学用系列|希沃云课堂无法播放插入视频?看这里一招搞定视频转换...
这两天不少老师都在尝试使用希沃云课堂录制时间胶囊或是开启云直播,当然群众的智慧(问题?)是无限的,今天给大家分享一个小技巧,如果遇到云课堂视频无法播放插入视频,怎么样又快又好一招搞定! 支持多媒体插入 ...
- 《跟我学算法系列文章——一文学会数据结构套路》
<一文学会数据结构套路> 关键词:数据结构 LRU Tree 文章目录 <一文学会数据结构套路> 前言 3.1 手写 LRU 算法 一.LRU 算法描述 二.LRU 算法设计 ...
- 人人都要学一点深度学习(1)- 为什么我们需要它
人人都要学一点深度学习(1)- 为什么我们需要它 版权声明 本文由@leftnoteasy发布于 http://leftnoteasy.cnblogs.com, 如需全文转载或有其他问题请联系whee ...
- java transferto_小六六学Netty系列之Java 零拷贝
前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger种一棵树最好的时间是十年前,其次是现在 我知道很多人不玩qq了 ...
- win7录屏_学用系列|清晰、体积小,这些录屏工具适合正在为录屏苦恼的你
2月10日,多家教学直播平台遭遇"教育双十一"的冲击,不少老师都感受到直播进不了课堂的苦,老师们不禁发出这样的困惑,直播那么难,我们可以录屏+视频网站发布吗?当然,不过,录屏软件那 ...
最新文章
- 【python gensim使用】word2vec词向量处理中文语料
- mongodb数据库显示obj_MongoDB基础(三)—基本操作及管理 | leon的博客
- IE6/IE7下:inline-block解决方案
- 基于numpy实现线性回归问题分析
- GIS实战应用案例100篇(七)-基于GIS和ENVI的矢量化提取水体边界
- LSGO软件技术团队2015~2016学年第十一周(1109~1115)总结
- JavaWeb中filter的详解及应用案例
- ASP.NET工作笔记014---用VB.NET封装服务器端控件
- PDE7 wave equation: intuition
- 电力用户用电信息采集系统通信协议报文解析示例
- 华为模拟器 ENSP 教程
- 关于mysql卸载不干净
- MATLAB手写数字识别
- 微信公众号开发(个人订阅号版)
- 【JAVA基础】Java基础之-代理详解
- 走进“开源SDR实验室” 一起玩转4G/5G开源项目srsRAN(srsLTE升级版):安装方法+NB-IoT测试
- Linux加密框架中的算法和算法模式(2)——模式介绍
- WPS JS宏表格定位实例
- Centos显示“用户名不在sudoers文件中,此事将被报告”
- 【论文笔记】Semi-Supervised Active Learning with Temporal Output Discrepancy(ICCV 2021)
热门文章
- linux系统32和64的区别,32位和64位的Linux系统区别
- 每周好书推荐《那些古怪又令人忧心的问题》
- 社区团购热潮退却,谁是电商卖菜“接盘侠”?
- 华云数据出席“云网芯安密屏”讨论会:加快数字化转型 推动湖北信创产业快速发展
- java方法声明无效_Java错误 - “无效的方法声明;需要返回类型”
- 深度学习高温蒸馏:Softmax With Temperature
- 面向对象(继承,多态,单态,魔术方法)
- day09-面向对象综合训练综合练习
- Win10笔记本扩展显示屏模糊处理办法
- from..import 语句