初步探讨数组和指针的区别与联系

来源:互联网 发布:剑灵龙族捏脸数据 编辑:程序博客网 时间:2024/05/20 00:37

在学习和使用指针与数组的时候,我总对指针和数组的关系充满疑惑,因此,以现有的知识和理解,我对它们做了一些归纳.

首先说一个结论,数组名不是指针常量.
从编译器的角度来看
当程序员定义了一个指针

int *p;

这时需要开辟4字节的空间,这段空间名字就是p,这段空间的起始地址就是p的地址,因为未初始化,这段空间所存的内容还是默认的随机值,程序员将会用这段空间存储一个地址,这个地址本身是一个整型变量的起始地址.
这里有几点需要注意的
p存储的内容可以修改,但是p本身的起始地址是不可修改的.
无论p指向什么类型的变量,都只会存储这个对象的起始地址,作为一个编译器,永远只为p开辟四个字节的空间,存储一个地址.
编译器不为p指向的对象开辟空间,这个对象的空间是创建它的时候开辟的.
无论程序员给p赋值了一个什么内容,编译器都把这个值看做一个地址.
编译器还要牢记定义给这个指针的类型属性,即它能够指向什么对象,并且在之后要用的时候严格匹配.

当程序员定义了一个数组

int arr[8];

这时需要开辟两段空间,第一段空间存的是地址,指向arr.第二段空间存的是数组元素,名字是arr,大小是sizeof(type)*size.
作为一个机智的编译器,我们把第二段空间的起始地址看做这个数组的地址,这个起始地址和arr这个名称也是绑定在一起不可修改的,所以不能允许程序员再给arr其他的地址.
有趣的是,这个空间的起始地址也是第一个元素的起始地址,这样我们要找某个元素的时候就很简单了,定义中给出了这些元素的类型,我们就知道每隔sizeof(type)字节就是一个新的元素.
这里也有几个需要注意的地方
由于我们找元素的时候是从起始地址向后偏移固定的字节数,这个行为太像指针了!好吧其实就是一样的,在人看不到的地方我们就是把起始地址当做一个常量指针然后对它进行操作.作为一个人性化的编译器,我们也应该允许程序员显式的使用指针进行这种操作.但是只限于进行下标运算找到对应元素这种行为,其他情况我们的眼里没有指针.
作为一个严谨的编译器,我们开辟的这段空间都是用来存值的,定义说存什么类型的值就存什么类型,不是这个类型的也看做这个类型.

所以就会出现这样的情况
当程序员在文件1定义一个数组

char arr[10]="abcdef";

但在文件2声明成一个指针

extern char* arr;

这个时候在文件二,我们把arr向后四个字节所存的值全部看做地址
所以当我们对a解引用,就是对0x97989910这个地址进行解引用,实际上它们分别是abcd对应的ascii码!

当另一个程序员在文件1定义了一个指针

char* p="abcdef";

但在文件二声明成一个数组

char p[];

这时在文件2,我们把p的内容,即字符串常量的地址认为是字符对应的ascii值,每隔一个字节取一个字符,假设p=0x55667788,此时p[0]=0x55,p[1]=0x66….
并且很恐怖的是,当程序员修改p[0],p[1]…这些数组元素的时候,会把p保存的内容修改,导致p不在指向”abcdef”,而会指飞.


接下来从使用的角度看

根据《C陷阱与缺陷》所说
对于一个数组,我们只能过做两件事:
确定该数组的大小
获得所指向该数组下标为0的元素的指针.
其他有关数组的操作,哪怕它们咋看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义一个数组小标的行为。

有两种典型的对于数组的行为是区别于指针的

sizeof(arrayname);&arrayname;

另外值得注意的是
当用字符串字面量初始化字符数组的时候
与用字符串字面量初始化指针的时候
他们开辟空间的段是不一样的。
除了这些情况,可以说
在使用时,数组名都会隐式的转换为数组首元素指针右值
比如你用方括号取值,如果方括号左边是个数组,那么数组名会被隐式的转换成首元素指针右值,然后对这个值进行解引用。
因此我们才能定义一个整型数组int arr[]; 却用* arr这个指针对它进行操作,实际上我们使用arr[1]的时候编译器内部的行为是使用 *(arr+1);

尽管这个数组名被隐式转换成了首元素指针,但是它是右值,它的内容是首元素的地址,指向首元素,是不可作为”可修改的左值”放在等号左边的,即数组名不可单独放在等号左边.


实际上,c语言中只有一维数组,但是,数组中的元素可以是任何类型的对象,因此若设想这些元素是另一些数组,就可以轻易的理解二维数组的概念.
假如定义一个整型二维数组

int arr[size1][size2];int *p;

size1表示一维数组有size1个元素,这些元素是数组
size2表示每个作为元素的数组有size2个元素,这些元素是整型。
因此有以下几点结论
sizeof(arr)=4*size1*size2
arr[1]表示arr数组的第一个元素,它本身是一个数组,它的行为也应看做是一个数组的行为
p=arr[1];
p指向了arr[1][0]这个元素.
p=arr;
这条语句是不合法的,因为p被定义为一个指向整型变量(int*) 的指针,而arr隐式转换为一个指向整型数组(int(*)[size2])的指针,他们的级别是不匹配的.
必须定义一个指向整型数组的指针int (*p)[size2]与arr匹配

    int  a[3][9] = { { 1, 2, 3, 4, 5 }, { 11, 22, 33, 44, 55 }, { 12, 13, 14, 15, 16 } };    int (*p)[9];    p = arr;      printf("%d\n", **(arr+1));    //11  arr+1指向第二个数组,*(arr+1)指向该数组第一个元素    printf("%d\n", *(*arr + 1));  //2  *arr指向第一个数组,*arr+1指向该数组第2个元素    printf("%d\n", p[1][1]);      //22  p[1]等价于*(p+1)指向第二个数组第一个元素,p[1][1]等价于*(*(p+1)+1)指向第二个数组第二个元素    printf("%d\n", (*p + 1)[1]);  //3   *p指向第一个数组第一个元素,(*p+1)[1]等价于*(*p+1+1),其中*p+1+1指向第一个数组第三个元素    printf("%d\n", *(p + 1)[1]);  //12 (p+1)指向第二个数组,*(p+1)[1]等价于**(p+1+1),*(p+1+1)指向第三个数组第一个元素    //由此可以看出[]优先级高于*

补充几点
对于一个用作定义的表达式,去掉名称就是类型
数组名的类型就是数组类型
对二维数组名取地址,得到的类型是指向数组的指针type(*)[size]

根据以上的分析得出结论
编译器对待数组和指针是不同的,不应该简单的认为数组名就等价于指针常量,要认真的区分指针行为和数组行为.但是当数组以下标进行运算的时候,编译器已经把数组暗搓搓地替换成指针,这种情况下两者完全等价.