目录
一.指针和数组
1.指向数组
2.数组下标和指针解引用的相等性
3.*和++优先级问题
二..函数,数组和指针
1.声明数组形参
2.使用const保护地址数据
3.指针和多维数组
4.指向多维数组的指针
5.函数和多维数组
一.指针和数组
在前面的章节里面我们已经说过了,如何将申明一个指向普通数据类型的指针,就是类型名 + * + 指针名。那如何指向数组呢?我们前面说过数组是一组类型一样元素的集合,还有数组名是首元素的地址。所以我们如果想要通过指针访问和修改数组元素,只需要把数组的首元素地址给到指针,然后让指针遍历就好了
1.指向数组
之前讲过,数组元素在内存当中是连续存储的,在加上元素类型也一样。这就说明了,只要我们得到首元素地址,按照类型存储大小递增,就能不断遍历到所有元素。我们来看个例子。
这里我声名了一个int类型数组,一共有三个元素
我们重点看for循环。
里面一共有两个printf打印语句,一个负责打印数组元素的地址和值,一个负责打印p存储的地址和值。这里我们用p = p +1的方式,给p存储的地址加上特定的值(注意,这里的1不是指地址加1,而是地址加上一个int类型占用的存储字节,int是4个字节,就是32位)
我们看到结果p存储的地址和结果都是和当前num下标所指的一样的。
我们来看num[1]和num[0]地址的差值。发现只有最后两位不同。
num[0]后两位:A8 num[1]后两位: AC。
BC - B8 = 4个字节,正好是一个int类型的字节大小。
2.数组下标和指针解引用的相等性
通过上面的例子,我们可发现,
p + I = &num[i]; 具有相同的地址
*(p+i) = num[i]; 具有相同的值(*的优先级大于+号,所以必须加括号)
所以对于两种表示法都是等效的。不过如果推荐用数组表示法,因为表示起来更加的简单,明了。
3.*和++优先级问题
*的优先级大于+号,但和++优先级相同,结合律是从右往左。
我们从第二行开始
*p1++,先地址递增,然后才解引用,但要明白,地址递增只有到,序列点的时候才会生效。
*++p2;先地址递增(前++不用到,序列点就会先自增完成,这点比较特殊);
(*p3)++;先解引用p3获得值,然后在该值的基础上加1;
二..函数,数组和指针
1.声明数组形参
在上一章中,我们用int * ar这种方式传到了数组的首元素地址。这里交大家一种类似的声名
int ar[]。int * ar 和int ar[]都表示ar是指向int的指针。但是ar[]还可以更清楚的传达传送过来的是一个数组元素的想法。但int ar[]这种指针声名形式只能在函数的原型和函数定义头中可以用
在这个例子当中,我们把数组的首元素地址传入到函数当中,并且把求了所有数组元素的和total。但这里需要注意下红色括号,这里我们打印ar数据类型大小。ar是指针,不管是指向啥类型的指针,它的大小都是8个字节(也有可能是4个字节)
2.使用const保护地址数据
如果我们想处理一个基本类型时,我们可以选择传递类型变量值或类型变量的地址。但放在数组身上就不行,对于数组,必须要传递地址到函数中。(因为只能靠地址,才能在函数中处理到数组的所有数据)
这个时候const的作用就到了。这里const位置不同,所造成的效果也不同
1: 开头
Const int * ar
ar指针所指向内容的值无法修改,但可以修改ar指向的地址
2:后面
Int * const ar
ar指针指向的地址无法修改,但地址的值可以修改
3:前面后面都有
Const int * const ar
ar指针指向的地址无法修改,且地址的值也无法修改
这里要记住
1.可以把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的(保证赋值过来地址上面的值都不会被修改)
2.非const数据只能赋给普通指针(如果把cosnt数据给到一个普通指针,那么指针就可以对该地址上面的值进行修改)
可以发现系统不会让你修改x,z存储地址上的值
这里可以看到系统不允许你修改指向x存储的值
3.指针和多维数组
Int zippo[4][2]
我们来对zippo进行详细讲解
1.因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]的值相同。而zippo[0]本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素(一个整数)的地址(&zippo[0][0]的值)相同。
简而言之,zippo[0]是一个占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。
由于这个整数和内含两个整数的数组都开始于同一个地址,所以zippo和zippo[0]的值相同
2.给指针或地址加1,其值会增加对应类型大小的数值。在这方面,zippo和zippo[0]不同,因为zippo指向的对象占用了两个int大小。而zippo[0]指向的对象只占用一个int大小。
所以,zippo + 1 和 zippo[0] + 1值不同
3.解引用一个指针或在数组名后使用带下表的【】运算符,得到引用对象代表的值。
因为zippo[0]是该数组首元素(zippo[0][0])的地址,所以*(zippo[0])表示存储在zippo[0][0]位置上的值
*zippo代表该数组首元素(zippo[0])的值,但是zippo[0]本身是一个地址。该地址是&zippo[0][0],所以*zippo就是&zippo[0][0]。所以**zippo与*&zippo[0][0]等价。
所以简而言之,zippo是地址的地址,必须解引用两次才能获得原始值,地址的地址或指针的指针就是双重间接
代码源码:
#include <stdio.h>
int main(){
int zippo[4][2] = {{2,4},{6,8},{1,3},{5,7}};
printf(" zippo = %p zippo+1 = %p\n",zippo,zippo + 1); //zippo + 1 在原地址的基础上加两个int类型,8byte
printf(" zippo[0] = %p zippo[0]+1 = %p\n",zippo[0],zippo[0] + 1); //zippo[0] + 1 在原地址的基础上加一个int类型,4byte
printf(" *zippo = %p *zippo+1 = %p\n",*zippo,*zippo + 1); //*zippo + 1,*zippo相当于zippo[0][0],所以加1,加一个int类型,4byte
printf("zippo[0][0] = %d\n",zippo[0][0]);
printf("*zippo[0] = %d\n",*zippo[0]); //zippo[0]地址为zippo[0][0]
printf("**zippo = %d\n",**zippo); //*zippo值为&zippo[0][0],所以**zippo等于zippo[0][0]的值
printf(" zippo[2][1] = %d\n",zippo[2][1]);
printf("*(*(zippo + 2) + 1) = %d\n",*(*(zippo + 2) + 1)); //zippo+2相当于zippo[0] + 2,等于zippo[2],*zippo[2]等于1的地址,然后加+1,
//得到zippo[2][1],然后在解引用,*zippo[2][1]等于3
return 0;
}
4.指向多维数组的指针
Int (* pz)[2];
pz指向一个内含两个int类型值得数组
Int * pz[2];
pz是一个内含两个指针元素得数组,每个元素都指向int得指针
([]优先级高于*)
效果和上面数组下标结果一致。
5.函数和多维数组
前面我们已经学习了如何多维数组,那么这一节当中我们将学习如何使用指针在函数当中处理多维数组。
这里我用红色形式声明了函数的一个数组中有两个元素,每个元素都有2个int类型的值。黄色部分,意思表示为存储两个int类型元素的数组。通过row控制数组的移动。
先看num[3][2],这里就比较明显了,但日常使用来说我们用的不同,因为这样的形参不能很好的表示其是一个指针。而在使用的时候,我们直接传入数组名就好了。或者&num都可以。用&num可以更好表示,变量的值是一个数组且数组中有三个元素,每个元素都有2个int类型的值。
而num[][2]就是上面我们所说的用的最多的一种方式。它能够最直接的表达出我们想传递一个数组指针的想法。
而看到下面两个函数中,下划线部分。我们使用*(*(num + i) + j);的方式获取地址上面的值。*(num + i)是确定第几个数组。然后在 + j 在解引用获得数组的第几个元素值。
好了朋友们我们今天的内容到这就结束了,今天的内容到这里就结束了,如果有啥不会的朋友记得论坛里面提问哈~
如果朋友你感觉文章的内容对你有帮助,可以点赞,关注文章和专栏以及关注我哈,嘿嘿嘿我会定期更新文章的,谢谢朋友你的支持哈