1. 数组名的双重身份解析
在C语言中,数组名是一个极具迷惑性的概念。它既可以被当作指针使用,又保留了数组本身的特性。理解这种双重身份是掌握指针与数组关系的关键。
1.1 数组名作为首元素地址的常规表现
在大多数情况下,数组名会被编译器自动转换为指向数组首元素的指针。这意味着:
c复制int arr[5] = {1, 2, 3, 4, 5};
printf("%p\n", arr); // 输出数组首元素地址
printf("%p\n", &arr[0]); // 同样输出首元素地址
这两个printf语句会输出相同的地址值。这种特性使得我们可以用指针的方式来操作数组:
c复制*(arr + 2) = 10; // 等价于 arr[2] = 10
注意:虽然arr看起来像指针,但它不是真正的指针变量。它没有自己的存储空间来存放地址值,而是编译器在编译时就确定的常量表达式。
1.2 数组名保持数组特性的两个例外
数组名在两种特殊情况下不会退化为指针:
例外1:sizeof运算符
c复制int arr[10];
printf("%zu\n", sizeof(arr)); // 输出整个数组的大小(如40字节)
这里sizeof计算的是整个数组占用的内存空间,而不是指针的大小。这是获取数组元素个数的常用技巧:
c复制int element_count = sizeof(arr) / sizeof(arr[0]);
例外2:取地址运算符&
c复制int arr[10];
printf("%p\n", &arr); // 输出数组的地址
虽然&arr和arr的值相同,但它们的类型不同:
- arr的类型是int*
- &arr的类型是int(*)[10](指向包含10个int元素的数组的指针)
这种类型差异在指针运算时表现得尤为明显:
c复制printf("%p\n", arr + 1); // 地址增加4字节(一个int大小)
printf("%p\n", &arr + 1); // 地址增加40字节(整个数组大小)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 指针与数组的等价访问方式
理解数组名与指针的关系后,我们可以用多种方式访问数组元素,这些方式在底层是完全等价的。
2.1 四种等价访问形式
对于数组arr和指针p = arr,以下访问方式完全等效:
c复制arr[2] // 1. 数组下标访问
*(arr + 2) // 2. 指针算术运算
p[2] // 3. 指针下标访问
*(p + 2) // 4. 指针算术运算
编译器实际上会将所有数组下标访问转换为指针算术运算。这种转换遵循以下公式:
c复制arr[i] ≡ *(arr + i) ≡ *(i + arr) ≡ i[arr]
是的,你甚至可以用i[arr]这种看似奇怪的写
