目录
一、指针是什么
1、内存
2、内存的管理与使用
3、指针变量的使用
二、指针和指针类型
1、指针类型的意义
2、指针+ 或 - 整数
3、指针解引用
三、野指针
1、野指针成因
2、如何规避野指针
四、指针运算
1、指针+-整数
2、指针 - 指针
指针 - 指针的运用(求字符串)
3、指针的关系运算(比较大小)
五、指针和数组
六、二级指针
1、什么是二级指针
2、二级指针的运算
七、指针数组
1、什么是指针数组
2、用一位数组模拟二维数组
一、指针是什么
这部分在以往初识C语言将的比较细点(初识C语音---指针),这里就简单为大家回忆一下
1、内存
想要理解什么是指针,必须先了解什么是内存。内存是电脑上的存储设备,一般都是4G/8G/16G等,程序运行的时候会加载到内存中,也会使用内存空间,例如任务管理器:
2、内存的管理与使用
我们将内存划分为一个个小格子,每一个格子是一个内存单元,大小为一个字节,对每一个内存单元进行编号,假设未来要找一个内存单元,就可以通过编号很快的找到,我们把这些编号叫做地址,而地址在c语言中又叫做指针。
举一个例子,在下图中,假设定义一个变量 int a=10,一个int类型的变量,需要占4个字节的空间,而每个空间都有地址,&a取出的是哪一个的地址呢?其实取出的是第一个字节的地址(也就是较小的地址),也就是说,&a最终取出来的地址是0x0012ff40 ,当然,可以把这个地址存到一个变量中,int* pa=&a,*表示pa是一个指针,int代表pa所指向的类型是int类型,这个pa也叫做指针变量(它是专门用来存放地址的)。
指针理解的2个要点:
1、指针是内存中一个最小的单元编号,也就是地址。
2、平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
总结:指针就是地址,口语中说的指针通常指的是指针变量。
3、指针变量的使用
通过以上代码就验证了,指针变量p存放的就是a的地址。
接下来,我们可以使用*解引用操作符来对其使用
总结:指针变量,就是用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那么问题来了:一个内存单元到底是多大? --- 刚刚讲过,就是一个字节
那它又是如何编址的呢?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
.....
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。 一个地址管理一个内存单元,那么2的32次方个地址就能管理2的32次方个内存单元,也就是2的32次方个字节,那2的32次方个字节又是多大空间呢?
根据进制转化:
2^32Byte = 2^32÷1024KB÷1024MB÷1024GB = 4GB
同样的方法,64位机器,也可以计算出来。
这里我们就明白:
•在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
•那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
•指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
•指针的大小在32位平台是4个字节,在64位平台是8个字节
二、指针和指针类型
1、指针类型的意义
通过上面我们知道了
32位机器,地址是4个字节,指针变量大小是4个字节。
64位机器,地址是8个字节,指针变量大小是8个字节。
这时有人就很疑惑,假设在32位机器上,大家都是4个字节,直接搞一个通用指针不就完事了,那为什么要区分int*、char*、double*这些呢?那它肯定就有特殊的意义。
先看看下面的代码
我们可以通过调试来观察变量a在内存中的变化
调试:先按下F10
为了方便观察,我们把它调成4列
我们发现,内存中确实存的是44332211,只不过是倒着放的,这个后期会讲。
接着按F10再往下走
我们发现,4个字节全部被改为0,这说明a的值确实被修改成0了
假设我把指针变量pa的类型改为char*结果又会是如何呢?
可以继续通过观察其内存变化
继续按F10
它只改变了1个字节!!
总结:指针类型其实是有意义的
1、指针类型决定了,指针在进行解引用操作的时候,一次性访问几个字节
如果是char*类型的指针,解引用访问内存中的一个字节
如果是int*类型的指针,解引用访问内存中的四个字节
float*和double*也同样如此......
指针类型还要其它意义,接着往下看
我们发现,pa+1跳过了4个字节,pc+1跳过了1个字节
总结:
指针类型的意义2:指针类型决定指针的步长(指针+1到底跳过几个字节)
字符指针+1,跳过1个字节
整型指针+1,跳过4个字节
其它类型的指针也是如此......
它的用途是什么呢?
举个例子
因为它是char*类型的指针,第一次循环改变一个字节,直到4次循环后,就把a改成0
最后再次总结指针类型的意义:
1、指针类型决定了,指针在进行解引用操作的时候,一次性访问几个字节
如果是char*类型的指针,解引用访问内存中的一个字节
如果是int*类型的指针,解引用访问内存中的四个字节
float*和double*也同样如此......
2:指针类型决定指针的步长(指针+1到底跳过几个字节)
字符指针+1,跳过1个字节
整型指针+1,跳过4个字节
其它类型的指针也是如此......
2、指针+ 或 - 整数
除了上面的+1,还可以-5或者+4等
注意:指针+一个数,是往高地址走,指针-一个数,是往低地址走
总结:指针的类型决定了指针向前或者向后走一步有多大距离。
3、指针解引用
这个在上面已经讲过了
总结:
指针的类型决定了,对指针解引用的时候有多大权限(能操作几个字节)。
比如:char*的指针解引用就只能访问1个字节,而int*解引用就只能访问4个字节
三、野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
相当于“野狗”
1、野指针成因
①指针未初始化
②指针越界访问
③指针指向的空间释放
解析:首先a是一个局部变量,根据局部变量的生命周期,出了它的作用域,生命周期结束。返回的时候a的地址就已经被销毁了,此时的地址就是一个野指针。但是运行的时候结果还是10,这只是侥幸,这是因为之前的内存空间还没有被覆盖。我们加上个输出语句就能破坏掉这个内存空间。
2、如何规避野指针
(1)指针初始化
(2)小心指针越界
(3)指针指向空间释放,及时置NULL
(4)避免放回局部变量的地址
(5)指针使用之前要检查有效性
四、指针运算
1、指针+-整数
上面已经讲过了,这里再举一个例子
解析:看下图就很容易理解代码循环了,但这里要主要循环体内的代码,*的优先级是比++操作符要高的,而++是后置的(先使用,后++),所以这代码的意思是把数组内5个元素全部赋值成0
2、指针 - 指针
运算的前提条件:两个指针要指向同一块空间(同个数组)
运算结果是:相减绝对值的结果就是两个指针之间的元素个数
解析:它是如何得到9的呢?看下图你就明白了
指针-指针的运用
之前我们求字符串长度是这么写的(不能用strlen的情况下)
其实还能这么写
解析:
注意:++千万不要放到循环表达式中,因为不管是前置还是后置++,它都会有副作用,会导致return的结果有误差 。
3、指针的关系运算(比较大小)
那么接下来我把代码简化下面这样
其实,简化过的代码实际在绝大部分的编译器上是可以顺利完成任务的,然而我们应该避免这样书写,因为标准规定并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
五、指针和数组
1.指针和数组是不同的对象
•指针是一种变量,存放地址的,大小4/8字节的
•数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素类型
2.数组的数组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针访问数组
如何用指针来访问数组呢?
或者还能这么写
总结:
int arr[10];
int* p = arr;//首元素地址
这里有一层等价关系
arr[i] == *(arr + i) == *(i + arr) == i [arr]
i[arr]是可以正常使用的,因为 [ ]只是一个操作符,i和arr是[ ]的操作数而已(就像a + b同样也能写成b + a)。在编译的过程中,arr[i]也会被翻译成*(arr+i)
六、二级指针
1、什么是二级指针
a的地址存放在指针变量pa中,我们称pa是一级指针。指针变量也是变量,是变量就得有地址,pa的的地址存放在ppa中,所以我们称ppa是二级指针。
这里再解释一下,pa前面一颗的*告诉我们pa是指针变量,而pa指向的a是int类型,所以pa的类型就是int*;同样的,ppa前一颗的*告诉我们ppa是指针变量,而ppa指向的pa是int*类型的,所以ppa的类型就是int**。
a、pa、ppa关系如下:
2、二级指针的运算
七、指针数组
1、什么是指针数组
指针数组是指针还是数组? --- 是数组(存放指针的数组)
可以类比整型数组和字符数组
整型数组是存放整型的数组
字符数组是存放字符的数组
举一个简单的例子
2、用一位数组模拟二维数组
假设要模拟三行四列的数组
思路:
开辟一个数组用来存放3个数组首元素的地址,因为数组在内存中是连续存放的,所以知道首元素的地址,后面也自然而然也就跟着知道了。
代码实现: