C语言中的指针和数组

前言

指针和数组是非常容易混淆的东西,这里我想捋捋几个容易混淆的地方。

假如我们有一个数组

int arr[] <- {1,2,3,4,5}; 

数组名:有时候会转换成指针?

根据D老师,C语言标准规定,在大多数使用数组名的表达式中,数组名会发生坍缩,数组名的返回值,会变成数组的第一个元素的地址,变成指向数组第一个元素的指针。
所以有以下性质:

arr == &arr[1]
*arr == arr[1]

比如,arr+1表示的是数组第二个元素的位置。
*(arr+1) 表示数组的第二个元素

比如arr[2]的表达式,数组名arr先坍缩为指针,然后进行指针运算arr+2,然后使用*(arr+2)这个解引用获取该地址的值。


例外的情况(数组名不被转换为指针的情况):
在少数几种情况下,数组名保持其“数组”身份,不会 decay 成指针:

  • 作为 sizeof 运算符的操作数
    sizeof(arr) 返回的是整个数组占用的字节大小(例如数组arr有 5 * sizeof(int) = 20 字节)。

  • 作为取地址运算符&的操作数
    &arr 产生的是一个指向整个数组的指针,其类型是 int (*)[5]。虽然 &arr&arr[0] 的数值(地址值)是相同的,两个指针的大小也是相同的,但它们的类型不同。对 &arr + 1 进行指针运算,会跳过整个数组的大小(20字节)。

  • 作为字符串字面量初始化字符数组时
    char str[] = "hello"; 这里 "hello" 是字符串字面量,本质是 const char[6],在初始化时,字符串字面量不会退化为指针,编译器直接复制字符串内容到新数组。


数组名成为函数的参数时退化为指针

当数组名(例如下方代码中的numbers)作为实参传递给函数时,实际上传递的是指向该数组首元素的指针。并且在函数中建立了新的指针变量 (例如下方代码的形参arr)。
因此,传递参数时,要传递数组名和数组的大小

# include <stdio.h>
                 
void modifyArray(int arr[], int size) {   
// 这里写int arr[], int arr[10](这里10会被编译器忽略),int *arr都是一样的效果,创立的新变量都是int *arr
// 这里的size就是数组大小,即数组中元素的多少
           
    for (int i = 0; i < size; i++) {
        arr[i] *= 2; // 通过指针直接修改原数组   乘以2
    }
}

int main() {
    int numbers[3] = {1, 2, 3};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    modifyArray(numbers, size);    
    //实际上在函数中发生的是:int *arr = &numbers[0] 或者 int *arr = numbers
                    
    // 验证结果
    for (int i = 0; i < size; i++) {
        printf("%d ", numbers[i]); // 输出:2 4 6
    }
    return 0;
}


数组指针的解引用是什么?

我们创建一个直接指向数组的指针,那么这个指针的解引用是什么?
这里直接揭晓答案,其实就是数组arr本身。当然要注意数组名的返回值的问题。

我们用代码验证一下。

# include <stdio.h>
int main() {
int arr[] = {1,2,3,4,5};  // 创造数组arr  
int (*p) [5] = &arr;    // 创造一个数组指针p,指向数组arr
                
// 那么*p会是什么? 也就是指针p的解引用是什么?  
                         
printf("数组指针p的解引用是: %p\n", *p);   
// 为什么会是指针类型呢?因为解引用为数组arr,当数组作为函数参数传递时,它被转换为指向其首元素的指针,类型是int *。
// 然后为了匹配 %p 格式,int* 被隐式转换为 void*
                
printf("数组名的返回值 (即指向数组的首个元素的指针的值) 是: %p\n", arr);
// 结果我们发现这两行的结果是一致的。
               
printf("数组指针p的解引用的大小是: %lu\n", sizeof(*p));   // 结果是20 
            
// 综上,我们证实了*p就是数组arr本身
       
return 0;

}

为什么不能赋值给数组名

int a[10] =  {1,2,3,4,5,6,7,8,9,10};  // 正确

//以下错误
int a[10];
a = {1,2,3,4,5,6,7,8,9,10}; // 错误!

数组名的本质

数组名是一个符号,不是变量
在编译时,编译器知道数组名代表数组的起始地址
运行时,数组名不会在栈或堆上分配额外内存来存储这个地址
它只是在代码中被直接替换为数组的起始地址

数组名不占用任何额外的内存来存储一个地址。它只是一个符号,代表一块内存的起始位置。编译器在编译期就知道这个位置。
正是因为数组名不占用内存存储地址值,所以:
-数组名在编译时就被固定为特定的内存地址
-试图赋值就像试图改变一个常量的值

posted @ 2025-09-22 17:08  Leeli1998  阅读(15)  评论(0)    收藏  举报