在C语言中,数组名绝大多数情况下表示的是数组首元素的内存地址,也就是说,数组名是一个指向数组第一个元素的常量指针。

(图片来源网络,侵删)
这个结论是理解C语言数组的关键,下面我们通过不同场景来深入探讨这个概念。
数组名作为“指针” (最常见的情况)
当数组名出现在表达式中时,它会“衰变”(decay)为一个指向其首元素的指针。
示例代码:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 1. 打印数组名 arr
// 这里 arr 衰变为指向 arr[0] 的指针
// %p 是打印指针地址的格式说明符
printf("数组名 arr 的值 (地址): %p\n", (void*)arr);
printf("arr[0] 的地址: %p\n", (void*)&arr[0]);
// 2. 数组名可以用于指针运算
// arr + 1 等价于 &arr[0] + 1,即指向 arr[1] 的地址
printf("arr + 1 的值 (地址): %p\n", (void*)(arr + 1));
printf("arr[1] 的地址: %p\n", (void*)&arr[1]);
// 3. 使用解引用操作符 * 获取值
// *arr 等价于 *(&arr[0]),即 arr[0] 的值
printf("*arr 的值: %d\n", *arr); // 输出 10
printf("arr[0] 的值: %d\n", arr[0]); // 输出 10
// 4. 数组名可以作为函数参数
// 在函数声明中,void print_array(int a[]) 和 void print_array(int *a) 是完全等价的
// 这再次证明了数组名会退化为指针
print_array(arr);
return 0;
}
void print_array(int *a) {
printf("函数接收到的指针指向的值: %d\n", *a);
}
输出结果分析:

(图片来源网络,侵删)
数组名 arr 的值 (地址): 0x7ffc... (一个具体的内存地址)
arr[0] 的地址: 0x7ffc... (与上面完全相同)
arr + 1 的值 (地址): 0x7ffc... (比上面大4,因为int通常占4字节)
arr[1] 的地址: 0x7ffc... (与上面完全相同)
*arr 的值: 10
arr[0] 的值: 10
函数接收到的指针指向的值: 10
从上面的例子可以看出,arr 和 &arr[0] 在值上是完全相等的,都指向数组的第一个元素。
数组名的“例外”情况 (不衰变的情况)
有三种关键情况下,数组名不会衰变为指针,它仍然代表整个数组。
a) sizeof 操作符
当数组名作为 sizeof 操作符的操作数时,sizeof 返回的是整个数组所占用的总字节数,而不是指针的大小。
示例代码:

(图片来源网络,侵删)
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 输出 20 (假设int占4字节, 5 * 4 = 20)
printf("sizeof(ptr) = %zu\n", sizeof(ptr)); // 输出 8 (在64位系统上,指针大小通常是8字节)
return 0;
}
分析:
sizeof(arr)计算的是整个int[5]数组的大小。sizeof(ptr)计算的是int*指针变量本身的大小。
b) & (取地址) 操作符
当对数组名使用 & 操作符时,得到的是整个数组的地址,而不是首元素的地址,虽然这个地址的值和首元素的地址值相同,但它们的类型和含义不同。
示例代码:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// arr 的类型是 int* (指向int的指针)
// &arr 的类型是 int (*)[5] (指向包含5个int的数组的指针)
printf("arr 的地址: %p\n", (void*)arr);
printf("&arr[0] 的地址: %p\n", (void*)&arr[0]);
printf("&arr 的地址: %p\n", (void*)&arr);
// 尝试指针运算
printf("arr + 1 的地址: %p\n", (void*)(arr + 1)); // 移动 sizeof(int) 字节
printf("&arr + 1 的地址: %p\n", (void*)(&arr + 1)); // 移动 sizeof(arr) 字节
return 0;
}
输出结果分析:
arr 的地址: 0x7ffc...
&arr[0] 的地址: 0x7ffc...
&arr 的地址: 0x7ffc... (值相同)
arr + 1 的地址: 0x7ffc... (比上面大4)
&arr + 1 的地址: 0x7ffc... (比上面大20, 即整个数组的大小)
分析:
arr和&arr[0的值相同,但类型不同,都是int*。&arr的值虽然和它们相同,但类型是int (*)[5]。arr + 1将地址增加一个int的大小。&arr + 1将地址增加整个int[5]数组的大小。
c) 字符串字面量初始化数组
当用字符串字面量初始化字符数组时,数组名代表整个数组。
#include <stdio.h>
int main() {
// str1 是一个字符数组,它有足够的空间存放 'H', 'e', 'l', 'l', 'o', '\0'
char str1[] = "Hello";
// str2 是一个指向字符串字面量的指针,它自己只是一个指针变量
char *str2 = "Hello";
printf("sizeof(str1) = %zu\n", sizeof(str1)); // 输出 6 (包括'\0')
printf("sizeof(str2) = %zu\n", sizeof(str2)); // 输出 8 (指针大小)
// str1 可以被修改,因为它是一个数组
str1[0] = 'h';
printf("str1: %s\n", str1); // 输出 "hello"
// str2 指向一个只读内存区域,修改它会导致未定义行为 (通常是段错误)
// str2[0] = 'h'; // 这行代码很危险!
return 0;
}
总结与对比
| 场景 | 数组名 arr 的含义 |
类型 | 示例 |
|---|---|---|---|
| 作为表达式 (赋值、运算、函数参数) | 衰变为首元素地址 | int* (对于 int arr[N]) |
int *p = arr; |
sizeof 操作符 |
代表整个数组,计算总字节数 | int[N] (数组类型) |
sizeof(arr) |
& 取地址操作符 |
代表整个数组的地址 | int (*)[N] (指向数组的指针) |
&arr |
| 作为字符串字面量初始化 | 代表整个字符数组 | char[] |
char str[] = "test"; |
重要提醒
- 数组名不是变量:它是一个常量指针,你不能对它进行赋值操作。
int arr[5]; arr = some_other_pointer; // 错误!不能给数组名赋值
- 理解指针与数组的关系:C语言的设计使得数组和指针的语法非常相似。
arr[i]总是等价于*(arr + i),也等价于*(i + arr),也等价于i[arr](虽然最后一种写法不推荐,但它能证明数组访问的本质是指针运算),这种等价性是C语言强大的特性,也是初学者容易混淆的地方。
希望这个详细的解释能帮助你彻底理解C语言中数组名的含义!
