1.从内存和编译器角度来理解数组
1.1内存角度和编译器角度
(1)定义五个变量,普通定义int a,b,c,d,e;和数组定义int a[5];
1.都是定义了五个变量,第一种方法定义的变量的内存地址不一定是连续的,第二种方法定义的元素一定是地址连续的。
2.对于编译器来说,定义数组和定义其他变量的本质都是一样的。
1.2来理解一些符号 a,a[0],&a,&a[0]分别和左值右值的关系
分析:
1.(1)a做左值。数组名a做左值时表示数组的所有地址,比如:int a[5];数组名a做左值时表示的就是4*5个内存地址。我们在操作数组的时候只能操作其中的一个元素,不能整体操作。比如 a = {1,2,3,4,5};整体操作是不行的。需要注意的是:它和数组定义初始化int a[5]={1,2,3,4,5}是不同的。
(2)a做右值。数组名a做右值时,表示数组首元素的首地址,第一个元素可能占好多地址,在这里只表示第一个字节的地址。其类型是数组元素的类型的指针。而非数组指针。
2.(1)a[0]做左值,表示第一个元素的地址,当a[0] = 1;时,是把1写入到元素a[0]对应的那个内存地址中去。
(2)a[0]做右值时,test = a[0]; 表示把a[0]的值写入test的地址中去。
3.(1)&a不能做左值,a的地址是随机分配的,但是一旦分配之后,在程序执行的时候,a的地址已经确定,所以成了常量,常量不能做左值。
(2)&a做右值,表示,整个数组的首地址。也就是一个指向数组的指针,所以&a做右值时的类型是数组指针。也就是int (*)[]类型。
4.(1)&a[0]不能做左值,道理和&a一样,变成了常量。
(2)&a[0]做右值时,表示数组首元素的首地址。
总结:
(1)a,&a,&a[0]在数值上是相等的,但是类型和含义完全不同。
(2)a和&a做右值的区别。a代表数组首元素的首地址,类型是int* 。&a表示数组的首地址,类型是int(*)[];
(3)a和&a[0]做右值时都是表示数组首元素的首地址。
2.从数组和指针的角度理解数组
1.通过代码看数组和指针的关系
#include<stdio.h> void main(void) { int a[5] = {1,2,3,4,5}; int *p = a; //将数组名a的值赋值给指针p //数组名a做左值时表示数组首元素的首地址类型是int* //所以这里 a 和 p 类型一致 //通过数组下标访问元素 printf("通过数组下标方式:第2个元素是 %d ",a[1]); //通过指针访问数组元素 printf("通过指针方式:第2个元素是 %d ",*(p+1)); }
分析:
(1)数组名a做右值时表示数组首元素的首地址,当p = a后,p也保存了数组首元素的首地址,当p+1时,p+1就指向了数组的第二个元素的首地址,以此类推。这里注意一点,p只是保留了首地址(一个字节),而第一个元素是int类型,占4个字节,这时候就靠指针p的数据类型的作用了,一看p是int*类型的指针,自动会根据p保留的第一个字节的地址去补齐剩下的那3个字节的地址,当找到4个字节地址后再去读取该值。
(1.1)比如 int a[5];int * p = a;当p自增1时,其实是自增(1+sizeof(指针类型)),举例:当int * p;p实际上是自增4个字节,char * p;p实际上是自增1个字节。
(1.2)指针在进行运算的时候,指针自增多少关键看指针类型。这时候再来看&a和a。&a做右值是数组指针,当&a自增时,实际上是增加了一个数组sizeof(int) * 5= 20个字节。而 a 自增实际上是自增了一个sizeof(int)大小的字节。
(3)看上面的代码,int * p = a; 若是改成int* p=&a;会怎样?一步一步分析:&a做右值时,代表数组的首地址,其数据类型是int (*)[5],这就得出了结果,&a和p类型不符,编译器报错。
(4)再来回顾一下,数组元素地址是连续的。所以指针访问数组才能自增去寻址。
总结
(1)a,a[0],&a,&a[0],做左值和做右值的区别
(2)指针自增的含义
(3)指针类型的含义
(4)数组和指针的相似处
下次说说指针和类型转换的内容