1第七章指针上一章我们学习了如何使用数组存放多个相同类型的数据并进行运算,但数组的长度在定义时必须给定,以后不能改变。例如,数组a[10]的长度是10,程序中只能引用10个数组元素a[0]~a[9]。如果事先无法确定需要处理的数据数量,又该如何处理呢?一种方法是估计一个上限,并将该上限作为数组长度,这常常会造成空间浪费。另一种方法是利用指针实现存储空间的动态分配。指针是C语言中一个非常重要的概念,也是C语言的特色之一。使用指针可以对复杂数据进行处理,能对计算机的内存分配进行控制,在函数调用中使用指针还可以返回多个值。在本章中,除了介绍指针的基本概念外,还要解释如何使用指针作为函数的参数,以及指针用于数组和字符处理方式。内存单元地址7.1用选择法对10个整数排序Pa数组P+0,a+05a[0]1000例7.1.1对10个整数排序p+1,a+18a[1]1002#includestdio.hp+2,a+22a[2]1004main()p+3,a+36a[3]1006{inti,j,t,*p,a[10];p+4,a+41a[4]1008p=a;p+5,a+57a[5]1010printf(请随意输入10个数:\n);p+6,a+63a[6]1012for(i=0;i10;i++)p+7,a+70a[7]1014scanf(%d,p+i);p+8,a+84a[8]1016p=a;p+9,a+99a[9]1018printf(这10个数是:\n);for(i=0;i10;i++)图7.1a数组的数据在内存中的存放形式printf(%d,*(p+i));注意:一个整型数据占2个内存单元(2个字节)printf(\n);p=a;printf(从大到小的排序是:\n);for(i=0;i10;i++)for(j=i;j9;j++)if((*(p+i))(*(p+j+1))){t=*(p+i);*(p+i)=*(p+j+1);*(p+j+1)=t;}for(i=0;i10;i++)printf(%d,*(p+i));printf(\n);}运行结果:请随意输入10个数:5826173049(输入时数字之间一定有空格)这10个数是:5826173049从大到小的排序是:987654321027.1.2地址和指针在C语言中,如果定义了一个变量,在编译时就会根据该变量的类型给它分配相应的内存单元。例如,假设int型变量占2个字节,则分配2个字节的内存单元,char型变量占1个字节,float实型变量和double实型变量则分别需要4个和8个字节的内存单元。(一般一个字节8位)。计算机为了对内存单元中的数据进行操作,一般是按“地址”存取的,也就是说对内存单元进行标识编号。如果把存储器看成一个建筑物,建筑物内的房间就是存储器单元,房间号就是地址。设有如下变量定义:intx=20,y=1,z=155;因为int型变量的存储长度为2个字节,因此假设C编译器将它们分配到1000~1001、1002~1003、1004~1005的内存单元中,如图7.2(a)所示。在程序中,通过变量名进行操作,如调用函数printf(“%d”,x),输出x的值20。而程序执行时是将变量翻译为它所在的内存地址进行操作的,上述输出操作可以描述为:将x所在的内存地址1000~1001单元的内容输出。这种使用变量的方法就叫做“直接访问”。一般以变量所在的内存单元的第一个字节的地址作为它的地址,如变量x的内存地址是1000,y的地址是1002,z的地址是1004,变量x、y、z的内容分别为20、1、155。注意:一定区分内存单元的内容和内存单元的地址。地址内存单元变量地址内存单元变量100020x100020x10021y10021y1004155z1004155z200020001000p2002(a)内存单元(b)变量和指针图7.2内存单元和地址在C程序中还有一种使用变量的方法,即通过变量的地址进行操作:用指针(point)访问内存和操纵地址。地址和指针是计算机中的两个重要概念,程序代码被放置到计算机内存中执行,变量或者程序代码被存储在以字节为单位组织的存储器中。假设再定义一个变量p,它位于2000单元,该单元中存放了变量x的地址1000,见图7.2(b)所示。此时,取出变量p的值1000,就可以访问内存1000单元,实现对变量x的操作,也就是说,通过变量p,可以间接访问变量x。与直接使用变量x相比较,使用变量p访问变量x的过程实现了对变量x的间接操作,因此,在C语言中,把这种间接操作的变量叫做指针变量,简称为指针。C语言使用指针对变量的地址进行操作。指针是用来存放内存地址的变量,如果一个指针变量的值是另一个变量的地址,就称该指针变量指向那个变量。前3面提到的p就是指针变量,它存放了变量x的地址,即指针变量p指向变量x。前面的章节中,已经多次看到了把地址作为scanf()的输入参数的用法。例如,调用函数scanf(“%d”,&n),把输入的值存放到一个特定的地址所指示的n变量所在的内存单元里。如果n是一个变量,那么&n就是变量的内存地址或存储位置。这里的&称做地址运算符。&是一元运算符,与其它的一元运算符有同样的优先级和从右到左的结合性。8.1.3指针变量的定义如果在程序中声明一个变量并使用地址作为该变量的值,那么这个变量就是指针变量。定义指针变量要使用指针声明符*。例如:inti,*p;声明变量i是int型,*p是指向int型变量的指针。任何指针值的合法范围包括特殊的地址0和一组正整数,在具体C系统中会把它们解释为机器地址。指针声明符*在定义指针变量时被使用,说明被定义的那个变量是指针。定义指针变量的一般形式为:类型名*指针变量名;类型名指定指针变量所指向变量的类型,必须是有效的数据类型,如int、float、char等。指针变量名是指针变量的名称,必须是一个合法的标识符。在许多场合,可以把指针变量简称为指针,但实际上指针和指针变量在含义上存在一定的差异。一般来说,在C语言中,指针被认为是一个概念,是计算机内存地址的代名词之一,而指针变量本身就是变量,和一般变量不同的是它存放的是地址。大多数情况下,并不特别强调它们的区别,这里如果未加声明,把指针和指针变量同等对待,都是指存放内存地址的指针变量。指针变量用于存放变量的地址,由于不同类型的变量在内存中占用不同大小的存储单元,所以只知道内存地址,还不能确定该地址上的对象。因此在定义指针变量时,除了指针变量名,还需要说明该指针变量所指向的内存空间上所存放数据的类型。下面是一些指针定义的例子:int*p;//定义一个指针变量p,指向整型变量char*cp;//定义一个指针变量cp,指向字符型变量float*fp;//定义一个指针变量fp,指向实型变量double*dp1,*dp2;//定义2个指针变量dp1和dp2,指向双精度实型变量注意:定义多个指针变量时,每一个指针变量前面都必须加上*。指针被定义后,必须将指针和一个特定的变量进行关联后,才可以使用指针,也就是说,指针变量也要先赋值再使用,当然指针变量被赋的值应该是地址。假设有定义:inti,*p;下面的语句可以对指针变量p赋值:p=&i;p=0;p=NULL;P=(int*)1732;第1条语句中的指针p被看做是指向变量i或包含变量i的地址,也就是将指针p和变量i关联起来,这也是指针最常用的赋值方法,图7.3给出了指针变量p和整型变量i之间的关联。第2条和第3条语句说明了怎样把特殊值0赋值给变量p,这时指针的值为4NULL。常量NULL在系统文件stdio.h中定义,其值为0,将它赋给指针时,代表空指针。C语言中的空指针不指向任何单元。在最后一条语句中,使用强制类型转换(int*)来避免编译错误,表示指向int型变量的指针。不提倡使用这类语句,因为一般不将绝对地址赋给指针,但特殊值NULL例外。Pi变量i的地址图7.3指针p指向变量i的示意图在定义指针变量时,要注意以下几点:(1)指针变量名是一个标识符,要按照C标识符的命名规则对指针变量进行命名(2)指针变量的数据类型是它所指向的变量的类型,一般情况下一旦指针变量的类型被确定后,它只能指向同一个类型的变量。(3)在定义指针变量时需要使用指针声明符*,但指针声明符并不是指针的组成部分。在对指针变量命名时(除整型变量外),建议用其类型名的首字母作为指针名的首字符,用p或ptr作为名字,以使程序具有较好的可读性。如将单精度浮点型指针命名为fp、fptr等。7.1.4指针的基本运算如果指针的值是某个变量的地址,通过指针就能间接访问那个变量,这些操作由取地址运算符&和间接访问运算符*完成。此外,相同类型的指针还能进行赋值、比较和算术运算。1、取地址运算和间接访问运算单目运算符&用于给出变量的地址。例如:int*p,a=3;p=&a;将整型变量a的地址赋给整型指针p,使指针p指向变量a。也就是说,用运算符&取变量a的地址,并将这个地址值作为指针p的值,使指针p指向变量a。注意:指针的类型和它所指向变量的类型必须相同。在程序中(不是指针变量被定义的时候),单目运算符*用于访问指针所指向的变量,它也称为间接访问运算符。例如,当p指向a时,*p和a访问同一个存储单元,*p的值就是a的值,如图7.4所示。Pa&a3*p图7.4指针运算示意图指针和地址的概念比较抽象,理解上也比较困难。下面通过一些实例进一步解释取地址运算和间接访问运算的使用,以更好地理解指针和地址的含义。例7.2指针取地址运算和间接访问运算5//取地址运算和使用指针访问变量#includestdio.hintmain(){inta=3,*p;//此行是程序的第4行:定义整型变量a和整型指针pp=&a;//把变量a的地址赋给指针p,即p指向aprintf(a=%d,*p=%d\n,a,*p);//输出变量a的值和指针p所指向变量的值*p=10;printf(a=%d,*p=%d\n,a,*p);printf(Entera:);scanf(%d,&a);//输入aprintf(a=%d,*p=%d\n,a,*p);(*p)++;//将指针所指向的变量加1printf(a=%d,*p=%d\n,a,*p);return0;}运行结果:a=3,*p=3a=10,*p=10Entera:5a=5,*p=5a=6,*p=6第4行的inta=3,*p和其后出现的*p,尽管形式是相同的,但两者的含义完全不同。第4行定义了指针变量,p是变量名,*表示其后的变量是指针;而后面出现的*p代表指针p所指向的变量。本例中,由于p指向变量a,因此,*p和a的值一样。再如表达式*p=*p+1、++*p和(*p)++,分别将指针p所指向变量的值加1。而表达式*p++等价于*(p++),先取*p的值作为表达式的值,再将指针p的值加1,运算后,p不再指向变量a。同样,在下面这几条语句中:inta=1,x,*p;p=&a;x=*p++;指针p先指向a,其后的语句x=*p++,将p所指向的变量a的值赋给变量x,然后修改指针的值,使得指针p不再指向变量a。从以上例子可以看出,要正确理解指针操作的意义,带有间接地址访问符*的变量的操作在不同的情况下会有完全不同的含义,这既是C的灵活之处,也是初学者最容易出错的地方。下面的例子,是理解如何使用指针改变变量的值。例7.3通过指针操作改变所指向的变量的值#includestdio.hintmain(){inta=1,b=2,t;int*p1,*p2;p1=&a;p2=&b;//关联指针和变量,使p1指向a,p2指向bprintf(a=%d,b=%d,*p1=%d,*p2=%d\n,a,b,*p1,*p2);6t=*p1;*p1=*p2;*p2=t;//交换*p1和p2的值printf(a=%d,b=%d,*p1=%d,*p2=%d\n,a,b,*p1,*p2);return0;}运行结果:a=1,b=2,*p1=1,*p2=2a=2,b=1,*p1=