C语言学习笔记(17) 数组和指针分析

摘要:总结了数组的本质,通过实例阐述了a+1和&a+1的区别,指针的关系,比较运算,指出为什么只能进行相减运算及其意义,解释了数组与下标运算的优劣,解析了一道面试题,说明了数组和指针作为函数参数的时候需要注意的地方,最后总结了数组和指针的一些差别。


一、数组的本质

    数组是一段连续的内存空间,数组名指向数组首元素的地址。

    数组占用的空间的大小为sizeof(array_type)*arrsy_size,比如这里我们定义一个数组int a[5],大小就是sizeiof(int)*5=20,这个数组占用空间的大小就是20。

    数组名是一个常量指针,这是因为其只能作为左值使用,不可以作为右值。

    a+1代表什么呢?运算结果呢?可以看下面的例子;

<span style="font-size:18px;">#include <stdio.h>
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</span>

    可以看到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;

<span style="font-size:18px;">#include <stdio.h>
#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<pend;p++)
       {
           printf("%c",*p);
           }
    printf("\n");
   
    return0;
    }</span>

    结果当然就是输出了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就指向数组的末尾了。

    这可以写一个例子验证一下,例子如下:

<span style="font-size:18px;">#include <stdio.h>
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</span>

    可以看到,&a+1比&a地址大了5,&b比&b+1大了24,正好是我们定义的两个数组的大小,在看a+1和b+1他们分别指向了下一个元素的地址。


六、面试题解析

<span style="font-size:18px;">#include <stdio.h>
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;
}</span>

    其实这里考的主要是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.指针的本质是变量,其中保存的值是内存中的地址。


    这篇帖子就总结到这里,如有不正确的地方还请指出,大家共同进步!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值