1、地址关联着两个存储空间,以图1的地址0x12ff7c为例:一个是地址本身作为常量存储的空间;另一个是通过该地址间接引用的数据空间,即我们形象地用箭头指向的整型变量n的空间。根据这个处理特点,把地址本身的类型称为指针类型或指针型,指针指向的数据的类型称为指针的数据类型或指针型的基类型或指针的基类型。因此,指针是一个复合类型。图1 变量与地址 当我们说一个指针是某种类型或某种类型的指针,指的是该指针的基类型,或该指针指向的数据的类型。例如一个指针是整型的,或一个整型指针,是指该指针的基类型是整型的,或指针指向的数据是整型的。指针是一个复合类型,因此它的标识符也是复合的,由表示指针类型的符号*和表示
2、指针基类型的符号联合组成。例如,整型指针用int*表示,双浮点型指针用double*表示。*代表指针类型,int或double表示指针的基类型。例如图1的地址0x12ff78,地址的基类型为整型,被引入C语言之后,用字面值常量来表示便是(int*)0x12ff78,我们称之为指针型字面值常量或指针字面值常量。1.2指针的基本操作 现在我们需要把地址的间接引用操作引入C语言作为指针型的基本操作:在指针前加间接引用运算符,也称解引用运算符*,所得表达式作为该指针指向的空间的标识符,这种由指针运算构成的数据空间表达式称为间接引用表达式或解引用表达式。图1的表代表了下面的等价式:x=*(double*
3、)0x12ff70 m=*(int*)0x12ff78 n=*(int*)0x12ff7c 我们可以做个实验来证实这个等价式。首先通过输出语句确定变量的地址:printf(%x,%x,%xn,&n,&m,&x); /输出变量n、m和x的地址 假设输出结果是12ff7c,12ff78,12ff70 (如果不是,那么以输出的结果为准)。然后通过指针常量间接修改变量n、m和x的值:*(int*)0x12ff7c=5; /相当于n=5;*(int*)0x12ff78=*(int*)0x12ff7c; /相当于m=n;*(double*)0x12ff70=3.1415; /相当于x=3.1415;n=%
4、d,m=%d,x=%fn,n,m,x); /输出变量n、m和x的值,检验结果 在我们的设计方案中,由一个数据空间地址计算出其余的数据空间地址是主要的操作,现在我们将它设计为指针类型的基本操作:指针加减一个整数。假设一组双浮点型数5.5、6.6、7.7、8.8、9.9,它们的存储空间按地址从小到大依次相邻,地址依次为0x120000、0x120008、0x120010、0x120018、0x120020,用指针字面值常量来表示便是(int*)0x120000、(int*)0x120008、(int*)0x120010、(int*)0x120018、(int*)0x120020。如果用指针加减一个
5、整数的基本运算来表示,那么指向5.5的存储空间的指针(int*)0x120000加1应该是指向6.6的存储空间的指针(int*)0x120008,加2应该是指向7.7的存储空间的指针(int*)0x120010,依次类推(见图2)。反过来,指向9.9的存储空间的指针(int*)0x120020减1应该是指向8.8的存储空间的指针(int*)0x120018,减2应该是指向7.7的存储空间的指针(int*)0x120010,依次类推。概括地说,一个指针加上(或减去)一个整数所得到的指针,其地址会增加(或减少)指针基类型大小(字节数)的该整数倍,即指针的算术运算单位是该指针基类型的大小,或者说是指
6、针指向的数据类型的大小。 图2 指针算术运算 对指针做加减整数的运算是没有限制的,但是对指针间接引用的空间是有限制的,它必须是系统分配的、可以由指针间接引用的空间,否则就可能破坏系统空间的数据。这个限制使我们不能随便使用一个指针字面值常量进行间接引用运算。例如下面的语句:*(int*)0x12ff7c=6;只有在0x12ff7c确是一个已经定义的整型数据空间地址的时候才是合理的。取址运算&实质上是指针的基本操作。例如,有下面的等价式:对上面的等价式左右实施取址运算&,有等价式:&x=&*(double*)0x12ff70=(double*)0x12ff70 对上面的等价式左右实施间接引用运算*
7、,有等价式:*&x=*(double*)0x12ff70=x 上面的等式说明取址运算&和间接引用运算*互为逆运算。指针的值是无符号整型数,可以实施逻辑运算和关系运算。综上所述,间接引用*、取址&、加减一个整数、两个指针相减、关系运算和逻辑运算是指针的基本操作。两个指针能够相加或相乘吗?不行,因为它无意义。一种类型应该包含哪些基本操作,这取决于我们设计这种数据类型的目的或用途。2数组类型 我们的设计是,一组类型相同、逻辑上有前后关系的数据,存储在物理上前后相邻的变量空间,使得通过一个数据空间的地址可以计算出其余的数据空间地址,然后通过空间地址(而不是空间名称)去间接访问数据(空间)。前面介绍过把
8、地址作为指针引入C语言,现在我们把物理上前后相邻的一组变量作为一个整体引入C语言。我们把这个整体称为数组类型变量,简称数组,把每一个作为成员的变量称为数组元素,数组元素空间按地址从小到大依次相邻。数组元素的个数称为数组长度或容量。根据我们的设计,对数组元素的引用是间接的,这需要知道指向第一个数组元素的指针。而编译器对空间的分配是随条件变化的,指向第一个数组元素的指针不能是字面值常量,只能是符号指针常量,即用符号表示的指针常量,这个符号由数组名兼任,称为(一维)数组指针,数组元素类型就是数组指针的基类型。数组名既是数组变量的标识符,又是指向第一个数组元素的指针常量,具体代表什么要根据上下文来确定
9、。定义一个数组需要指出数组名、数组长度和数组元素类型(数组指针基类型)。数组的定义格式为:类型标识符数组名整型常量表达式;其中类型标识符表示数组元素类型,整型常量表达式表示数组长度即容量。数组类型没有独立的标识符,因为数组实际上是一个复合类型,数组的存储格式、存储空间大小和基本操作由其数组元素类型和数组长度决定,所以数组类型由数组元素类型和数组长度联合表示。当我们说某种类型的数组或数组是某种类型时,是指该数组的数组元素是某种类型的。例如,定义一个长度为5、数组名是d、数组元素类型为双浮点型的数组,即长度为5、数组名是d的双浮点型数组:double d5;/长度为5,数组名是d的双浮点型数组 数
10、组名d既表示数组(变量),又表示指向第一个数组元素的指针常量,具体表示什么呢?若实施sizeof运算,则d代表数组,结果是:sizeof(d)=5*sizeof(double)=5*8=40 /5个数组元素空间大小之和 还可以由表达式sizeof(d)/sizeof(do- uble)计算出整型数组d的容量即长度。若实施算术运算和间接引用运算,则d代表数组指针。根据指针的性质,d,d+1,d+2,d+3,d+4依次是指向第1至第5个数组元素的指针,实施间接引用运算之后,*d,*(d+1),*(d+2),*(d+3),*(d+4)依次是第1至第5个数组元素的间接引用表达式。不过,一般采用符合数学
11、习惯的下标表达式或索引表达式:d0,d1,d2,d3,d4,其中“”是下标运算符或索引运算符,其中的整数称为数组元素下标或索引。数组元素的间接运算表达式和下标表达式是一回事(见图3)。图3 数组的综合特征 长度为n的数组,下标为0n-1,超过下标范围的索引访问是非法的,不管编译器对超越下标范围的访问如何处理,我们都要自觉地遵守这个实际上属于我们自己的设计。例如,对上面定义的数组d,下标表达式d5和d-2是非法的。这不是一种规定,而是数组和数组指针作为一个整体的表现:数组元素没有名称,它要由数组指针间接引用,它的索引访问或下标访问的实质就是数组指针的间接引用运算(例如d2=*(d+2),这是数组
12、对数组指针的依赖;而数组指针是其间接引用范围由数组范围决定的指针。一个变量可以看作是长度为1的数组。例如:int n=5;n等价于长度为1的整型数组指针,因此(&n)0与n等价:(&n)0=*(&n)=*&n=n 数组是一个复合类型(见图3),包含数组类型、数组指针类型和数组元素类型。数组的类型由数组元素的类型和数组长度复合表示,例如长度为5的双浮点型数组的类型是double5。数组指针的基类型是数组元素类型,数组指针的间接引用运算只有在该数组范围内实施才有意义,这是指针对数组的依赖,而数组的元素没有名称,对数组元素的引用依赖指针,这是数组对指针的依赖。数组指针和数组共用一个名称隐含了这种整体
13、关系。d作为数组指针和&d的值相同,但它们不是同一类型的指针,d等价于&d0,其基类型等价于数组元素d0的类型double,所以数组指针d的类型double*,算术运算单位是8,而&d的基类型是数组d的类型doulbe5,所以&d的类型是doulbe(*)5,运算单位是40。3 指针变量 数组元素没有名称,只能依赖数组指针的间接引用。数组指针是打开数组元素空间的“钥匙”。如果把数组指针传给同类型的指针变量,那么该指针变量就可以和数组指针“共享”一个数组空间,我们称此为地址传递。一个指针,不论什么类型,在32位机上都只占4个字节,所以复制数组指针比复制数组中的数据更有效率。指针变量的定义格式为:
14、类型标识符* 变量名;以图4为例,把数组指针a赋给指针变量p,就有了等价式:pi=ai 但是,指针变量p与数组指针a不等价,因为前者是指针变量,后者是指针常量。指针变量p与数组a也不等价,下面的等式可以说明:sizeof(p)=4 /指针变量空间大小 sizeof(a)=20 /数组空间大小 而且指针&p与&a值不同,基类型也不同,&p是p空间地址,是指向指针空间p的指针,类型是int*,而&a是数组空间地址,是指向数组空间的指针,类型是int(*)5。因为数组名称既表示数组指针又表示数组,所以要从数组名称中取出数组指针赋给指针变量,就需要隐式类型转换,通常称之为decay。图4 指针变量 如
15、果传递数组指针的目的是传递数组的值,那么应该把数组指针传递给同类型指针变量。例如,整型数组指针应该传给整型指针变量,双浮点型数组指针应该传给双浮点型指针变量。下面我们以反例来说明这样做的意义。int a5=10,15,20,25,30;/整型数组 double* pd;/双浮点型指针变量 pd=(double*)a;/整型数组指针经强制转换之后赋给双浮点型指针变量 在32位机上,双浮点型指针pd的算术运算单位是8个字节,而整型指针常量a的运算单位是4个字节,因此,pd+i的值和a+i的值不等,从而pdi和ai也不等。又例:int a5= 10,15,20,25,30;float* pf;/单浮
16、点型指针变量 pf=(float*)a;/整型数组指针经强制转换后赋给单浮点型指针变量 在32位机上,单浮点型指针pf的算术运算单位是4个字节,整型指针常量a的运算单位也是4个字节,因此,pf+i的值和a+i的值相等,但是编译器对它们所指向的数据类型解释不同:pfi是单浮点型,ai是整型。具体地说,a0以普通整型格式存储着10,而pf0按单浮点型格式“解读”时,其中存储的不是10;而且对a0可以实施求余运算,对pf0不能实施该运算。如果传递数组指针的目的是“让出”数据处理之后而“空闲下来”的数组空间,那么可以把该数组指针传递给不同类型的指针变量,但是需要强制类型转换。以图5为例,把长度为2的双
17、符点型数组指针d传递给整型指针变量pi,通过pi可以访问长度为4的整型数组空间(按类型大小计算,长度为2的双浮点型数组空间可以用做长度为4的整型数组空间)。双浮点型数组指针d和整型指针变量pi都指向同一个空间,我们用哪一种类型的指针去间接引用,这个空间就是哪一种类型的数组空间,而且空间中的数据是最后存储的数据。例如,在语句int *pi=(int*)d执行之后,pi0=17执行之前,虽然指针d和pi都指向一个空间,但是d0和d1中的双浮点型数2.125和3.625还存在,执行了语句pi0=17和pi1=29以后,d0中的双浮点型数2.125已经不存在,改为两个整型数17和29,但是d1的值3.
18、625还存在。图5 由不同类型数据共享的数组空间 一个变量可以看作是一个长度为1的数组,因此指向变量的指针可以看作是指向长度为1的数组的指针(见图6)。pn0=*pn =(&n)0 =n 图6 指向变量的指针 4二维数组类型 一个序列存储在一维数组中,其元素在逻辑上的前后位置由数组元素的下标来表示。可是如何存储一个矩阵呢?矩阵的元素在逻辑上有两个位置,行的前后位置和列的前后位置。为此需要引入二维数组。二维数组是数组元素为一维数组(称行一维数组)的一维数组。因为一维数组的元素类型是相同的,所以一个二维数组的行一维数组的类型相同,长度相同,元素的类型也相同。行一维数组的个数称为二维数组的行数,行一
19、维数组的长度称为二维数组的列数。行一维数组也称为二维数组行元素,行一维数组元素也称二维数组元素。二维数组定义格式为:类型标识符数组名行数列数;例如,定义一个4行3列的二维整型数组:int a43;二维数组作为特殊的一维数组,数组长度为4,其数组元素为:a0,a1,a2,a3 它们分别是长度为3的行一维整型数组,类型都是int3。行一维数组的元素的类型是整型,而且依次为:a00a01 a02 /行一维数组a0的数组元素 a10 a11 a12 /行一维数组a1的数组元素 a20 a21 a22 /行一维数组a2的数组元素 a30 a31 a32 /行一维数组a3的数组元素 与一维数组类似,作为特
20、殊的一维数组,数组名a既表示数组(用斜体a表示以示区别),又表示数组指针。当a表示数组时,它的类型是由元素类型即行一维数组类型int3和数组长度即行数也即行一维数组个数4联合构成,int43。而且 sizeof(a)=sizeof(ai) =4*12=48 当a表述数组指针时,有等式:a=&a0 a0作为行一维数组表达式,既表示行一维数组类型(用斜体a0表示以示区别),又表示行一维数组指针。在上面等式的取址运算&a0中,a0表示行一维数组,因此指针a的基类型是行一维数组类型int3,算术运算单位是12,即一个行一维数组大小,有等式 a+i=&ai(i=03) 因此被称为二维数组指针,类型用in
21、t (*)3表示。二维数组a的其他综合特性见表1和图7 图7二维整型数组a43(阴影部分表示指针常量) 一个长度为n的一维数组可以看作是1行n列二维数组。double d5=1,2,3,4,5;d相当于1行5列二维双浮点型数组指针(见图8)。反之,一个m行n列二维数组可以看作长度为m*n的一维数组。图8长度为5的一维数组d可以看作1行5列二维数组 5二维指针变量 与二维数组指针常量对应的是二维指针变量,定义格式为:类型指示符 (*指针变量名)列数;举例说明:int (*p)3;/二维指针变量p int a43=1,2,3,4,5,6,7,8,9,10, 11,12;/二维整型数组 p=a;/二
22、维数组指针传递 这时的二维指针变量p等价于二维数组指针a:p+i=a+i (i=0,1,2,3) pi=ai (i=0,1,2,3) pij=aij (i=0,1,2,3,j=0,1,2) 但是不等价于二维数组a:下面的等式可以说明:sizeof(p)=4 /p表示指针变量 sizeof(a)=48 /a表示二维数组 图9 二维指针变量和二维数组 6二维数组和(一维)指针 一个m行n列二维数组,可以看作是长度为m*n的一维数组。int a43=1,2,3,4,5,6,7,8,9, 10,11,12;a0是指向行一维数组元素a00的指针常量,它等价于长度为12的一维整型数组指针,该一维数组的12
23、个元素依次为:a00 a01 a02 a03 a04 a05 a06 a07 a08 a09 a010 a011 二维数组指针a的行下标(假设用i表示)增加1,地址值增加12,一维数组指针a0的下标增加1,地址值4,于是有等式:a0i*3+j=aij 于是,可以把二维数组的行一维数组的地址传给(一维)指针变量:int*p=a0; /int *p=*a; 或int *p=&a00;于是有等式(见图10):pi*3+j=a0i*3+j=aij 图10 二维数组和(一维)指针变量 7指针数组和指针的指针 所谓一维指针数组,就是数组元素为(一维)指针变量的一维数组。char* a5;a是长度为5的字符
24、型指针数组,数组元素a0、a1、a2、a3、a4都是字符型指针变量,它们各自可以指向一维字符数组,特别是字符串:a0=File; a1=Edita2=Compile a3=Runa4=Tools指针数组可以初始化:char* a5=, ;或 char* a=数组指针a类型应该由“*”和其基类型即数组元素char*联合构成,所以是char*,被称为指针的指针常量:char* pa; /指针的指针变量 pa=a; /接受字符型指针数组的指针a的值(见图11) char* pa=a;图11 字符指针数组和字符指针的指针 二维数组是数组元素为指针常量的指针数组,反之,指针数组是行一维数组是指针变量的二
25、维数组。它们在下面的情形下统一。假设 /二维数组 int* p4; /指针数组 令指针数组的指针元素分别指向二维数组的行一维数组 p0=a0;p1=a1;p2=a2;p3=a3;则有等式:pij=aij 8小结 机器语言发展到C语言,作为机器语言要素的地址就要发展为C语言的要素,这就是指针类型。指针是C的类型,就应该和整型、字符型等语言内置类型一样,具有自己的常量,因此,指针字面值常量的引入是不可避免的。而处理的需要又使指针一出现,就和数组构成一个整体,它们都以对方的存在作为自己存在的前提:指针的基类型是它可以指向的数组的元素类型,指针间接引用范围取决于它实际指向的数组大小;一个指针常量就是一个数组指针;一个指针变量,当它指向一个数组时,与该数组指针等价。这是指针对数组的依赖。反之,数组的元素没有名称,它的表示依赖指针;数组指针是指向该数组的指针常量。这是数组对指针的依赖。一个变量等价于一个长度为1的数组,指向一个变量的指针看作是指向一个长度为1的数组的指针,这实际上依然是指针和数组的相互依赖、相互作用的整体性关系。但是这还不能证明指针和数组定义是科学的,还需要通过二维指针和二维数组的定义来检验。例如,二维数组必须是一维数组的推广,就像二重积分是一重积分的推广一样,而且它们在一定条件下可以互相转化,例如,长度为n的一维数组等价于1行n列的二维数组,
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1