椋鸟C语言笔记#19

数组名、指针访问数组、一维数组传参的本质

Featured image

萌新的学习笔记,写错了恳请斧正。

数组名的理解

数组名其实就是数组首元素的地址,只有两个例外(马上讲)

我们不妨写一个程序验证一下:

#include <stdio.h>
 
int main()
{
    int arr[10] = { 0 };
    printf("%p\n", &arr[0]);
    printf("%p\n", arr);
    return 0;
}

运行后发现两者结果相同,都是相同的地址。

但是在下面这一串代码中,好像情况有所不同:

#include <stdio.h>
 
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("%d\n", sizeof(arr));
    return 0;
}

这里输出的结果为数组的长度:40 字节

但是 arr 难道不是数组首元素的地址吗,地址的长度怎么会是 40 个字节呢?

其实,这就是两个例外中的一个。

例外情况
  1. sizeof(数组名):sizeof 中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小
  2. & 数组名:这里的数组名表示整个数组,取出的是整个数组的地址 (整个数组的地址和数组首元素的地址值一样但是类型不同 ,解引用得到的不是首元素而是整个数组)

为了更深入的理解取地址数组名与数组首元素地址的区别,我们看看这段代码:

#include <stdio.h>
 
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("&arr[0] = %p\n", &arr[0]);
    printf("&arr[0]+1 = %p\n", &arr[0]+1);
    printf("arr = %p\n", arr);
    printf("arr+1 = %p\n", arr+1);
    printf("&arr = %p\n", &arr);
    printf("&arr+1 = %p\n", &arr+1);
    return 0;
}

其运行结果如下:

运行截图1

这里我们发现 & arr[0] 和 & arr[0]+1 相差 4 个字节,arr 和 arr+1 相差 4 个字节,是因为 & arr[0] 和 arr 都是
首元素的地址,+1 就是跳过一个元素;但是 & arr 和 &arr+1 相差 40 个字节,这就是因为 & arr 是数组的地址,+1 操作是跳过整个数组的。

使用指针访问数组

知道数组名的含义后,我们其实就可以通过指针来访问数组了

#include <stdio.h>
 
int main()
{
    int arr[10] = {0};
    int i = 0;
    int len = sizeof(arr) / sizeof(arr[0]);
    int* p = arr;    
 
    //输入
    for(i = 0; i < len; i++)
        scanf("%d", p + i);    //@@1
    
    //输出
    for(i = 0; i < len; i++)
        printf("%d ", *(p + i));    //@@2
 
    return 0;
}

注:上方 @@1 和 @@2 两处的 p 均可替换为 arr

所以说 arr 与 p 在这里是等价的

那我们能通过 arr[i] 来访问数组的元素,那是不是也可以通过 p[i] 来访问呢?

答案是肯定的,如下:

#include <stdio.h>
 
int main()
{
    int arr[10] = {0};
    int i = 0;
    int len = sizeof(arr) / sizeof(arr[0]);
    int* p = arr;    
 
    //输入
    for(i = 0; i < len; i++)
        scanf("%d", p + i);
    
    //输出
    for(i = 0; i < len; i++)
        printf("%d ", p[i]);    //@@@
 
    return 0;
}

所以 p[i] 实际上就等价于 *(p+i)

那么 arr[i] 是不是也就等价于 *(arr+i) 呢?

是这样的。在编译器处理时,arr[i] 实际上就是被转换成首元素指针加偏移量,然后解引用来访问的,而知道这些后,我们甚至可以玩一点新花样。

数组访问的特殊写法

我们知道加法操作符左右两边是可换的(两表达式不互相影响时)

所以 *(arr+1) 其实可以写成 *(i+arr)

而 *(arr+i) 等价于 arr[i],那么 *(i+arr) 是不是也就可以写成 i[arr] 呢?

答案是,可以!!!

所以 arr[i] 也可以写成 i[arr]!!!

不过我们一般不这么写

一维数组传参的本质

如果我们把一维数组传递给一个函数,本质上传递的是首元素的地址

这也就是为什么数组传参后函数操作会实际的影响到数组本身

因为传递的是地址,解引用后实际操作的是原参数(实参)的内存空间

只知道首元素地址是不能确定一个数组有多长的,所以我们一般把长度一起传过去

所以接受的类型应该是指针类型:

void Print(int* arr, int len)
{
    for (int i = 0; i < len; i++)
        printf("%d", arr[i]);
}

但我们之前讲过数组传参可以写成这样:

void Print(int arr[], int len)
{
    for (int i = 0; i < len; i++)
        printf("%d", arr[i]);
}

实际上这两种写法等价,本质上还是指针(其实是 C 语言专门设计为可以写成这样便于理解)

这也就解释了为什么参数中 arr[] 的方括号内不需要数字,而有数字也会被忽略