指针这种东西,是需要记录下来才能理解的比较清晰,所以专门记录一下指针的一些知识点:
- 指针理解
- 指针也是一种数据类型,和其他各种数据类型基本一样
- 指针有几个要素
- 指针的的值,就是该指针变量的值,该值就是一个内存地址的编号,这个内存地址就是该指针所指向的变量所处的内存地址
- 指针变量的变量名,该变量名和其他各种数据类型的变量名没有任何区别,都是一个变量名而已
- 指针可以指向任何数据类型,通过指针的值,从而找到对应数据类型的值
- 由于存储的是内存地址,所有的指针变量的取值范围都是固定的,不会超出内存空间的长度大小
- 指针变量自身也存在于内存中,所以指针变量也有内存地址,因此别的指针变量也可以指针向这个指针变量,这样就构成了多级指针,例如
int *a = &x
,而int *b = &a
,则a就是单级指针变量,b就是多级指针变量 - 指针本质要做的就是通过内存地址来间接访问变量值,增加了一种访问变量值的方法,由于引入了内存地址的概念,使得C可以操作内存中的数据
- 要在printf中打印指针变量的值,用%p做占位符
- *号的应用
- *号在数据类型和变量名之前的时候,表示定义一个指针变量,*后的变量名就是该指针变量的名字
- *号单独位于指针变量之前,表示取出该指针变量所指向的变量的值
- 指针变量有几级,取出最终原始变量的值就需要使用几个*
- &号的应用
- &号位于变量之前,表示取该变量的内存地址
- 指针与数组
- 首先要明白概念,数组的内存地址就是数组的首元素的内存地址
- 数组名有多重作用:
- 首先,数组名是整个数组的代表,通过数组名[i]可以依次访问数组中的每个元素
- 其次,数组名的值就是整个数组的内存地址,里边存放的就是数组的内存地址,所以数组名可以直接赋值给指针变量,且不需要取地址符&,例如:
int a[3]; int *b = a;
,可见数组名可以完全看作一个指针变量, - 再次,由于数组的内存地址就是数组首元素的内存地址,所以数组名的值就是数组首元素的内存地址,则可以得出如果
int a[3];
则a的值等于&a[0], 那么&a[0]则可以直接赋值给指针变量,可以得出int a[3]; int *b = &a[0];
,和上边赋值的效果是一样的
- 使用指针操作数组,实际上是操作的数据的内存地址
- 假设有
int a[3]; int *b = a;
,则可以使用指针变量b来获取数组中的每一个元素的值,操作方式是*(b + i)
,由于b的值就是首元素的地址,那么获取首元素的值可以直接使用*b就可以拿到,
- 指针的运算
- 指针可以进行加减乘除运算,乘除运算一般在指针上没有意义,所以通常指针都进行的是加减运算
- 指针的加减运算单位不是以内存基本单元byte为单位的,而是以指针所指向的数据类型所占的内存大小为单位的
- 参照这个公式:假设有
int a[3]; int *p = a;
,则指针运算”p + n”等同于p的值加上sizeof(int) * n个byte,最中的结果将会停留在第n个元素的内存地址处,例如数组的内存地址为0x00001,则p初始化后值也为0x00001,则运算”p + 2”的结果等于0x00001 + (sizeof(int) = 4) * 2 = 0x0009,而0x0009就是第三个元素的内存地址 - 通常情况下在数组中会使用指针运算,但是不排除也会在其他数据类型中使用指针运算。例如结构体
- 指针运算本质上也是变量的运算,所以当然也可以使用++,–等运算符
- 指针++与–运算符的结果与+1和-1的结果一样
- 原则上,由于++,–运算符的优先级与的优先级一样,那么在进行*p++这种运算的时候,就要看两个运算符的结合方式了,最终得出先算++后算的顺序,那么可以省略了p++两边的括号,可见这种运算方式比较简洁,并且可以得出公式:”p++” = “*(p++)” = “(p+1)”
- 可是实际情况不如我们所料,实际情况中p++并没有得到下一个数组元素的值,原因是因为p++是后置++,有先运算后++的特性,则我们的*p++的执行顺序变成了先算号后算++,所以得到的结果还是当前指针指向的结果
- 如何修复呢?
- 可以在p++外面加上括号,提高该部分的优先级,则会是这样:*(p++)
- 可以把++前置,组成先++后运算的顺序,则会是这样:*++p
- 可以得出:
- *p++会直接得到当前元素的值,直到运算完毕之后,才会将p进行++,可以得出公式:”*p++” = “(*p)++”,这一点在参与运算的时候要注意
- *(p++)会得出下一个元素的值,可以得出公式:”*(p++)” = “*++p” = “a[n+1]”
- –运算符与++运算符思路一样
- 函数的指针参数
- 函数列表中的参数类型可以是指针类型,例如:
int add(int *a,int *b)
,这里的形参格式与定义指针变量的格式是一样的 - 参数被定义为指针的函数,实参必须是一个指针变量,假设
int *a = &x;int *b = &y;
则1中的函数需要这样调用:add(a,b);
- 由于基本数据类型的函数参数是传值调用,所以2中的函数调用过程中,指针变量a,b的值将会被拷贝一份传递给形参,而a,b的值不会受到任何影响,
- 由3可知,形参就是一个指针变量,形参的值其实是一个内存地址,那么在函数中,我们就可以随意操纵指针所指向的变量的值,由于我们操纵的是内存中指针,所以内存中的数据是会确切的发生改变的
- 例如swap函数,形参得到的实参的拷贝,获取到内存地址之后进行运算,最终会将实参所指向的变量的值进行改变,而实参自身却没有发生任何变化
- 通过这样的运算可以得出,类似于*p这样的取值运算,操作的是指针变量的值所指向的那个变量,而跟指针变量名无关,例如两个变量名不同的指针变量p1和p2,有着相同的值,那么通过
*p = 123;
这样的运算之后,p2指针所指向的变量的值也会被修改为123,由此可见操作内存地址的重要性
- 函数列表中的参数类型可以是指针类型,例如:
- 指针与const
- const在*p之前,表示p的值可以变化,但是p指向的是一个常量,它的值不能改变
- const在*和p之间,表示p是一个常量指针,p里边的值不能变化,但是p所指向的变量的值可以发生改变
- 如果1和2都符合,则表示一个常量指针指向一个常量