首先有一点提醒(或者一些新的东西,如果你以前不知道的话):对于任何数组或指针p
和索引i
表达方式p[i]
完全一样*(p + i)
.
现在希望能帮助您了解发生了什么......
数组a
程序中的内容存储在内存中的某个位置,具体位置并不重要。获取所在位置a
被存储,即得到一个指向a
,您使用地址运算符&
like &a
。这里要学习的重要一点是,指针本身并不意味着什么特殊的,重要的是基本类型的指针。的类型a
is int[4]
, i.e. a
是一个由四个组成的数组int
元素。表达式的类型&a
是一个指向四个数组的指针int
, or int (*)[4]
。括号很重要,因为类型int *[4]
是一个由四个指针组成的数组int
,这是完全不同的事情。
现在回到最初的观点,即p[i]
是相同的*(p + i)
。代替p
我们有&a
,所以我们的表达*(&a + 1)
是相同的(&a)[1]
.
现在这解释了什么*(&a + 1)
意味着什么以及它有什么作用。现在让我们思考一下数组的内存布局a
。在记忆中它看起来像
+---+---+---+---+
| 0 | 1 | 2 | 3 |
+---+---+---+---+
^
|
&a;
表达方式(&a)[1]
treats &a
因为它是一个数组的数组,但它绝对不是,并且访问该数组中的第二个元素,这将超出范围。这当然从技术上来说是未定义的行为。让我们暂时运行一下,并考虑一下如何that在记忆中看起来像:
+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | . | . | . | . |
+---+---+---+---+---+---+---+---+
^ ^
| |
(&a;)[0] (&a;)[1]
现在请记住,类型a
(这与(&a)[0]
因此意味着(&a)[1]
也必须是这种类型)是四个数组int
。由于数组自然会衰减为指向其第一个元素的指针,因此表达式(&a)[1]
是相同的&(&a)[1][0]
,其类型为指向int
。所以当我们使用(&a)[1]
在表达式中,编译器给我们的是一个指向第二个(不存在的)数组中第一个元素的指针&a
。我们再一次来到了p[i]
equals *(p + i)
方程:(&a)[1]
is a 指向int
, it's p
in the *(p + i)
表达式,所以完整的表达式是*((&a)[1] - 1)
,然后查看上面的内存布局减去一int
从给出的指针(&a)[1]
给我们之前的元素(&a)[1]
这是最后一个元素(&a)[0]
,即它给了我们(&a)[0][3]
这与以下相同a[3]
.
所以表达*(*(&a + 1) - 1)
是相同的a[3]
.
它很冗长,并且穿越了危险的领域(越界索引),但由于指针算术的力量,最终一切都解决了。不过,我不建议您编写这样的代码,它需要人们真正了解这些转换如何工作才能破译它。