1、数组下标运算符的工作原理
C语言中,数组下标运算符 []
的行为基于指针运算。表达式 a[i]
的实际含义如下,
*(a + i) // 等价于 a[i]
a[i]
是指向 a
开始的第 i
个元素的值,即从 a
的起始地址移动 i
个位置,并解引用这个新位置的值。基本数组访问如下,
#include <stdio.h> int main() { int a[5] = {1, 2, 3, 4, 5}; printf("a[2] = %d\n", a[2]); // 输出 3 printf("*(a + 2) = %d\n", *(a + 2)); // 等价,输出 3 return 0; }
a[2]
实际上就是 *(a + 2)
,它意味着从数组 a
的起始地址移动2
个元素,并返回该位置的值。
2、反转的数组下标
a[i]
等价于 *(a + i)
,这种关系是对称的。换句话说,a[i]
可以重写为 i[a]
,如下,
a[i] == *(a + i)
5[a] == *(5 + a)
5[a]
等价于 *(5 + a)
,这在指针运算中是完全合法的。C语言可以使用这种形式,因为在指针运算中,加法的顺序并不重要,a + 5
与 5 + a
是等价的。反转的数组访问如下,
#include <stdio.h> int main() { int a[5] = {1, 2, 3, 4, 5}; printf("a[2] = %d\n", a[2]); // 输出 3 printf("2[a] = %d\n", 2[a]); // 输出 3,等价于 a[2] printf("a[4] = %d\n", a[4]); // 输出 5 printf("4[a] = %d\n", 4[a]); // 输出 5,等价于 a[4] return 0; }
3、数组和指针的对称性
C语言中的数组名可以视为指向数组首元素的指针。数组和指针有许多相似之处,尤其是当进行指针加法运算时。数组的下标访问运算符只是对这种加法运算的语法糖。数组和指针的等价性,数组名 a
可以看作是指向数组首元素的指针,即 a == &a[0]
。指针加法 a + i
计算的是数组中第i
个元素的地址,5 + a
同样有效,并且与 a + 5
等价。
#include <stdio.h> int main() { // 定义一个数组 int a[] = {10, 20, 30, 40, 50}; // 通过数组名和下标访问元素 printf("通过数组名访问 a[2] = %d\n", a[2]); // 输出 30 // 使用指针访问数组元素 int *p = a; // 数组名 a 相当于 &a[0],即指向数组第一个元素的指针 printf("通过指针访问 *(p + 2) = %d\n", *(p + 2)); // 输出 30 // 使用指针并使用数组下标访问 printf("通过指针和下标访问 p[2] = %d\n", p[2]); // 输出 30 // 使用 i[a] 访问数组元素 printf("通过 i[a] 访问 2[a] = %d\n", 2[a]); // 输出 30 return 0; }
4、编译器对 a[i] 和 i[a]的处理
编译器在处理 a[i]
或 i[a]
时,都会将它们转换为指针运算。因此,无论是 a[i]
还是 i[a]
,编译器都会将其解释为指针加法和解引用操作。
编译器转换 a[i]
被编译器转换为 *(a + i)
,i[a
] 被编译器转换为 *(i + a)
。两种转换在效果上完全相同。通过编译器生成的汇编代码来查看编译器对 a[i]
和 i[a]
的具体处理。代码如下,
#include <stdio.h> int main() { int a[] = {10, 20, 30, 40, 50}; // 使用 a[i] 和 i[a] 访问数组 int x = a[2]; // 等价于 *(a + 2) int y = 2[a]; // 等价于 *(2 + a) printf("x = %d, y = %d\n", x, y); return 0; }