经典题解析:数组名加一与数组地址加一,为什么走得不一样

图中元素指针按一个元素前进,数组指针按整个数组前进;尾后位置可以形成,却不能解引用。

数组名 a 和数组地址 &a 打印出来似乎一样,为什么分别加一却走了不同距离?答案不是“编译器随便多跳了几个字节”,而是它们在表达式中的类型不同。指针运算必须根据所指对象解释,地址外观看起来相同并不能代替类型规则。

数组不是指针变量

设 int a[5] 保存五个整数。在很多表达式里,a 会转换为指向首元素的指针,类型为 int*;但 a 本身仍是数组对象,不是一个可以重新赋地址的普通指针变量。对 a 取地址,得到的是指向整个数组的指针,类型为 int (*)[5]。

转换也不是在所有语境里都发生。比如 sizeof a 计算整个数组所占大小,而对数组取地址也保留数组这一层类型。因此“数组名永远等于指针”会在这些最常见的反例上失效。

声明函数形参为某些数组写法时,参数类型会进行相应调整,又不能据此推出函数里保留了调用方数组的完整长度。真实数组对象与作为参数传入的指针,应分开判断。

加一按所指类型移动

元素指针 a+1 指向第二个 int,步长是一个 int 的大小。数组指针 &a+1 则越过整个五元素数组,步长对应整个数组对象的大小,也就是五个 int。

这里没有承诺 int 在所有平台上都是四字节。用 sizeof 所指对象来表达步长,才是语言层面的说法;某台机器恰好得到四和二十,只是那个环境中的数值。

指针加法也不是任意整数地址计算。形成指针时,要遵守它所属数组对象的范围及相应规则。即使算出的地址数字恰好落在别的对象附近,也不因此获得访问那个对象的权限。

地址相似,不代表指针运算的类型相同

尾后指针能形成,不能读它指向的值

对五元素数组,a+5 是合法的尾后元素指针,可以用于比较、循环结束判断,并在适用条件下做差;不能写成 *(a+5) 读取一个不存在的第六个元素。a+4 才指向最后一个实际元素。

&a+1 也位于整个数组对象之后。它与元素层面的尾后指针可能表现出相同的地址位置,但类型和所属对象解释仍然不同。正确做法不是先强行改成想要的指针类型,再假定后面的减法一定成立。

很多面试片段会把整个数组的尾后地址强转成元素指针,再回退取值。不能只凭某次打印出了末元素,就把跨类型指针运算的可移植性证明省掉。若目的只是读取末元素,有清楚合法的表达方式,不必依赖这种地址猜测。

沿各自类型回退就很清楚

下面分别沿元素指针和数组指针回退,表达的是同一个合法读取意图:

#include <iostream>
int main() {
    int a[5] = {10, 20, 30, 40, 50};
    auto element_end = a + 5;
    auto array_end = &a + 1;
    std::cout << *(a + 1) << ' '
              << *(element_end - 1) << ' '
              << (*(array_end - 1))[4] << std::endl;
}

结果是二十、五十、五十。array_end 减一回到原数组,再在数组里取第四号元素;element_end 减一则直接回到最后一个 int。每一步都能说明对象与下标,不需要从打印地址反推是否合法。

若只是需要遍历和长度信息,还可以把有效区间作为接口的一部分传递。不要让调用方凭一个裸地址猜数组长度;类型没有携带的范围,需要通过明确契约补充。

差值也必须有共同范围

同一数组内的元素指针相减,可以得到相隔的元素个数,而不是字节距离。a+5 与 a 相减得到五;整个数组指针的相应差值则按数组对象单位解释。不要把两者混成一个统一的“减地址除四”公式。

对无关对象的指针,不能因为数值接近就随意相减来推断元素个数。生命周期也要有效:对象已结束,原先指针的可用性不会因为仍然保存着相同数值而自动延续。

面试回答可以沿“数组类型、常见退化、所指对象步长、尾后边界、合法回退”展开。如果把数组长度从五改成八,&a+1 的步长会跟着变,为什么 a+1 的元素步长却不会因此增加?