从 &a+1 说起————数组变量、&和+运算符
摘录
对于数组a,&a+1 输出了什么?&和+在这里的作用是什么?
&a+1 == ?
这个问题由和同学聊到这个标题而来,其中 a 是一个数组的名字。
一开始我想当然地认为:&a 取得数组首地址,+1 指向数组首地址的下一个地址(字节)。但是事实并非如此,事实上,上面这个算式指向的数组末尾地址 +1 的那个地址。
为了搞懂这个问题,必须要理解一个在这之前的学习中被忽略的问题,即:数组是什么类型?int 型变量和 int 型数组和长度不同的 int 型数组,属于相同的变量类型吗?进而,我了解到了引用、左值与右值、以及关于指针的一些知识。
开始探索吧。
测试代码
我们以 c++ 来讨论这个问题,为此,定义变量并写下测试程序如下,我们稍后解释测试结果:
1 | // 数组 |
在上述代码中,我们使用了 sizeof 来检查输出变量的大小,typeid 运算符检查变量的类型。typeid 的 name 方法输出变量类型的名字。如下解读:A 指代 array,i 指代 int 型,P指代 pointer。其他的没有出现在输出中。
它的输出如下:
1 | value: 6487488 6487488 6487492 6487508 6487492 6487489 |
对测试结果的解释
数组变量和数组退化
在定义一个数组变量,编译器将为数组分配一块连续的地址空间,并且产生一个数组变量 a。在这里,a 是数组名,也即所谓数组变量。数组变量是一种特殊的变量类型,它包含有【数组长度】个【数组单元变量类型】的变量。例如,我们的数组变量 a 就是包含了 5 个 int 型单元的数组变量。它不是一个 int 型变量,也不是含糊其辞的数组型变量。在我们是输出结果中,这被写作A5_i
。
在很多情况中,数组变量 a 的表现和一个指向 a[0] 的指针一样,但是它们并不一样:指针不包含长度信息。很显然数组变量指明了数组的长度,但是一个指针并不清楚自己指向了一个数组单元还是一个变量。从数组变量变成指针,我们称之为“数组退化”。
“数组退化”的发生情况很多,最常见的一种是把它作为参数传入以后。为什么要这么设计很好理解:不同长度的数组是不同类型的变量,如果不退化成指针,写函数时就必须指定数组的长度,这很不方便。如果退化成指针,我们只需要再加一个参数指明数组长度就可以解决问题。
左值和右值
另一种会导致数组变化的原因是将其作为了右值。
左值和右值是 c++11 引入的特性。左值指能在内存寻址的值,右值则不是。我们用下面这个例子进行区分:
1 | int i; |
很明显我们不能说 12 存储在内存上的哪儿,但是可以说出 i 在内存的地址。
下面是数组变量因为作为右值而退化的例子(或者说,我们认为编译器在这里把数组名解释成了指向数组头的常量指针)。
1 | int a[5]; |
特别需要注意的是,尽管对左值进行自增操作是允许的,但是上面的 a++ 写法事实上是错误的,因为数组变量指向的地址是不可修改的。这也是为什么我们说它退化后是常量指针。
另外一个有意思的地方在于 a++ 和 a 一个返回右值一个返回左值。这是因为 a 操作先取得 a 的值,再返回 a 的值,最后把加一的值赋给 a ,++a 则完成了赋值操作再返回。这导致前者返回的值事实上不是 a 地址的值,所以只是右值。
关于 & 符
& 运算符有两种用法,第一种是取地址,第二种是取引用。
取地址当然就是取得变量地址,这意味着两件事:第一,它的右侧必须跟着一个左值;第二它返回的值的类型是指向某个地址的变量————即指针。
另一种引用是引用。引用相当于给 n 变量取了一个别名 nn。如果改变 n 的值,也就改变了 nn的值。这两个变量的地址是完全一样的,这不同于定义一个指针。nn 并不是一个指针,它和 n 有相同的变量类型和地址。
前面提到了 a++ 和 a ,而 &a 和 &++a 都是错误的,但是原因并不相同。前者是因为返回了右值,后者是因为视图修改常量指针。
指针的 + 运算
指针的 + 运算会得到不同的结果。具体来说就是,指针 +1 时,指的并不是地址 +1 字节,而是 +1 指针指向的变量大小单元。如果是 int 型指针,那么 +1 指的就是 4 字节,如果是 char 型,就是 1 字节。
如前所述,数组是数组类型而不是指针。所以一个指向数组的指针 +1 ,并不是加一个指针大小(64位系统上是 8 字节),而是加一个数组长度。首地址加数组长度,很显然指向的就是数组地址的尾地址 +1 了。
我们通过一个简单的小测试来验证这个说法:输出(int*)&a+1和(cahr*)&a+1。可以发现前者是在首地址基础上 +4,后者是 +1.这是因为数组变量型指针在 +1 前被转换成了 int型和 char 型,从而影响到了后续的 +1 操作的含义。
一个有趣的事情是,+ 运算符的优先级高于 & 运算符。但是在 &a+1 中会先执行 & 运算,这是因为如果先执行 + 运算,返回的右值无法进行 & 运算,出现语法错误,所以编译器隐式转换成了(&a)+1。
总结
这里是前面的总结。
- a:数组变量,直接输出得到其存储的数组起始地址。
- a+1:数组变量退化成指向数组起始地址的指针常量,再加一个数组内单位长度
- &a:取数组变量的地址,会得到值是数组变量的起始地址,但是数据的类型是数组变量型指针
- &a+1:等价于 (&a)+1,因此是数组变量地址的邻接地址
- &a++:错误,a++ 返回右值,返回值不是真正 a 的值,违反了 & 的要求
- &++a:错误,++a返回左值,但是数组变量指向的地址不可修改
- &(a+1):错误,a+1是右值
(int*)&a+1: 将数组变量类型指针转换为int型指针再加一个单位