如前所述,数组是一组内存中连续的存储单元,每一个单元都有自己的地址,如有定义int a[5],且数组的首地址为1000,则该数组的内存存储形式如图7-6所示。
图7-6 一维数组存储示意
由于该数组基类型为int,因此每一个单元占4个字节。我们可以定义一个指针来保存某一个数组单元的地址,此时我们就说该指针指向了数组元素,其定义形式同我们前边所学的定义形式相同,格式为:
类型说明符*指针变量名;
需要注意的是,由于要保存的是数组单元的地址,因此在定义时类型说明符必须要和数组的基类型一致。
例如:
int arr[5];
int*p;
则p=&a[i](0≤i≤4)是完全正确的,如可以写成p=&arr[0]。根据前边所学的指针的基本用法,当指针中保存了i号单元的地址时,*p就是arr[i]。由此可见,指向数组元素的指针的基本用法同前边所讲的指针的基本使用方法完全一致。
由于数组名字代表的就是数组的起始地址即数组零单元的地址,因此以下两个赋值语句是等价的。
p=arr;
p=&arr[0];
都是将数组零单元的地址赋给指针p,即使指针p指向数组arr的零单元,如图7-7所示。
图7-7 指向数组元素的指针
需要注意的是,指针和数组名虽然都是地址量,但是不能对数组名字赋值。这是因为在定义一个数组时,该数组被分配了一段地址连续的存储单元,这块存储单元在程序运行期间是不能改变的,可以将数组名看作是个地址常量,所以不能对它进行赋值。试想一下,如果数组名字可以被任意地址赋值,也就意味着数组可以在内存中随意地改变位置,这是C语言不允许的。而指针变量是保存地址的变量,在程序运行期间,其值是可以变化的,所以可以赋给指针任意合法的地址值。
2.指向数组元素的指针变量的初始化
同前边所讲指针变量初始化一样,也可以对指向数组元素的指针变量进行初始化,即在定义指针变量的同时,用数值单元的地址对指针变量赋值。如有数组int arr[5],则指针变量初始化可以是:
int*p=&arr[0];
如果是将零单元地址赋给指针p,也可以写作:
int*p=arr;
当然,以上初始化形式也可以将定义和赋值分开来写:
int*p;
p=arr;(或者p=&arr[0])
总之,对指向数组元素的指针进行初始化,和前边所学指针初始化形式一样,只不过赋给指针的是某一个数值单元的地址而已。
一般指针在定义后都要进行初始化,防止在做内存间接访问时由于没有初始化而引用非法的内存地址,如果确实不用赋予某一单元的地址,可以使用NULL(空指针)来对指针变量初始化。NULL是stdio.h头文件中预定义的值为0的常量,可以赋给任意类型的指针。
3.指向数组元素的指针的运算
除了前边学习过的“*”运算符合“=”外,在指针指向数组中的某一个单元时,允许对指针进行++、--及和整数进行加减的运算。
(1)指针加减整数
在将指针指向数组中的某一个单元后,运行指针加减一个整数i,其含义为让指针指向当前位置开始向前(p-i)或向后(p+i)的第i个单元,比如指向p+3或者p-3,其含义如图7-8所示。
图7-8 指针加减整数
实质上指针进行p+i运算时,系统真正执行的操作是p+i*sizeof(arr),sizeof(arr)是数组中每一个单元所占字节数。运行以下程序,观察其输出结果。(www.xing528.com)
【例7-5】输出不同类型数组中单元的地址。
从运行结果可以看出,当p=&a[5]、q=&b[5]时,p+3和q+3就分别是a[8]和b[8]的地址,而p+3和p相差了12个字节,但是q+3和q相差24个字节,虽然都是将指针加上了整数3,但是得到的结果并不一样。这是因为一个int单元占4个字节,但是一个double单元要占8个字节,这就验证了我们刚刚所讲,也就是说系统中并不是直接将指针加上了3,而是加上了3和一个单元所占字节个数的乘积。
需要注意的是,在执行指针加减整数的运算时,一定不要超过了数组单元下标范围,否则将会产生地址越界,为程序带来潜在风险。
(2)指针的自加和自减运算
指针除了可以加减一个整数外,也允许自加和自减运算,这是毫无疑问的,因为从语法上来说,p++就相当于p=p+1赋值,根据前边所讲,就是让指针p指向当前单元的下一个单元,当然p--就是让指针p指向当前单元的前一个单元。在使用过程中,仍然要注意地址的越界问题。
(3)两个指针相减
当两个指针指向同一数组中的单元时,将两个指针相减,此时差值的绝对值表示两个地址间相隔的单元的个数,如图7-9所示。此时如果输出q-p的值,将得到3,也就是指针p和指针q之间相隔的单元的个数。事实上根据前边所讲可知,q=p+3,所以其差值为3也是理所当然的。
但是要注意的是,如果指针p和指针q不是指向的同一个数组中的单元,此时将两个指针相减是没有意义的。另外两个指针相加在编程中没有意义。
图7-9 指向同一数组的指针相减示意
如前所述,由于数组名字代表数组的起始地址即数组0单元的地址,因此数组名字本身也是一个指针。有定义如下:
int arr[10],i;
则arr+0即&arr[0],arr+1即&arr[1]…arr+i即&arr[i](0≤i≤9)。
4.通过指针引用数组元素
在引入指针后,使用数组元素可以使用三种方法:
(1)下标法。
这是我们前边学过的方法,就是使用数组名加下标的形式访问数组,这也是大家最熟悉的一种数组访问方式。
(2)地址法。
访问数组arr中的i号单元,可以使用*(arr+i)的形式,如前所述,arr+i就是arr中i号单元的地址,则利用间接内存访问的形式*(arr+i)就是arr[i]。
(3)指针法。
可以使用指针p来访问数组arr中的每一个单元,如果p=arr,则p和arr均代表数组的起始地址,此时*(p+i)等价于*(arr+i),都代表数组arr[i]。
总结如下:
如果有定义int arr[10],*p;p=arr;
则arr+i等价于p+i,都代表&arr[i];
*(arr+i)等价于*(p+i),代表arr[i]
【例7-6】分别用下标法、地址法和指针法输出数组中的全部元素。
由此可以看出,在p被arr赋值的情况下,二者是等价的。换言之,数组名就是一个指针,反之指针p也可以作为数组名,因此上例中可以使用p[i]的形式来访问数组,有兴趣的同学可以将例7-6中的arr[i]修改成p[i]进行验证。
指针变量p作为一个变量,其值是可以变化的,在使用过程中,可以用p++的形式来变量数组中的每一个单元,而数组名是一个地址常量,是不能够自加或自减的,这是使用数组名和指针访问数组的不同之处。
【例7-7】使用指针自加输出数组中的元素。
由于p的初值为arr,则*p代表arr[0],指向p++后,指针p指向arr的1号单元,依次类推,可以输出数组中的所有的元素。程序中arr+5代表arr[5]的地址,这里数组的下标为0到4,arr[5]是不存在的,因此循环条件为p<arr+5。p-arr是一个整数值,这里表示数组arr的下标,初始时p=arr,此时p-arr等于0,随着循环的执行,每一次执行p++后,p-arr取值分别为1、2、3、4,用来表示数组的下标。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。