说好的今天讲指针,说到做到啦
1.要学好指针首先要了解什么是指针,首先给个定义它是一种用来存储变量地址的变量类型,通常我们也会说指针其实就是一个地址,下面先看一段代码叭
这段代码的大意就是我定义了一个整型变量,并赋给它一个值,之后我将这个整型变量a的地址给指针p,最后我就可以通过p去找到a的值,就相当于你把你家的地址给我,我就可以通过这个地址找到你家(当然啦有点废话文学),不过非常形象。
2.解引用(用于访问指针所指向的内存单元的值)
当我们用指针访问到a的地址后,就可以不用直接操作a进而改变a的值,可以直接在a的地址中改变a的值(当然a的地址不变,只是地址中存储的值发生了改变),通过这个方法我们可以进行一些“非法”操作啦
比如我们将a定义成一个具有常属性的变量,就无法直接对a的值进行改变,这时我们就可以用解引用,例如这样
(c++中不支持该操作,编译器会报错)
1.注意我上文对指针p的定义是int*,那么会有人问如果写成int * p或者int *p可以吗,答案是肯定的啦,它们本质是一样的,那么又有人要问了为什么非要用int嘞,char/short/long这些不行吗?
对于这个问题就涉及到我们接下来要讲的指针变量啦。提到变量我们首先就要考虑这个变量占多少字节
当然这里我就用了int char两个类型,我们不难发现在debug状态下x64中任意指针变量的字节都是8,x86中任意指针类型的字节都为4。那么有人就要问了,release状态下呢,这里我没贴图,但结论就是release下任意指针类型(不论x64x84)的字节都为4。
这时又有人要问了,那既然它们在同一环境下字节相同,为什么还要定义不同的变量名呢,这就涉及到我们接下来所讲的以及指针运算。
贴上一个错误代码示例
编译器会给出类型不兼容的警告,所以指针变量类型和你要访问的变量类型要保持一致。
当然所有的指针变量中有一个极为特殊,void*可以接受所有变量类型的地址并不会触发类型不兼容的警告,但它的缺陷就是不能进行解引用操作和接下来要讲的指针运算。
再看两组代码
通过这两种代码我们不难发现当指针前移或后移时,移动字节并不相同,int移动4个,short移动2个,char移动1个,这也是不同指针变量设置不同变量名的意义之一。
2.const修饰指针变量
众所周知,const可以用来定义常量也可以用来修饰变量,当然它也可以用来修饰指针变量(有两种情况)第一种例如
对*p进行const修饰,造成的结果是无法用解引用操作,因为此时的*p被定义成一个常变量后不能修改其中的值。
第二种例如
此时用const对p进行修饰,所以*p不受影响,依旧可以被重新赋值。
上面讲啦许多与指针有关的知识,但好像没有太大的实际价值,接下来讲一下指针的实际应用——指针运算。
1.指针+整数
这两个算法都是用指针遍历数组然后打印,把数组中第一个元素的地址存进指针,然后顺藤摸瓜找出数组中剩余元素的地址。
2.指针-指针(地址-地址)
大家觉得最后打印出来的值是多少,相信大部分人会说36(因为int类型指针移动4个字节),但结果却并不是36,而是9。
所以记住这么一句话指针-指针的绝对值是元素的个数,那为什么是绝对值嘞,再看一组代码
所以懂了叭!
我们都知道C语言中有个函数叫做strlen,统计一个字符串中出\0外的字符个数
就像这样,那么接下来我们可以用指针-指针自己设计一个类似于strlen函数的函数,来更直观的体验一下指针-指针:
具体代码如下
见证奇迹的时刻,你会发现它和C语言标准函数strlen的功能一样。
野指针是指指向一个已释放或者未分配的内存地址的指针。这种情况通常会导致程序崩溃、数据损坏或者安全漏洞等问题。野指针的成因有以下几种情况:
1. 未初始化的指针:在使用指针之前没有对其进行初始化,导致指针指向一个随机的内存地址。
2. 释放后使用指针:在释放动态分配的内存之后仍然使用指向该内存的指针。
3. 越界访问指针:指针指向超出分配内存范围的地址。
4. 空指针解引用:对一个空指针进行解引用操作。
5. 指针运算错误:在指针运算过程中出现错误,导致指针指向错误的地址。
为了避免野指针的出现,可以采取以下措施:
1. 初始化指针:在使用指针之前,务必将其初始化为一个有效的地址,例如 NULL。
2. 使用智能指针:智能指针可以自动管理动态分配的内存,避免手动释放内存时出现错误。
3. 检查指针有效性:在使用指针之前,通过检查指针是否为 NULL 或者是否指向有效的内存地址来确保指针的有效性。
4. 使用范围检查:对于数组或动态分配的内存,使用范围检查来避免越界访问。
5. 避免指针运算错误:确保指针运算的正确性,避免指针指向错误的地址。
6. 使用错误处理机制:在程序中添加错误处理机制,捕获和处理可能出现的野指针错误。