正如ameyCU 所解释的,[] 下标运算符的优先级高于一元运算符*,因此表达式*a[i] 将解析为*(a[i]); IOW,您正在索引a 并取消引用结果。
如果a 是T 的数组(或指向T 的指针;更多内容见下文),则此方法有效。但是,如果a 是一个指向T 的数组 的指针,那将无法满足您的要求。这可能是最好的视觉解释。
假设声明:
int arr[3] = { 0, 1, 2 };
int (*parr)[3] = &arr; // type of &arr is int (*)[3], not int **
这是内存中的情况(有点;地址是凭空提取的):
Address Item Memory cell
------- ---- -----------
+---+
0x8000 arr: | 0 | <--------+
+---+ |
0x8004 | 1 | |
+---+ |
0x8008 | 2 | |
+---+ |
... |
+---+ |
0x8080 parr: | | ----------+
+---+
...
所以你看到数组arr 及其三个元素,指针parr 指向arr。我们想通过指针parr访问arr的第二个元素(地址1处的值0x8004)。如果我们写*parr[1]会发生什么?
首先,记住表达式a[i]定义为*(a + i);也就是说,给定一个指针值a1,从a 偏移i 元素(不是字节)并取消引用结果.但是将i elements 从a 偏移是什么意思呢?
指针算法是基于被指向类型的大小;如果p 是指向T 的指针,那么p+1 将为我提供下一个T 类型对象的位置。因此,如果p 指向地址为0x1000 的int 对象,那么p+1 将给我int 对象的地址在p - 0x1000 + sizeof (int) 之后。
那么,如果我们写parr[1],那会给我们带来什么?由于parr 指向一个三元素数组,如果int,parr + 1 将为我们提供int 的下一个三元素数组的地址-0x8000 + sizeof (int [3]),或@987654365 @(假设 4 字节 int 类型)。
请记住,[] 的优先级高于一元 *,因此表达式 *parr[1] 将被解析为 *(parr[1]),其计算结果为 *(0x800c)。
这不是我们想要的。要通过parr 访问arr[1],我们必须确保parr 已被取消引用之前通过使用括号将* 运算符显式分组来应用下标操作:(*parr)[1]。 *parr 计算结果为 0x8000,其类型为“int 的三元素数组”;然后我们访问该数组的第二个元素(0x8000 + sizeof (int) 或 0x8004)以获得所需的值。
现在,让我们看一下——如果a[i] 等价于*(a+i),那么a[0] 就等价于*a。这意味着我们可以将(*parr)[1] 写为(parr[0])[1],或者只是parr[0][1]。现在,您不想在这种情况下这样做,因为parr 只是指向一维数组的指针,而不是二维数组。但这就是二维数组索引的工作方式。给定一个像T a[M][N]; 这样的声明,在大多数情况下,表达式a 将“衰减”为T (*)[N]。如果我写了类似的东西
int arr[3][2] = {{1,2},{3,4},{5,6}};
int (*parr)[2] = arr; // don't need the & this time, since arr "decays" to type
// int (*)[2]
然后要访问arr 到parr 的元素,我需要做的就是写parr[i][j]; parr[i] 隐式 取消引用 parr 指针。
- 这就是事情变得混乱的地方;数组不是指针,它们内部不存储任何指针。相反,数组表达式不是
sizeof 或一元* 运算符的操作数,其类型从“T 的N 元素数组”转换为“指向T 的指针”,其值表达式是数组第一个元素的地址。这就是您可以在数组和指针对象上使用[] 运算符的原因。
这也是为什么我们在代码sn-p中使用&操作符来获取arr的地址;如果它不是 `&` 运算符的操作数,则表达式“衰减”从类型“int”的三元素数组到“指向int 的指针”