指针详解
这篇质量不太行:(
内存和地址
在了解指针之前,先讲讲内存是如何管理的
首先因为内存很大(一般有几个G),所以为了高效管理,有了内存单元
的概念。而这个单元的大小,正好是一个字节。
因为一个比特位
就是一个二进制位,太小了,超过一个字节,在处理char
这样一个字节长的变量很麻烦。
定下长度后,就可以给内存单元编号,而每个内存单元获得的独一无二的编号,便是它的地址,以声明了一个变量a为例,示意图如下
变量的地址
上图中a
占4个字节,每个字节都有自己的地址,但要找到a
其实只需要找到第一个地址就行了,实际上在C语言
中也是如此,a
的地址就是首字节地址
,即图中的0x000000AF88DFF6A4
关于几个名词
在C语言
中称地址
为指针
,储存地址的变量叫指针变量
,平时也简称指针
,此时强调的是指针变量
里储存的地址,而不是这个变量。
指针变量的组成
指针变量也要拆成两部分来看
一个是变量的值
,在同一个程序中,所有指针变量的值的长度
都是一样的,都指向了某一个内存中的字节
, 至于具体多长,取决于环境:32位程序是4个字节
,64位程序是8个字节
另一个是变量的类型
,类型决定编译器从值
所指向的字节,向后总共读几个字节,以及用什么方式读取内存里的内容。以下图的代码为例
可以看到三种指针指向了同一个字节,即它们的值是相等的,但指针类型
不同,解引用之后得到的值
也不同,
char
比int
短,所以*p_char
只能取到00
,
而虽然float
和int
一样长,但对内存的读法不同,所以*p_float
和*p_int
依然不同
指针(变量)的使用
声明指针变量
指针变量也是变量,在没有结合性问题时,和一般变量的声明方式差不多。
变量的声明:变量类型
+ 变量名
指针的声明:指向的变量类型
+ *
+ 变量名
以下以声明一个字符指针为例
1 | char* pointer = NULL; |
变量的声明逻辑如上图
进阶:二级指针->N级指针
我们可以用同样的逻辑声明更高级的指针
1 | char* *ppstr = NULL;//ppstr是一个二级指针 |
在声明中,前面的char*
声明了ppstr
指向的变量类型,后面的*
与变量名
结合,声明ppstr
是一个指针.
此处,称指向一级指针
的指针为二级指针
,同理有三级指针
,至N级指针
.
指针的解引用
指针最常见的用处就是通过变量里储存的地址,通过直接修改目标变量的内存来修改变量的值 , 当然还有强制转换指针类型来读取目标变量的一部分内存 之类的骚操作
函数的传址调用
在遇到指针前,使用函数时,由于实参传到函数里都变成了形参,无法通过形参(包括修改形参的值)来改变实参的值,因为形参终究只是实参的一份临时拷贝.
而有了指针之后,函数的实参,形参关系不变,但我们有了更高端的形参,也就是指针
, 尽管函数内的指针
依旧是函数外的指针
的临时拷贝,但我们已经能通过其储存的值访问函数外变量
的内存了,同时包括读取
和修改
, 这种通过传入指针来修改外部变量的函数调用,便称为函数的传址调用
以如下代码为例
1 | void Swap_int(int*a,int*b) |
提问?如何修改函数外的指针的值?
依然还是把这一指针的地址传进去,而函数的形参写成更高一级的指针
如下代码,例如我想在函数里把外部的指针置空
1 | void Reset(char* *pstr) |
有关指针的危险操作
野指针的解引用
有些指针因为错误操作,指向了不能访问的内存,一旦解引用,就有可能使程序崩溃
情形如下
使用了 未初始化/赋值 的指针
1 | int* pa;//未初始化,pa的值为随机值 |
所以声明指针时最好初始化,如果不知道初始化成什么,就用NULL
空指针初始化
指向了 已回收的 内存空间
有的函数错误*地返回了内部临时变量的地址, 在外面使用返回的指针,因为此时函数的栈帧已经销毁,会发生野指针的解引用
1 | char* fun(void) |
空指针的解引用
空指针NULL
,值
为0
,一旦解引用就会报错,所以在解引用陌生指针时一定要注意判空
1 | //情形一 |