C语言自学完备手册(28)——指针(2)

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


讲给Android程序员看的前端系列教程(图文版)
讲给Android程序员看的前端系列教程(视频版)
Android程序员C语言自学完备手册


版权声明

  • 本文原创作者:谷哥的小弟
  • 作者博客地址:http://blog.csdn.net/lfdfhl

数组与指针概述

数组和指针虽然是不同的东西,但是两者有着非常紧密的关系。在C语言中:数组名原则上会被解释为指向该数组起始元素的指针。 通俗地来讲:数组名是一个指针,它指向了数组的起始元素。例如:有一个数组a[5],那么数组名a就是一个指针,它指向了该数组的起始元素。

请看如下示例:

#include 
#include 

int main()
{
    int a[5]={1,2,3,4,5};
    printf("a=%p\n",a);
    printf("&a=%p\n",&a);
    printf("&a[0]=%p\n",&a[0]);
    return 0;
}

结果如下:

a=0060FEFC
&a=0060FEFC
&a[0]=0060FEFC

从该示例中也可以看:a和&a以及&a[0]的值是一致的,它们均代表了数组起始元素a[0]的地址。

明白了这一点,我们再来看如下示例:

#include 
#include 

int main()
{
    int a[5]={1,2,3,4,5};
    int *p;
    p=&a[0];
    printf("&a=%p\n",&a);
    printf("&a[0]=%p\n",&a[0]);
    printf("p=%p\n",p);
    printf("*p=%d\n",*p);
    printf("a[0]=%d\n",a[0]);
    return 0;
}


在示例中声明了数组a和指针p,并将&a[0]的值存入p。也就是说:指针p会被初始化为指向数组a的起始元素a[0],即*p=a[0]。

结果如下:

&a=0060FEF8
&a[0]=0060FEF8
p=0060FEF8
*p=1
a[0]=1

之前我们讲解并验证了:a和&a以及&a[0]的值是一致的,它们均代表了数组起始元素a[0]的地址。 所以,我们可以优化刚才的示例:

#include 
#include 

int main()
{
    int a[5]={1,2,3,4,5};
    int *p=a;
    printf("&a=%p\n",&a);
    printf("&a[0]=%p\n",&a[0]);
    printf("p=%p\n",p);
    printf("*p=%d\n",*p);
    printf("a[0]=%d\n",a[0]);
    return 0;
}

对比可知:将如下两句代码

  int *p;
  p=&a[0];

替换成了:

int *p=a;

从这里我们可以看出: 数组名a会被解释为&a[0],存入p的值为&a[0]的值,即p=&a[0]

至此,我们对指针与数组的基础知识做一个总结。

核心小结:

假设指针p指向数组a,我们知道:

1、数组名原则上会被解释为指向该数组起始元素的指针。

2、a和&a以及&a[0]的值是一致的,它们均代表了数组起始元素a[0]的地址。

3、指针p会被初始化为指向数组a的起始元素a[0],即*p=a[0]

4、数组名a会被解释为&a[0],存入p的值为&a[0]的值,即p=&a[0]


指针的移动

指针p指向数组的元素e时,遵循以下规则:

  • p+i 为指向元素e后第i个元素的指针
  • p-i 为指向元素e前第i个元素的指针

之前,我们已经总结过:数组名原则上会被解释为指向该数组起始元素的指针,所以该规则也可以如下描述:

  • a+i 为指向元素e后第i个元素的指针
  • a-i 为指向元素e前第i个元素的指针

也就是说:指针p指向数组a时p+i与a+i和&a[i]是等价的。 当i=0时,则p与a和a[0]是等价的;这又回到了之前已经讲过的内容了,不再赘述。

请看如下验证示例:

#include 
#include 

int main()
{
    int i;
    int a[5]={1,2,3,4,5};
    int *p=a;
    for(i=0;i<5;i++){
        printf("&a[%d]=%p,p+%d=%p,a+%d=%p\n",i,&a[i],i,p+i,i,a+i);
    }
    return 0;
}

结果如下:

&a[0]=0060FEF4,p+0=0060FEF4,a+0=0060FEF4
&a[1]=0060FEF8,p+1=0060FEF8,a+1=0060FEF8
&a[2]=0060FEFC,p+2=0060FEFC,a+2=0060FEFC
&a[3]=0060FF00,p+3=0060FF00,a+3=0060FF00
&a[4]=0060FF04,p+4=0060FF04,a+4=0060FF04

示意图如下所示:
C语言自学完备手册(28)——指针(2)_第1张图片

既然p+i与a+i和&a[i]是等价的,这三者均表示地址,那么我们再取出它们各自中保存的值看看:

#include 
#include 

int main()
{
    int i;
    int a[5]={1,2,3,4,5};
    int *p=a;
    for(i=0;i<5;i++){
        printf("&a[%d]=%p,p+%d=%p,a+%d=%p\n",i,&a[i],i,p+i,i,a+i);
    }
    puts("----------line------------");
    for(i=0;i<5;i++){
        printf("a[%d]=%d,*&a[%d]=%d,*(p+%d)=%d,*(a+%d)=%d\n",i,a[i],i,*&a[i],i,*(p+i),i,*(a+i));
    }
    return 0;
}

结果如下:

&a[0]=0060FEF4,p+0=0060FEF4,a+0=0060FEF4
&a[1]=0060FEF8,p+1=0060FEF8,a+1=0060FEF8
&a[2]=0060FEFC,p+2=0060FEFC,a+2=0060FEFC
&a[3]=0060FF00,p+3=0060FF00,a+3=0060FF00
&a[4]=0060FF04,p+4=0060FF04,a+4=0060FF04
----------line------------
a[0]=1,*&a[0]=1,*(p+0)=1,*(a+0)=1
a[1]=2,*&a[1]=2,*(p+1)=2,*(a+1)=2
a[2]=3,*&a[2]=3,*(p+2)=3,*(a+2)=3
a[3]=4,*&a[3]=4,*(p+3)=4,*(a+3)=4
a[4]=5,*&a[4]=5,*(p+4)=5,*(a+4)=5

我们发现:指针p指向数组a时*(p+i)与a[i]和*&a[i]以及*(a+i)是等价的!

接下来,我们继续来看另外一个规则:指针p指向数组的元素e时,遵循以下规则:

  • 指向元素e后第i个元素的*(p+i),可以写为p[i]
  • 指向元素e前第i个元素的*(p-i),可以写为p[-i]

既然*(p+i)可以写为p[i] 那么可得知:指针p指向数组a时*(p+i)和p[i]与a[i]和*&a[i]以及*(a+i)是等价的! 刚才的代码可以继续完善:

#include 
#include 

int main()
{
    int i;
    int a[5]={1,2,3,4,5};
    int *p=a;
    for(i=0;i<5;i++){
        printf("&a[%d]=%p,p+%d=%p,a+%d=%p\n",i,&a[i],i,p+i,i,a+i);
    }
    puts("----------line------------");
    for(i=0;i<5;i++){
        printf("a[%d]=%d,*&a[%d]=%d,*(p+%d)=%d,*(a+%d)=%d,p[%d]=%d\n",i,a[i],i,*&a[i],i,*(p+i),i,*(a+i),i,p[i]);
    }
    return 0;
}


结果如下:

&a[0]=0060FEF4,p+0=0060FEF4,a+0=0060FEF4
&a[1]=0060FEF8,p+1=0060FEF8,a+1=0060FEF8
&a[2]=0060FEFC,p+2=0060FEFC,a+2=0060FEFC
&a[3]=0060FF00,p+3=0060FF00,a+3=0060FF00
&a[4]=0060FF04,p+4=0060FF04,a+4=0060FF04
----------line------------
a[0]=1,*&a[0]=1,*(p+0)=1,*(a+0)=1,p[0]=1
a[1]=2,*&a[1]=2,*(p+1)=2,*(a+1)=2,p[1]=2
a[2]=3,*&a[2]=3,*(p+2)=3,*(a+2)=3,p[2]=3
a[3]=4,*&a[3]=4,*(p+3)=4,*(a+3)=4,p[3]=4
a[4]=5,*&a[4]=5,*(p+4)=5,*(a+4)=5,p[4]=5

核心总结

指针p指向数组a时:

(1) &a[i]、a+i、&p[i]、p+i均表示指向数组中第i个元素的指针

(2) a[i]、*(a+i)、p[i]、*(p+i)均表示对数组中第i个元素的访问

验证代码:

#include 
#include 

int main()
{
	int i;
	int a[5] = {1, 2, 3, 4, 5};
	int *p = a;
	for (i = 0; i < 5; i++){
        printf("&a[%d] = %p  a+%d = %p  &p[%d] = %p  p+%d = %p\n",i, &a[i], i, (a + i), i, &p[i], i, (p + i));
	}
	for (i = 0; i < 5; i++){
		printf("a[%d] = %d  *(a+%d) = %d  p[%d] = %d  *(p+%d) = %d\n",i, a[i], i, *(a + i), i, p[i], i, *(p + i));
    }
	return 0;
}

结果如下:

&a[0] = 0060FEF4  a+0 = 0060FEF4  &p[0] = 0060FEF4  p+0 = 0060FEF4
&a[1] = 0060FEF8  a+1 = 0060FEF8  &p[1] = 0060FEF8  p+1 = 0060FEF8
&a[2] = 0060FEFC  a+2 = 0060FEFC  &p[2] = 0060FEFC  p+2 = 0060FEFC
&a[3] = 0060FF00  a+3 = 0060FF00  &p[3] = 0060FF00  p+3 = 0060FF00
&a[4] = 0060FF04  a+4 = 0060FF04  &p[4] = 0060FF04  p+4 = 0060FF04
a[0] = 1  *(a+0) = 1  p[0] = 1  *(p+0) = 1
a[1] = 2  *(a+1) = 2  p[1] = 2  *(p+1) = 2
a[2] = 3  *(a+2) = 3  p[2] = 3  *(p+2) = 3
a[3] = 4  *(a+3) = 4  p[3] = 4  *(p+3) = 4
a[4] = 5  *(a+4) = 5  p[4] = 5  *(p+4) = 5

图示如下:
C语言自学完备手册(28)——指针(2)_第2张图片


数组名与指针

通过之前的学习,给我们一种感觉:数组名和指针是同一回事,两者是等价的;而且有的书上明确指出数组名就是指针数组的指针。其实,这类的说话有失偏颇,是不够准确、不够严谨的。

先来看四小东西:数组,指针,指针常量,指针变量。

  • 指针不再多说
  • 指针常量——它是一个常量,该常量占用一定大小的内存,在内存中存放的就是地址(指针)。但该地址值是不可以修改的,更不能执行自增,自减操作。
  • 指针变量——它是一个变量,该变量占用一定大小的内存,在内存中存放的就是地址(指针)。但是它可以像其它变量类型一样进行操作,例如赋值、自增、自减等等。
  • 数组——它是一种数据结构,逻辑结构;并不是一种物理结构。数组名可在参与运算时转化为指针常量并指向数组首地址,当访问数组元素时,就是在指针常量的基础上进行偏移从而访问元素。所以,数组名也不是指针变量。

其实,我们也能通过非常简单的方式证明数组名和指针不是等价的;代码如下:

#include 
#include 

int main()
{
    int arraySize,pointerSize;
    int a[5]={1,2,3,4,5};
    int *p=a;
    arraySize=sizeof(a);
    pointerSize=sizeof(p);
    printf("arraySize=%d,pointerSize=%d",arraySize,pointerSize);
    return 0;
}

在该示例中分别将数组名a和指针p传递给sizeof( ),结果如下:

arraySize=20,pointerSize=4

嗯哼,看到了吧;两者所占内存空间并不一样!压根就不是同一个东西。

之前,我们学过赋值运算符=,现在我们来看如下示例:

#include 
#include 

int main()
{
    int a[5]={1,2,3,4,5};
    int b[5]={6,7,8,9,0};
    a=b;
    return 0;
}

从表面来看该示例代码没有任何错误,但事实上第8行代码a=b; 是错误的!虽然数组名a会被解释为执行数组首元素的指针,但是不可改写其值。如果可以这样赋值,那么数组的地址就会被改变!所以,赋值表达式的左侧不可为数组名!


数组名与函数

在学习了指针之后,我们再回过头来看之前的代码,如下所示:

#include 
#include 

//获取数组中的最大值
int getMax(int a[],int length){
    int i,max;
    max=a[0];
    for(i=0;imax){
            max=a[i];
        }
    }
    return max;
}

int main()
{
    int a[]={3,4,5,1,2};
    int length=sizeof(a)/sizeof(a[0]);
    int max=getMax(a,length);
    printf("数组的最大值是:%d",max);
    return 0;
}

函数int getMax( )的第一个参数a,本质上而言是一个指针!该函数为什么要设计两个输入参数呢?有指针不就够了么?假若函数getMax( )只接收一个指向数组的指针,那么是无法知道该数组的长度的!所以,不但需要指向数组的指针还需要知晓数组的长度。故,一般而言在函数调用过程中传递数组时会同时传递数组长度。

如果,不这么做会怎么样呢?请看如下示例:

#include 
#include 

void printArray(int a[]){
     int i;
     for(i=0;i<10;i++){
        printf("a[%d]=%d\n",i,a[i]);
     }
}

int main()
{
    int a[5]={1,2,3,4,5};
    printArray(a);
    return 0;
}

在本示例中:数组长度为5,但是for循环的条件是i<10;假若在Java中那么避免会报数据越界异常ArrayIndexOutOfBoundsException,那么,在这里也会么?

结果如下:

a[0]=1
a[1]=2
a[2]=3
a[3]=4
a[4]=5
a[5]=56
a[6]=2
a[7]=6356884
a[8]=4198653
a[9]=1

Process returned 0 (0x0)   execution time : 0.669 s
Press any key to continue.

嗯哼,我们可以看到程序没有报错,而且a[5]—a[10]的值依然可以打印出来!!

程序修改如下:

#include 
#include 

void printArray(int a[],int len){
     int i;
     for(i=0;i

所以,在C语言中操作数组时需要我们自己明确数组的长度并作出相应的限制从而避免越界等异常情况。

你可能感兴趣的:(C/C++,C语言自学完备手册)