第七章指针指针是C语言的精华所在,是C语言中广泛使用的一种数据类型。运用指针编程是C语言最主要的风格之一,如果你不能用指针编写有效、正确和灵活的程序,可以认为你没有学好C语言。利用指针变量可以表示各种数据结构;能很方便地使用数组和字符串;并能象汇编语言一样处理内存地址,从而编出精练而高效的程序,极大地丰富了C语言的功能。同样指针对于成功地进行C++程序设计也是非常重要的。指针功能最强,同时也是最危险的,所以一定要从掌握指针的原理、机制入手,勤加练习,学会在各种情况下正确地使用指针变量,用指针编写出有效、正确和灵活的程序。7.1指针的概念7.1.1内存地址和变量地址内存地址是指内存中存储单元的编号,计算机硬件系统的内存储器中,拥有大量的存储单元(容量为1字节)。为了方便管理,必须为每一个存储单元编号,这个编号就是存储单元的“地址”。每个存储单元都有一个惟一的地址,在地址所标识的存储单元中存放数据。注意:内存单元的地址与内存单元中的数据是两个完全不同的概念。变量地址是系统分配给变量的内存单元的起始地址。假设有这样一个程序:main(){intnum;scanf(%d,&num);printf(num=%d\n,num);}C编译程序编译到该变量定义语句时,将变量num登录到“符号表”中。符号表的关键属性有两个:一是“标识符名(id)”,二是该标识符在内存空间中的“地址(addr)”。为描述方便,假设系统分配给变量num的2字节存储单元为3000和3001,则起始地址3000就是变量num在内存中的地址。系统对变量的值的存取是通过变量在内存中的地址进行的,如系统执行“scanf(”%d“,&num);”和“printf(”num=%d\n“,num);”时,存取变量num值的方式可以有两种:(1)直接访问──直接利用变量的地址进行存取1)上例中scanf(“%d”,&num)的执行过程是这样的:用变量名num作为索引值,检索符号表,找到变量num的起始地址3000;然后将键盘输入的值(假设为3)送到内存单元3000和3001中。此时,变量num在内存中的地址和值,如图9-1所示。2)printf(num=%d\n,num)的执行过程,与scanf()很相似:首先找到变量num的起始地址3000,然后从3000和3001中取出其值,最后将它输出。(2)间接访问──通过另一变量访问该变量的值C语言规定:在程序中可以定义一种特殊的变量(称为指针变量),用来存放其它变量的地址。例如,假设定义了这样一个指针变量num_pointer,它被分配到4000、4001单元,其值可通过赋值语句“num_pointer=#”得到。此时,指针变量num_pointer的值就是变量num在内存中的起始地址3000。通过指针变量num_pointer存取变量num值的过程如下:首先找到指针变量num_pointer的地址(4000),取出其值3000(正好是变量num的起始地址);然后从3000、3001中取出变量num的值。(3)两种访问方式的比较两种访问方式之间的关系,可以用某人甲(系统)要找某人乙(变量)来类比。一种情况是,甲知道乙在何处,直接去找就是(即直接访问)。另一种情况是,甲不知道乙在哪,但丙(指针变量)知道,此时甲可以这么做:先找丙,从丙处获得乙的去向,然后再找乙(即间接访问)。7.1.2指针变量的定义变量的指针就是变量的地址,我们可以定义一个指向一个变量的指针变量,也就是说指针变量就是存储其他变量地址的变量。指针与指针变量的区别就是变量值与变量的区别。我们学过基本的数据类型,如int,float,char,double等,其中每一种基本数据类型都有相应的指针变量。在C++中与C语言不同的是,C++有const数据类型,因为常量是有地址的,所以我们在C++中可以定义const指针变量。7.1.2.1指针变量的定义C语言中所有的指针变量在使用之前都必须定义,规定起类型。指针变量不同与整形变量和其他类型的变量,它是用来专门存放地址的,必须将它定义为“指针类型”。指针变量的一般定义为:类型标识符*标识符;其中标识符是指针变量的名字,标识符前加了*号,表示该变量是指针变量,而最前面的类型标识符表示该指针变量所指向的变量的类型。一个指针变量只能指向同一种类型的变量,也就是讲,我们不能定义一个指针变量,既能指向一个整型变量又能指向双精度变量。下面都是合法的定义:float*pointer_1;(指向实型变量的指针变量)const*pointer_2;(指向常量的指针变量)在定义指针变量时要注意标识符前面的“*”不能漏掉,它表示该变量为指针变量。但指针变量名并不是*pointer_1,*pointer_2,而是pointer_1,pointer_2。指针变量在定义中允许带初始化项。如:inti,*ip=&i;注意,这里是用&i对ip初始化,而不是对*ip初始化。和一般变量一样,对于外部或静态指针变量在定义中若不带初始化项,指针变量被初始化为NULL,它的值为0。TurboC中规定,当指针值为零时,指针不指向任何有效数据,有时也称指针为空指针。7.1.2.2指针变量的运用1相关运算符有两个相关的运算符:1)&:取地址运算符。2)*:指针运算符(或称“间接访问”运算符)例如:&test为变量test的地址,*ptr为指针变量ptr所指向的变量。2指针变量赋值指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。C语言中用我们在前面介绍的地址运算符&来表示变量的地址。其一般形式为:&变量名;如&a变示变量a的地址,&b表示变量b的地址。变量本身必须预先说明。设有指向整型变量的指针变量p,如要把整型变量a的地址赋予p可以有以下两种方式:(1)指针变量初始化的方法inta;int*p=&a;(2)赋值语句的方法inta;int*p;p=&a;不允许把一个数赋予指针变量,故下面的赋值是错误的:int*p;p=1000;被赋值的指针变量前不能再加“*”说明符,如写为*p=&a也是错误的。3间接引用指针“*”是乘法,又可以用于定义指针,现在在这里用于指针的简介引用。间接引用指针时可获得该指针指向变量的内容。当*放在可执行语句中的指针之前,称为间接用操作符,*放在指针定义中时,称为指针定义符。例如,下面的程序间接引用指针str,输出sum的内容:#includeiostreanm.hvoidmain(){int*str;intsum=110;str=∑cout*strendl;}运行结果为:110该运行结果就是指针str所指向的变量sum中的内容。间接引用的指针可用于运算符的左边,也可用于运算符的右边,例如,上例中,我们可通过str改变变量sum的原始值:*str=340;countsumendl;改变*str就是改变sum。所以其输出结果为340。4指针运算指针允许的运算方式有:(1).指针在一定条件下,可进行比较,这里所说的一定条件,是指两个指针指向同一个对象才有意义,例如两个指针变量p,q指向同一数组,则,,=,=,==等关系运算符都能正常进行。若p==q为真,则表示p,q指向数组的同一元素;若pq为真,则表示p所指向的数组元素在q所指向的数组元素之前(对于指向数组元素的指针在下面将作详细讨论)。(2).指针和整数可进行加、减运算。设p是指向某一数组元素的指针,开始时指向数组的第0号元素,设n为一整数,则p+n就表示指向数组的第n号元素(下标为n的元素)。不论指针变量指向何种数据类型,指针和整数进行加、减运算时,编译程序总根据所指对象的数据长度对n放大,在一般微机上,char放大因子为1,int、short放大因子为2,long和float放大因子为4,double放大因子为8。当然++,――自加、自减运算符也同样适应于指针。(3).两个指针变量在一定条件下,可进行减法运算。设p,q指向同一数组,则p-q的绝对值表示p所指对象与q所指对象之间的元素个数。其相减的结果遵守对象类型的字节长度进行缩小的规则。7.2数组的指针和指向数组的指针变量7.2.1概述1.概念数组的指针就是数组在内存中的起始地址,数组元素的指针就是数组元素在内存中的起始地址。数组中第一个元素的内存地址等于数组的内存地址。2.指向数组的指针变量的定义与赋值指向数组的指针变量的定义,与指向普通变量的指针变量的定义方法一样。例如,intarray[10],*pi=array(或&array[0]);或者:intarray[10],*pi;pi=array;注意:数组名代表数组在内存中的起始地址(与第1个元素的地址相同),所以可以用数组名给指针变量赋值。但是数组名并不代表整个数组,上面的“pi=array”的作用是把array数组的首地址赋给指针变量pi,而不是把数组array各元素赋给pi。数组名是指针常量,在程序中不能给数组名赋值,对编译器来说,数组名表示内存中分配了数组的固定位置,修改了这个数组名,就会丢失数组空间,所以数组名所代表的地址不能被修改。7.2.2通过指针引用数组元素如果有“intary[10],*p=ary;”,则:(1)p+i和ary+i都是数组元素ary[i]的地址,如图7-1所示。(2)*(p+i)和*(ary+i)就是数组元素ary[i]。(3)指向数组的指针变量,也可将其看作是数组名,因而可按下标法来使用。例如,p[i]等价于*(p+i)。注意:p+1指向数组的下一个元素,而不是简单地使指针变量p的值+1。其实际变化为pointer+1*size(size为一个元素占用的字节数)。例如,假设指针变量p的当前值为3000,则p+1为3000+1*2=3002,而不是3001。Ary[0]Ary[1]Ary[2]Ary[i]Ary[9]Ary数组PP+1ary+1ary+iP+iary+9P+9图7-1*(ary+i)根据以上的叙述,引用一个数组元素,可以用下标法,如ary[i]形式;也可用指针法,如*(ary+i)或*(p+i)。其中ary是数组名,p是指向数组的指针变量,其初值p=ary。[例7.1]使用指向数组的指针变量来引用数组元素。/*程序功能:使用指向数组的指针变量来引用数组元素*/1.下标法main(){intary[10],*p=ary,i;printf(“Input10numbers:”);for(i=0;i10;i++)scanf(“%d”,&ary[i]);/*使用指针变量来输入数组元素的值*/printf(“ary[10]:”);for(i=0;i10;i++)printf(“%d”,a[i]);/*使用指向数组的指针变量输出数组*/printf(“\n”);}2通过计算各个数组元素地址,找出元素的值。main(){intary[10],*p=ary,i;printf(“Input10numbers:”);for(i=0;i10;i++)scanf(“%d”,p+i);/*使用指针变量来输入数组元素的值*/printf(“ary[10]:”);for(i=0;i10;i++)printf(“%d”,*(p+i));/*使用指向数组的指针变量输出数组*/printf(“\n”);}3用指针变量指向数组元素main(){intary[10],*p=ary,i;printf(“Input10numbers:”);for(i=0;i10;i++)scanf(“%d”,p+i);/*使用指针变量来输入数组元素的值*/printf(“ary[10]:”);for(i=0;i10;p++)printf(“%d”,*p);/*使用指向数组的指针变量输出数组*/printf(“\