序言
指针这个模块是C语言里面比较难理解的的,学习成本倒是不高,就是有点费脑子.我们这里重点关注什么是指针和指针的用法.这篇博客我重新写了写了一遍,原来的那个实在太简陋了,里面新增了一下内容.
地址
谈到指针我们不得不说一下地址.什么是地址呢?地址就是能够标识一件事物的确切位置.这里有一个例子.张三是你的同学,一天,张三给你打电话,说李四,今天你来我家吧,我家在XXX小区XXX号楼.说完就把电话挂了.这时候你急冲冲的跑到张三的楼下.这时候你就犯难了,我不知道他在几层哪个房间啊?你需要再次打电话询问房间号.这个时候房间号就是一个地址.
指针是什么
我们已经明白了地址的意思,那么指针又是什么呢?这里我们类比一下,我们把这栋楼看作是计算机的内存,门牌号就是地址,不过我们在计算机中更加喜欢把这个地址称为指针,也就是说指针就是地址.
我们已经知道了指针的含义,那么我们是如何把内存给划分的呢.一栋楼可以被划分为多个房间,每一个房间就有一个门牌号,我们可以把一个房间看作是楼的划分单位.在计算机中,我们把内存的最小单元字节看作划分单位.我们给每一个字节都编上序号,就像是房间的门牌号一样,我们得知门牌号就可以找到那个空间.
这里的门牌号就是我们说的地址,也就是指针,我们仔细验证一下
我们需要把这个int a = 10这个语句给拿出来说一下.首先是关于变量a的,我们在内存中开辟一块空间,把a的数据,这就就是10给放进去.既然开辟了空间,那么这块空间肯定存在编号,这里的编号也就是指针就是a的地址.
假设你的电脑是32位的,这里我们笼统的解释一下,可以看作你的电脑存在32位地址线,每一个地址线都可以表示0或者1,也就是我们可以得到0到232-1共232个数据,我们把这些数据作为编号,用它来充当地址.等下在指针的大小那里我们会重点分析一下.
指针和指针变量
这个话题可能很少有人谈到,我们在看一些编程语言的书籍时会发现一会儿说指针,一会儿说指针变量,那么他们究竟有什么区别?这里分析一下.
我们看下面的代码,我们知道a是一个int类型的变量,他存储的是10,那么我们是不是也可以按照这个思路理解p也也是一个变量,存储的的是a的地址.,实际上我们可以这么做.
这里总结一下.
- 地址就是指针,指针就是地址
- 指针变量是一个变量,里面存储的是地址.
- 二者有区别,不过我们一般将指针变量说成指针
为什么定义指针
这个问题非常好,指针这么难理解,我们为什么还要定义这个东西呢,不是自找麻烦吗?这里我给出两个原因,我这里就解释第二个.第一个等到进阶的部分在和大家谈.
- 简化代码
- 参数传递
&a就是指针 p就是指针变量这里有一个问题,我们要求写一个函数,函数的功能是交换两个变量的值.我们该如何实现.
这里我们就疑惑了,我们不是实现了两个值的交换吗,这里为何没有变化.我们在函数那里就明白了,函数形参是实参的一份拷贝,我们改变形参是不会影响到实参的,那么我们如何修改原本的数据,这里就要用到指针了.
这里我们要谈一下为何这里可以实现.我们这样想,我们把a和b看作是一个上锁的箱子,指针就是钥匙.我们函数传参了,形参是实参的一份拷贝,不就是重新打造一个一摸一样的钥匙吗.这里的*就是开锁的动作,我们后面还要谈这个运算符,相同的额钥匙是可以打开同一把锁的,这就完成了函数的功能.
指针
现在我们就可以好好谈一下指针了,这里的指针还是比较简单的,主要是为了后面的进阶部分打下基础,不过要想理解透还是很困难的.我们先来谈一下指针的大小.
指针的大小
前面我们谈过,32位平台下,电脑存在32位地址线,每一个地址线都可以表示0或者1,也就是我们可以得到0到232-1共232个数据,我们把这些数据作为编号,用它来充当地址.那么我们就需要32个比特位来存储这个个指针,我们知道1字节等于8个比特位,那么32个比特位就是4字节.我们等下测试,这里想换算一下32位平台总共有多少指针.我们知道了共有2^32个指针,按照下面的换算方法.也就是说32位平台下,指针共有4g.
这里测试一下不同的类型对应指针的大小,结果和我们说的一样.
同理在64位机器下,我们指针的大小是8,共有16g的指针.
类型
这里就有问题了,既然我们每一种指针类型的大小都已经确定了,那么还设置这么多了类型干什么,直接用一个类型代替不久完了吗?这里就有很大意思.,需要我们一一分析.
- 指针类型决定解引用可以访问几个字节
- 指针类型决定 +1 可以走几步
指针的解引用
我们先来看下一下下面的用法,我们解引用赋值的的时候改变的是4个字节,这一点我们很容易接受,毕竟int类型是4个字节.
如果说上面的我们很容易理解,那么下面这个你就会很迷惑.
我们把int*指针强制类型转换成char*,这里去解引用,我们发现只能修改一个字节.这里我们就可以下一个结论了.
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节).比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节.
指针+-整数
我们先来测试下下面的额用例.这个还是比较简单的,不过有的选择题会考这个这个,而且还是比较困难的,我们这里简单的简绍,等到后面专门出一个博客,好好分析一下相关的题目.
这里我们下一个结论,指针的类型决定了它加1跳过几个字节,而这个字节是跟解引用得到的类型有关.
指针运算
我们在谈一下指针的两个运算.都是比较简单的.
指针 - 指针
指针-指针得到的是两个指针之间元素的个数,我们知道指针是第一个字节的地址.
这里我们就可以模拟实现一下 strlen函数了
指针比较
指针比较这个知识点我就说一下,我们一般谈的是高地址、低地址,也可以说成大地址、小地址.这就可以比较打大小了.这个知识点主要用在双指针的题目中,这里我们以逆置字符串为例子.
野指针
我们先来解释一下什么是野指针.现实生活中,我们可能会遇到野猫野狗,它们没有人管理,有可能会伤人.在计算机中我们把这种不受人管理的指针称为野指针. 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的.
现在我们类看一下未初始化的的情况,不同的编译器对未初始化的指针的处理是不一样的,我们VS的这个比较严格,不经初始化的是禁止使用的.
第二类就是这个指针前面是可用的,后面出了作用域这块空间就会被释放,新手总是喜欢返回栈指针.下面的代码是一定错误的,要知道a出了函数空间就会被销毁,你返回它的地址有什么用,里面的数据又不确定.
我们测试一下代码,你就会明白了.
你一看这个不是很正确吗,那么你再试试多打印一次,相同的语句,得到不同的结果.这个是和函数栈帧有关,到时候你看了就会明白.
还有一类情况,这个情况是我们以后最头疼的,访问越界.准确来说以后边界问题是我们的难点,这里简单的说一下.前面的10次这个指针都是正常的,当我们访问下标是10的这个时候就是访问越界,指针就变成了野指针.
我们总结一下,可能造成野指针的情况.
- 未初始化
- 释放了,但是再次使用 返回 函数栈指针
- 访问越界
这里说一下,我们在应用层面是检测不出野指针的,只有在运行时编译器有可能给我们报错,所以对于我们不在用的指针我们让他变成NULL.这样就可以减少野指针的出现概率,使用的时候,最好还是判断指针的有效性.
二级指针
我们知道了int的地址是指针,那么指针变量的地址又是什么?我们对指针变量取地址得到就是二级指针.同一我们可以一层一层套娃下去,得到三级、四级...不过我们一般就用到三级指针.
指针数组
我们在谈初阶最后的一个内容,指针数组.就像我们好孩子一样,我们的重点是孩子,同理指针数组是一个数组,只不过这个数组存储的是一个指针.
这里的parr就是一个指针数组.我们这里主要是分变这个东西究竟是什么,这个方法在进阶里面很有用,这里算是一个铺垫.首先我们知道[]的优先级高于*,所以parr先和[]结合,表明它是一个数组,此时我们再判断,我们看到一个*,该数组,每一个元素是一个指针,再判断一下,就剩下int了,那么指针的类型是int*.