数组名的再理解
先看下面的这段代码,程序会输出什么结果?
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *p = (int *)(&a+1);
printf("%d %d\n", *(a+1), *(p-1));
return 0;
}
答案详见本文的最后。
先来一步步分析一下数组名到底是什么,首先看一下下面这段代码:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *p;
p = a;
return 0;
}
编译和运行都没有错误,说明a的值的类型直接赋给p是可以的,至少这说明了a的类型是属于可以直接赋值给p的类型中的一种,即a应该是一个指针类型的变量,而且这个指针类型变量的绝对不是int*类型的,因为在上面的程序中如果我们输出sizeof(a)和sizeof(p)的话可以看到一个输出的是20,一个输出的是4,证明这两个类型是不一样的,程序稍作修改:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *p;
a = p;
return 0;
}
编译器报错误:cannot convert from ‘int *‘ to ‘int [5]‘,然而这个int [5],C语言里并没有这种类型,这个类型其实很明显就是int a[5]把a去掉之后剩下的。暂时可以这样理解,a就代表的分配的20个字节的单元整体,同时a本身的值是这20个字节的起始地址,所以a是有两层含义的。再改一下程序:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *p;
a ++;
return 0;
}
编译器报错:‘++‘ needs l-value,这个错误的意思是++需要一个可以作为左值的操作对象,++唯一能够操作的就是变量,然而a根据上面的分析就是分配的一个20字节的变量,a的值也就是这20个字节变量的首地址,然而对a进行++操作却是不可以的,这个特点跟const修饰的变量时一致的。const修饰的变量相当于被赋予了只读属性,也就是说读取是可以的,写操作是非法的,所以const修饰的变量是不能做左值的,也就是不能被重新赋值的。再次修改一下例子:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *p;
p = &a;
return 0;
}
编译器报错:cannot convert from ‘int (*)[5]‘ to ‘int *‘,有了这个错误问题就越来越清晰了,对a的取地址操作得到的类型是int (*)[5],即一个数组指针,也就是说这个时候取地址操作对a的理解是一个20个字节的整体(5个int型的单元)。然而当采用p=a这种使用方法的时候,使用的是a的第二种理解方法就是取的是a的值,也就是这20个字节的起始地址。
再由C语言的优先级来分析最开始的题目,int *p = (int *)(&a+1)这条语句,&的优先级高于+,所以等同于int *p (int *)((&a)+1),这下来逐步分析,&a得到的是一个5个int型单元的数组指针,&a+1得到的就是这个指针加1的结果,也就是说现在的指针会指向a这20个字节后的第一个单元,这个单元的地址再被强制转换为int *类型赋值给p,所以p也就指向了a的20个字节后的第一个单元,所以*(p-1)取到的值就应该是p的前一个单元,也就是a的5个单元中最后的一个单元的值。所以得到最开头的程序输出为:2
5