开题问:数组名可以当做常量指针使用。那么指针是否可以当做数组来使用?

1 数组的访问方式

  1. 以下标的形式访问数组中的元素:

  1. 以指针的形式访问数组中的元素

下标形式与指针形式的转换:

对于上面的转换方式,不要觉得奇怪。这是很正确的转换。

1.1 数组的访问方式代码分析

数组下标形式的访问,我们很常见,下面看一看不是很常见的访问形式;

  • 代码 29-1.c
#include <stdio.h>

int main(){

    int a[5] = {0};
    int* p = a;
    int  i =0;
    
    for(i=0; i<5; i++){
        p[i] = i+1;
    }
    
    for(i=0; i<5; i++){
        printf("a[%d] = %d\n", i,*(a+i));
    }
    
    printf("\n");
    
    for(i=0; i<5; i++){
        i[a] = i+10;
    }
    
    for(i=0; i<5; i++){
        printf("a[%d] = %d\n",i, p[i]);
    }
    return 0;
}
  • 编译运行结果为:

可以看出:

  • 指针确实可以当做数组来使用
  • 访问数组可以通过下标形式与指针形式,具体见上图中的转换公式。

2 数组和指针不同

虽然上面说了数组与指针都可以进行访问数组,它们很相似,但是数组与指针是不同的。在之前的文章中已经有过分析,这里不再赘述,直接看以前的文章:【软件开发底层知识修炼】二十七 C/C++中的指针与数组是不同的

3 a 和 &a 的区别

  • a为数组首元素的地址
  • &a 为整个数组的地址
  • a 和 &a 虽然在数值上是相等的。但是它们的意义不同。在指针运算中存在差别:

3.1 指针运算的经典代码案例分析

  • 代码29-3.c
#include <stdio.h>

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1);    
    int* p2 = (int*)((int)a + 1);
    int* p3 = (int*)(a + 1);
    
    printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
    
    return 0;
}

上述代码编译运行的结果为:

5, 33554432, 3

我们暂且不看p2[0]的结果。

  • p1[-1] 表示当前指针指向的元素的前一的元素。因为 &a + 1 后是直接跨过可整个数组到了数组最后一个元素的下一个元素的位置。所以 p[-1] = 5
  • p3[1] = 3很好理解。(*(p3 + 1) = 3)

那么p2[0]到底是怎么回事?

可以看下图的数组的内存图(Linux 系统下的小端模型):

  • 上图就是数组a的内存模型,在Linux系统的小端模式下。如果这个不懂那就没法往后看。
  • a是数组首元素的地址,a+1 是第二个元素的地址,这也很好理解
  • (int)a 是将a这个常量指针转换为int型变量(加入为tmp),那么(int)a + 1 就直接相当于这个tmp变量加1,然后就到了如上图中的位置。
  • 然后(int*)((int)a + 1) 又将其转换为指针。所以*p2 = p2[0] = 0x02000000
  • 0x02000000转换为十进制的值是:33554432 。
  • 到这里,就应该没有什么疑问了

4 数组作为函数的参数

  • 数组作为函数的参数的时候,编译器会将其编译为对应的指针。

注意:所以,数组作为函数参数的时候,必须将数组的个数也作为参数传进去,否则在函数内无法计算数组的大小。

4.1 数组作为函数参数的代码案例分析

  • 代码:29-4.c
#include <stdio.h>

void func1(char a[5]){
    printf("In func1 sizeof(a)=%d\n",sizeof(a));
    
    *a = 'a';
    
    a = NULL;   //可以作为左值,但是数组就不行
}

void func2(char b[]){
    printf("In func2 sizeof(b)=%d\n",sizeof(b));
    
    *b = 'b';
    
    b = NULL;  //可以作为左值,但是数组就不行
}
int main(){
    char array[10] = {0};
    
    func1(array);
    printf("In func1 array[0]=%c\n", array[0]);
    
    func2(array);
    printf("In func2 array[0]=%c\n", array[0]);
    
    return 0;
}
  • 编译运行结果如下:

分析:

  • 由上述代码看出当数组作为函数参数时,在函数体内,它的大小永远是4,是因为它在编译的过程中退化为指针。在32系统中,指针的大小总是4字节。
  • 并且可以看出,数组作为函数参数的时候,可以作为左值。但是数组就不可以作为左值,这也充分说明数组作为函数参数的时候,最终退化为指针,指针是可以作为左值的。

5 总结

  • 数组名和指针仅仅是使用方式相同。数组与指针的本质是不同的。
  • 数组名不是整个数组的地址,它是数组首元素的地址
  • 数组作为函数参数的时候,会被编译器编译为指针。