之前介绍了指针是什么,以及简单的指针的使用,接下来我们就来研究一下指针的一个重要应用:使用指针来操作数组
1 指针和数组
首先我们来观察数组
#include
数组是一系列类型相同的数据的集合,我们可以通过数组名[索引]的方式打印出数组元素的指
那么如果我们直接打印数组变量会发生什么事情呢
#include
会输出一个值,那么这个值代表了什么
我们可以利用指针和取地址符来观察一下
#include
我们可以发现数组名和指向数组的指针,以及数组第一个元素的地址他们的值是相同的
也就是说,数组名可以代表数组的地址,数组的地址和首元素的地址是相同的,我们定义一个指针指向数组,和定义一个指针指向数组的首元素是一样的
同时我们也了解指针的运算,那么如果把指针加1,就相当于移动了一个int长度,这时指针刚好指向了数组的第二个元素
#include
在这里a[1]代表数组的第二个元素,p+1,指向了第二个元素的地址,所以 s[1]==*(p+1)
这里需要注意的时取值运算符*的优先级大于+运算符,所以要加括号,否则计算的就是(*p)+1得到的结果就是第一个元素的值加1了
那么我们可以推导出来一个公式:
假设a是数组,n是元素索引,p是指向数组的指针那么数组的第n个元素使用指针来表示就是*(p+n),也就是说 a[n]和*(p+n)是等价的
如果需要遍历数组的元素,那么使用数组的索引和使用指针都可以完成
#include
2 指针和二维数组
我们在理解二维数组的时候通常会把二维数组理解为有行列的表格
int a[2][3]={1,2,3,4,5,6}可以理解为
但是实际上的储存形式是线性连续的
那么如果需要获取第二行的元素a[1][0]就需要我们移动3次
#include
这显然不是很方便
对于二维数组,如果需要去找到里面的元素,那么就需要一个能够一次跳过整行长度的指针来遍历数据
,我们需要修改一下指针的类型,把p的类型声明为 int (*p)[3]
这个声明语句比较复杂,需要仔细说明一下
在c语言的声明语句当中,需要把握住一个就近的原则,之后我们会看到各种非常复杂的类型声明,只要把握住这个原则就不会出现错误了
- 首先需要找到声明的变量
- 其次找到和变量结合性最高的运算符,这个时候定义的类型就是变量的类型
- 然后根据优先级依次判断
首先找到变量 p ,其次由于小括号的存在*的结合性最高,所以p是一个指针,然后是[],说明这个指针指向了一个数组,最后是 int ,说明数组类型是int类型
总结出来就是 p 是一个指向整数类型数组的指针
再看声明 int * p[3] 和刚才的表达式只差一个小括号,那么声明出来的类型就完全不同
这里[]的优先级高于*,所以 p 是数组,然后找到 * , 说明这是一个保存指针数据的数组,最后确定保存的指针是int型的
总结出来就是一个保存 int指针的数组
那么知道我们的指针是什么意思以后,就可以直接找到二维数组的指定的行
#include
这里的a[1]是二维数组当中的一行,我们依然可以把他看作一个数组,a[1]是数组名代表的是第二行的地址,这和我们移动一次指针的效果是一样的
接下来就需要取出a[1]这个数组当中的元素值了
#include
我们发现并没有成功
这是因为 p 是一个指向数组的指针,取值之后得到的是一个数组,也就是说*p是一个数组,直接打印数组得到的还是一个数组地址,所以需要取两次值
#include
那么如果我要取出a[1]的第二个值,就可以把第一次取出来的值当做数组就可以了
#include
其实我们依然可以完全通过指针移动来实现
#include
这里的 *(*(p+1)+1) 是一个比较复杂的表达式,我们需要仔细看一下
- p是指针,*p是指针取出的值,是一个 int 类型的数组 也就是说 *p 和 a[0] 是一致的;
- *(p+1) 也是一个 int 类型的数组, *(p+1) 和 a[1] 是一致的
- *(p+1)+1,表示把 数组的指针移动一位 , *(p+1)+1 相当于把 指向a[1]的指针移动一位,这个时候 *(p+1) 的类型不再是指向数组,而是指向数组的元素
- 取值 *(*(p+1)+1) 就是指针指向的元素的值
关注点要放在当前表达式的数据类型是什么,是指向元素,还是指向数组
p和p+1 类型相同是指向数组的指针
*p和*(p+1) 类型相同是指向元素的指针
**(p)和**(p+1)以及*(*(p+1)+1)类型相同是取出的数值
可以通过*的数量来确定类型,没有*就是数组指针,一个*就是元素指针,两个*就是值
因此我们在取二维数组值的时候可以有一个公式
这 a 是二维数组,p是数组指针,m是行号,n是列号
a[m][n]就相当于 *(*(p+m)+n)