摘要:总结了数组的本质,通过实例阐述了a+1和&a+1的区别,指针的关系,比较运算,指出为什么只能进行相减运算及其意义,解释了数组与下标运算的优劣,解析了一道面试题,说明了数组和指针作为函数参数的时候需要注意的地方,最后总结了数组和指针的一些差别。
一、数组的本质
数组是一段连续的内存空间,数组名指向数组首元素的地址。
数组占用的空间的大小为sizeof(array_type)*arrsy_size,比如这里我们定义一个数组int a[5],大小就是sizeiof(int)*5=20,这个数组占用空间的大小就是20。
数组名是一个常量指针,这是因为其只能作为左值使用,不可以作为右值。
a+1代表什么呢?运算结果呢?可以看下面的例子;
#include
int main(void)
{
char a[5]={'h','e','l','l','o'};
int b[6]={1,3,5,4,5,6};
printf("arraya size is :%d\n",sizeof(char)*5);
printf("arrayb size is :%d\n",sizeof(int)*6);
printf("*a+1is :%d\n",*a+1);
printf("*b+1is :%d\n",*b+1);
printf("ais :%0x\n",a);
printf("bis :%0x\n",b);
printf("a+1is :%0x\n",a+1);
printf("b+1is :%0x\n",b+1);
printf("*(a+1)is :%d\n",*(a+1));
printf("*(b+1)is :%d\n",*(b+1));
return0;
}
运行结果如下:
arraya size is :5
arrayb size is :24
*a+1 is :105
*b+1 is :2
a is:bfad444b
b is:bfad4430
a+1 is:bfad444c
b+1 is:bfad4434
*(a+1) is :101
*(b+1) is :3
可以看到a+1和b+1都指向了数组中下一个元素的地址,我们把其中的值取出来也和我们定义的元素的值是一致的,也就是a[i]=*(a+i)。
二、指针的运算
其实上面的例子我们已经用到了指针的运算,这里规则如下。
1.指针是一种特殊的变量,其与整数运算的规则为:
p+n=(unsigned int)p+n*sizeof(*p)
当指针指向一个数组中元素时候,p+n表示从当前元素往下第n个元素,p-n表示从当前元素之前的第n个元素。
2.指针之间只支持减法运算,并且参与运算的指针的类型必须相同:
p1-p2=(unsigned int)p1-(unsignedint)p2/sizeof(type)
只有当两个指针指向同一个数组中的元素时候,才会有意义,相减的结果是数组元素的下标差,当两个指针指向不同数组的元素的时候,相减的意义未知。之所以不能相加,和相乘我想是因为太容易越界了,相除也会造成未知数的产生,相减也只是限定在同一个数组中,这样才会有意义,假如一个在栈空间,一个在堆空间,相减的意义是无法给出的。
三、指针的比较
指针也可以进行关系运算:<,<=,>,>=,前提还是必须指向同一个数组中的元素,但是指针之间的比较运算==,!=是可以在任意指针之间的进行的,这个其实很好理解,既然在同一数组中的指针可以进行相减,那么关系大小也是可以比较的,任意两个指针其中存放的无非是地址,地址之间比较大小也是可以的。
下面给出的例子就是利用这一点打印出了hello;
#include
#define DIM(s) sizeof(s)/sizeof(char)
int main(void)
{
chars[5]={'h','e','l','l','o'};
char*pbegin=s;
char*pend=s+DIM(s);
char*p=NULL;
for(p=pbegin;p
结果当然就是输出了hello,这里for循环语句里面的就用到了指针之间的关系运算。
四、数组的访问,下标和指针
1.以下标的形式去访问,例如a[1]=1;a[2]=2;
2.以指针的形式去访问,例如*(a+1)=3;*(a+3)=5;
从理论上而言,当指针以固定增量在数组中移动时,其效率高于下标产生的代码,并且当指针增量为1且硬件具有硬件增量模型时,表现更佳。
但是现在编译器生成代码的优化率已经大大的提高,在固定增量的时候,下标的效率已经和指针相当,但是从代码可读性和可维护性的角度来看,下标具有优势。
之所以指针的效率高,拿赋值语句做比较,指针进行的是*p1++=*p2++,其中需要进行的是自增运算,但是数组比如a[i]=a[j],需要用首地址+i或者j*sizeof(type),这其中包含一个乘法运算,所以效率就慢在这里了。
c语言中可以用clock()得到当前的毫秒数,可以写一个例子start存放执行前的代码,结束后可以end=clock(),然后相减得到程序运行的时间,这里就不做演示了。
五、a和&a的区别
这部分我觉得是最重要的了,之前也讨论过数组地址和数组首元素的地址的区别,虽然值是一样的,但是意义不同,这里在进行指针运算的时候区别就出来了:
a代表的是数组首元素的地址;
&a代表的是数组的地址;
指针运算具有区别:
a+1=(unsigned int)a+sizeof(*a),a的绝对值加上里面元素的大小
&a+1=(unsigned int)(&a)+sizeof(*&a)。a的绝对值加上整个数组的大小,因为&和*是一个相反的运算,最后其实就是a。这里&a+1就指向数组的末尾了。
这可以写一个例子验证一下,例子如下:
#include
int main(void)
{
chara[5]={'h','e','l','l','o'};
intb[6]={1,3,5,4,5,6};
printf("arraya size is :%d\n",sizeof(char)*5);
printf("arrayb size is :%d\n",sizeof(int)*6);
printf("ais :%0x\n",a);
printf("bis :%0x\n",b);
printf("a+1is :%0x\n",a+1);
printf("b+1is :%0x\n",b+1);
printf("&ais :%0x\n",&a);
printf("&bis :%0x\n",&b);
printf("&a+1is :%0x\n",&a+1);
printf("&b+1is :%0x\n",&b+1);
return0;
}
运行结果如下:
array a size is :5
array b size is :24
a is :bfdf78fb
b is :bfdf78e0
a+1 is :bfdf78fc
b+1 is :bfdf78e4
&a is :bfdf78fb
&b is :bfdf78e0
&a+1 is :bfdf7900
&b+1 is :bfdf78f8
可以看到,&a+1比&a地址大了5,&b比&b+1大了24,正好是我们定义的两个数组的大小,在看a+1和b+1他们分别指向了下一个元素的地址。
六、面试题解析
#include
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
int* p3 = (int*)(a + 1);
printf("%d, %0x, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
其实这里考的主要是p1和p2的指针的操作,我们知道&a+1就是指向a数组的末尾,这里p1[-1]就是往前移一个位置,所以输出的是5。第二个,(int)a+1的意思是在首地址的基础上,往下移动一个字节的意思,这样就错位了,可以看下面这个图:
这里涉及到系统大小端的存放模式,由于是小端系统,例如0x12345678在小端系统下的存储顺序是87,65,43,21,所以这里的0,0,0,2其实存放的是00,00,00,20,那么打印出来就是0x02000000了;第三个不用多说了,打印出来的是3.这里运行结果如下:
5,2000000,3
七、数组参数
C语言中,数组作为函数参数的时候,编译器将其编译成对应的指针,所以一般情况下,当定义的函数中有数组作为参数的时候,需要定义另外一个变量声明数组元素的个数,也就是数组的大小,就像下面,无论你声明不声明数组大小,在作为函数参数的时候,到了编译器处理的时候全部转化为一个指针,在该函数里面使用sizeof(a)的时候,一般都是等于4,而不是数组的实际大小。
voidf(int a[]) è voidf(int* a);
voidf(int a[5]) è voidf(int* a);
八、指针和数组的对比
1.数组声明的时候,编译器自动分配一片连续的内存空间。
2.指针分配得时候,值分配了容纳指针大小的4字节空间。
3.在作为函数参数的时候,数组参数和指针参数等价。
4.数组名在多数情况下可以看做常量指针,其值不会被改变。
5.指针的本质是变量,其中保存的值是内存中的地址。