天天看点

【C语言进阶剖析】29、指针和数组分析(下)

1 数组的访问方式

1.1 两种数组访问方式

1.2 下标形式和指针形式对比

2 a 和 &a 的区别

3 数组参数

4 小结

在开始之前,先思考一个问题:数组名可以当作常量指针使用,那么指针是否也可以当作数组名来使用呢?

访问数组中的元素有两种访问方式,通过下标访问和通过指针访问数组

【C语言进阶剖析】29、指针和数组分析(下)

下标形式和指针形式基本是等价的,但是效率略有区别

指针以固定增量在数组中移动时,效率略高于下标形式

指针增量为 1 且硬件具有硬件增量模型时,效率更高

注意:现代编译器的生成代码优化率已大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可读性和代码维护的角度来看,下标形式更优

下标形式与指针形式之间可以相互转换,转换如下:

【C语言进阶剖析】29、指针和数组分析(下)

这里 a[n] 可是可以转换为 n[a] 的哦

下面通过一个例子说明数组的指针访问和下标访问

编译运行结果如下:

从结果我们可以看到,通过指针和数组都可以实现访问数组中的元素,可以把数组名当作常量指针使用,也可以把指针当作数组名来使用,a[i] 和 i[a] 是等效的

指针和数组的概念不是完全相等的,二着是有区别的,我们通过一个例子来说明指针数组和指针的不同

a 是数组首元素的地址,&a 是整个数组的地址,在数值上二者是相同的,*a 表示取元素第一个元素。

如果把 29-2.c 的第 6 行 extern int a[]; 改为 extern int *a; 代码更改如下:

再次编译运行,结果完全不一样了

为什么会出现这个运行结果呢?编译器编译 ext.c 后,数组 a 的地址就是 0x562f0c0ba010,等价于 a 的地址就是 0x562f0c0ba010。编译 29-2.c 时,碰见 extern int* a; 就寻找其他位置定义的 a 的值,a 的地址为 0x562f0c0ba010,所以 &a 就是取 a 的地址,就是 0x562f0c0ba010,a 的值是该地址下对应的数据,为 0x200000001。*a 表示取地址值为 0x200000001 的数据,不合法,是不允许访问的,返回段错误。

下面来说明一下为什么地址 0x562f0c0ba010 对应的数据为 0x200000001,我们打印的是 %p,%p 代表的是:按十六进制输出数据,长度为 8 个字节,linux 是小端序系统,低地址存放低位,数组存储方式如下,取 8 字节数据为 0x200000001

【C语言进阶剖析】29、指针和数组分析(下)

a 为数组首元素的地址

&a 为整个数组的地址

a 和 &a 的区别在于指针运算

【C语言进阶剖析】29、指针和数组分析(下)

a + 1 表示指向下一个元素,a + 1是越过一个元素,&a + 1 表示指向这个数组最后一个元素后面的位置,&a + 1 是越过一个数组,可以从下图清晰的看到二者的指向

【C语言进阶剖析】29、指针和数组分析(下)

下面看一个例子,更好的区分 a 和 &a

p1 指向整个数组最后一个元素后面的位置, p1[-1] ==> *(p1 -1),所以 p1[-1] 指向数组最后一个元素。

(int*)((long long)a + 1); 先把指针转换为 long long 再相加,这已经不是指针运算了,就是基本数据类型之间的运算,再次解引用,等于从数组首元素的第二个字节开始,取 4 个字节的数据,结果如下,linux 系统是小端序,取到的数据为 0x02000000,等于十进制的 33554432。

p3 指向数组第二个元素

【C语言进阶剖析】29、指针和数组分析(下)

数组作为函数参数时,编译器将其编译成对应的指针

【C语言进阶剖析】29、指针和数组分析(下)

结论:将数组名作为参数传递给函数时,数组名退化为指针,一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来表示数组的大小

下面通过例子来验证我们的理论

在 func1() 函数中,,打印 sizeof(a),数组退化成指针,所以不管数组长度如何,打印的都是指针的长度,由于退化成指针,所以允许 修改指针 a = null; 我们是不能修改数组名的指向的。func1() 函数是一样的,只是没有指定数组长度。

1、数组名和指针仅使用方式相同,数组名本质不是指针,指针也不是数组名

2、数组名并不是数组的地址,而是数组首元素的地址

3、函数的数组参数退化为指针