目录
前言
一、数组名的理解
二、使用指针访问数组
三、一维数组传参的本质
四、二级指针
五、指针数组
六、数组指针
1.数组指针的初始化
2.数组指针的使用
七、二维数组传参的本质
总结
前言
前篇文章主要介绍了指针的基本知识,那么本篇讲重点介绍C语言中的数组与指针的关系,内容较丰富,主要内容有使用指针访问数组、一维和二维数组传参的本质、二级指针、以及指针数组和数组指针等。干货满满,希望对大家有所帮助
一、数组名的理解
我们最开始对数组名的理解是:数组名为数组首元素地址
如:
如上数组名确实是数组首元素的地址,
但是任何情况下数组名都是数组首元素的地址吗?
其实不然,数组名确实是数组首元素地址,但是有两个例外:
- sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小, 单位是字节
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
验证:1.sizeof(数组名)
如上,sizeof(数组名)确实是计算了整个数组的大小,以往我们都知道,sizeof是计算变量类型的大小的,比如放一个整形在sizeof里返回的就是4。因此把数组名单独放在sizeof里的时候,数组名表示的是整个数组
验证:&数组名
注意观察:&arr+1刚好跳过了整个数组,来到了数组末尾元素的后一位,因此我们可以确认:&arr取出了整个数组的地址
当然一个指针肯定只能存储一个地址,它存不了整个数组的所有地址,但它表示的却是整个数组,因为&arr得到的是一个数组指针的类型,这个在后文会详细介绍,我们只需要知道指针+-整数前进或后退的步长与它的类型有关,这个在我指针初阶文章有讲到
总结:数组名为数组首元素地址,但有两个例外
二、使用指针访问数组
前面我们已经了解,数组名为数组首元素地址,除了两个例外,并且指针加减整数可以前后跳过地址,下面我们就可以使用指针访问数组了。
如:
关于arr[ i ] <==> *(arr+i) 我在主页文章指针初阶已介绍,这里不再赘述
由于指针接收数组首元素地址,所以p[i] 等价于 arr[i]:
三、一维数组传参的本质
首先提出一个问题,我们上文计算数组元素个数是在主函数中计算然后传给函数的,那我们能不能直接在函数中计算数组元素个数呢?反正函数也接收了数组的地址
尝试:
我们发现计算的结果并不一致,这是为什么呢?
其实一维数组传参时,数组名只是数组首元素的地址,这个地址是由函数的一个指针类型的形参接收的,而sizeof只计算类型大小,只有单独放数组名时才计算整个数组大小,而数组名传参时已经变为一个指针类型,一个指针类型只占4/8个字节,p为指针,p[0]得到一个整形,32平台下都为4字节,相除就为1,是计算不出元素个数的
我们将函数的形参int* p换成 arr1[] 也是一样的,形参写成数组和写成指针是等价的
总结:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
四、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针。
定义语法:类型** 变量名
二级指针解引用
既然二级指针存储的就是一级指针的地址,那么*二级指针就是访问一级指针存储的内容:
那么**二级指针呢,就是访问一级指针储存的地址指向的内容:
总结:二级指针用来存储一级指针变量的地址
五、指针数组
指针数组是指针还是数组呢?
我们可以简单类比一下,整形数组是数组、元素为整形,字符数组是数组、元素为字符。
那么指针数组就是一个数组,元素为指针类型
定义语法:类型* 变量名[元素个数]
指针数组的每一个元素都是用来储存地址的:
比如:
注意:如何理解 int* parr[ ] 呢,parr与 [ ] 结合表示一个数组,int* 表示数组的元素为整形指针类型
因此我们可以用指针数组来简单模拟一个二维数组:
简单解释一下:
- 函数参数为二级指针的原因:类比上图整形数组arr1,它的类型为 int [5],我们知道数组去掉数组名剩下的就是数组的类型,它的首元素为一个整形 int,因此过去函数接收整形数组只需要创建一级整形指针变量就行,而数组parr,它的类型为int* [3],它的首元素类型为 int*,所以函数接收一个指针数组就需要二级指针来接收
- parr[i][j] 等价于 *((*(parr+i))+j)
- 这只是模拟二维数组,它不是真正的二维数组,因为二维数组的元素地址内存中是连续存放的,而这个指针数组,虽然每个指针元素变量本身的地址是连续的,但是元素储存的地址不一定是连续的
六、数组指针
前面我们知道指针数组是数组,那么数组指针就是一个指针
那么数组指针怎么创建呢?
定义语法:类型 (*变量名) [元素个数]
我们这里必须要区分清楚:
- int* p[10],它是一个指针数组,p与[10]结合是一个数组,int与*结合表示数组的元素类型为int*
- int (*p)[10],它是一个数组指针,*先和p结合,表示一个指针变量,再和[10]结合表示指向的数组有10个元素,int 表示指向的数组的元素为整形
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
注意:无论是什么指针,只要是指针,它的大小就是4/8字节
1.数组指针的初始化
数组指针是一个指针,指向的是一个数组,前面我们说过&arr表示取出数组整个元素的地址,因此这个值就可以用一个数组指针来接收,因此数组指针的初始化如下:
数组指针 = &数组名
如:
我们通过调试就可以看到,&arr的类型就是与p一样的数组指针类型,所以我们前面所说&arr+1为什么能跳过整个数组,就是因为它是一个数组指针类型,表示整个数组
2.数组指针的使用
这里稍微解释一下:
- 函数形参,如前面所说,&arr表示整个数组,其实我们也可以这样理解,arr是一维数组的首元素地址,对这个首地址进行&,通常没太多意义,非要解释,可以按下面理解
- *(*p+i) 的意思,*p表示解引用指向这个一维数组,一维数组元素类型为 int ,因此这里+i 是跳过 i*sizeof(int) 个字节,这里跳过字节后依然是一维数组元素的地址,因此需要再使用*号解引用一次,才能访问到其指向的数据
- 数组指针多用于二维数组,这里用作一维数组较牵强,一般不用作一维数组的使用,只需要理解&arr表示整个数组,+1会跳过整个数组即可,下面我们来理解二维数组传参的本质就能理解我上面所说的意思
七、二维数组传参的本质
我们用二维数组演示:
这里我们就能很好的理解了:
- 我们知道数组指针可以接收一个&一维数组,也就是,假设一个一维数组ch,令数组指针=&ch,因为我们知道&ch+1是跳过整个数组,那么数组指针+1不也是跳过整个数组
- 如上图,我们给函数传了一个二维数组的数组名,注意没有用&符号,只是单纯的把二维数组的数组名传给了函数,而函数是用一个数组指针的形参来接收
- 我们首先来理解一下二维数组,我们是不是可以把二维数组看成一个一维数组的数组,也就是说,二维数组的元素是一个个类型相同的一维数组,如上图,arr[3][5],我们可以理解为该二维数组有三个元素:arr[0]、arr[1]、arr[2]。每个元素为一个一维数组,而 [5] 表示表示一维数组有5个元素
- 理解了二维数组,我们再来看看二维数组的数组名,如规则所说,数组名为数组首元素地址,那么二维数组的数组名就是arr[0]的地址,也就等价于&arr[0],这样就说得通了,为什么函数要用数组指针来接收,因为二维数组的数组名就是它首元素的地址,也就是&arr[0]。
- 所以对于*(*(p + i) + j)的理解就是这样,*(p+i)为跳过i个一维数组,再解引用锁定这个一维数组的首元素地址,也就是p[i],找到其一维数组的首地址,再 +j 找到一维数组的元素地址,最后再*解引用找到该元素,也就是p[i][j]
总结:⼆维数组传参,形参的部分可以写成数组,如arr[3][5],也可以写成指针形式,如 int (*p)[5]。
总结
以上就是本文关于数组与指针的全部内容了,偏理解,建议多看几遍,如有不通欢迎提出,一定会解答的,最后感谢大家的支持
还没有评论,来说两句吧...