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;
}