1、1 1第9章 指 针9.1 指针和地址指针和地址9.2 指针变量和指针变量的赋值指针变量和指针变量的赋值9.3 指针和函数参数指针和函数参数9.4 指针和数组指针和数组9.5 地址运算地址运算9.6 字符指针和有关函数字符指针和有关函数9.7 指针数组和指向数组的指针指针数组和指向数组的指针9.8 指针与函数指针与函数2 2指针是C语言中非常重要的一种数据类型,在C语言程序设计中被广泛使用。一方面,有时候某些计算必须用指针来表示;另一方面,同一个计算用指针来解决会使编写的程序更高效、更紧凑。指针是C语言学习过程中比较难掌握的内容,尤其对缺乏计算机硬件知识的读者更是如此;指针与数组紧密相关。本章
2、对指针的概念、指针作为函数的参数传递、指针和数组的关系、地址运算、指针和字符串的关系、指针数组和指向数组的指针、指向函数的指针等问题进行讲述。3 3一台计算机有一组连续的、编了号的、可以寻址的内存单元,可以独自对这些内存单元进行操作,或者以连续集合的方式对这些内存单元操作。一种普遍的情形是:char类型的数据占用一个字节单元,对这种类型的数据操作,就是对一个字节单元的操作;9.1 指指针针和和地地址址4 4short int类型的数据占用两个字节单元,对这种类型的数据操作,就是对两个字节单元的操作;而long 类型的数据占用四个相邻字节单元,对这种类型的数据操作,就是对四个字节单元的操作。一个
3、指针是指一组能够保存某种类型变量的地址的存储单元,指针通常占用两个字节或四个字节的内存单元(与系统有关)。5 5图9-1 内存的组织形式6 6假设c是一个char类型的变量,占用一个字节单元,p是一个指针,占用4个存储单元(指针占用多少个存储单元与系统有关,系统不同,会有差别)。根据指针的定义,我们可以用指针p保存字符型变量c的地址,假设变量c对应的内存单元地址是y4y3y2y1H,是4个字节,如果把变量c的地址保存到指针p,我们就说p指向变量c,或者说p是指向变量c的一个指针,如图9-1所示。7 7单目运算符“&”可以求出一个对象的地址,称为求地址运算符。例如:p=&c;该语句的作用是将变量
4、c的地址赋值给指针变量p,p现在就指向变量c。“&”运算符只能用于内存中的对象变量或数组元素,不能用于表达式或者寄存器变量。单目运算符“*”是一个间接访问运算符(也称为指针运算符)。当“*”用于指针上时,表示访问该指针指向的对象。例如:8 8int x=1,y=2;int*ip;/*ip是指向int型变量的指针。*/ip=&x;/*ip现在指向x。*/y=*ip;/*将指针ip指向的变量x的值赋给y,即y现在的值是1。*/*ip=0;/*将值0赋给指针ip指向的变量x,即x现在的值是0。*/注意:“*”运算符出现在指针的定义中和出现在表达式中的语法相似,但含义不同。9 9应当注意,指针是受限于
5、其指向某种对象类型的,即每个指针与指向的某种具体数据类型要一致。不过有一点要注意,void类型的指针比较特殊,void类型的指针赋值将在9.5节中介绍。如果ip是指向整数x的指针,那么*ip可以出现在x能出现的任何位置,也即x=x+10;可以用*ip=*ip+10;来代替。单目运算符“*”和“&”比算术运算符的优先级要高,这样对于下列赋值运算:y=*ip+1;就是将ip指向变量的值加1,再将结果赋给y。1010而语句“*ip+=1;”是将ip指向的变量值加1,然后再赋给那个变量;“+*ip;”语句也可完成同样的运算;“(*ip)+;”语句也可以完成,但注意“()”是必需的,否则,结果将是对ip
6、指针加1后的间接引用,这是因为单目运算符“*”和“+”优先级相同,结合时是自右向左结合的。指针是变量,它们也可以不通过间接引用来使用。例如:iq是另一个指向int型变量的指针,则执行语句“iq=ip;”后,将ip复制到iq,使iq指向ip指向的对象,此时iq和ip指向相同的对象。11119.2.1 指针变量指针变量指针变量用于保存某种类型变量的地址。指针变量的定义形式如下:类型名*指针变量名1,*指针变量名2,;例如:char*p1,*p2;9.2 指针变量和指针变量的赋值指针变量和指针变量的赋值1212该语句定义两个指针变量p1和p2,用星号(*)说明p1和p2是指针,用char来说明类型,
7、p1和p2可以指向char类型的变量,也就是说p1和p2用来保存char类型变量的地址。又如:int*p1,*p2;double*f1;此时,p1、p2是两个指向int类型变量的指针,f1是指向double类型变量的指针,也就是说,p1和p2中保存int类型变量的地址,f1保存double类型变量的地址。再比如:int*p1,*s1,k=20;s1=&k;p1=&s1;1313此时,指针变量p1指向int*类型的变量,而int*类型的变量也是一个指针,这个指针又指向int类型的变量,也就是说,p1是指向int类型指针的指针(p1是一个二级指针)。以上语句使s1指向k,然后使p1指向s1。其中,
8、“&”符号是求地址运算符,通过这个运算符可以获取一个变量的地址。定义指针变量要指出它的类型,用类型来指明它能够保存哪种类型变量的地址,这非常重要。我们在上一节讲到,不同数据类型占用的存储单元字节数不同,我们可以通过指针的加1、减1运算来移动指针,也就是通过修改指针中保存的地址指向下一个对象。1414不同数据类型的对象,要让指针指向下一个对象,所跨越的字节数是不同的,对指针加1、减1,是要求指针指向下一个不同的对象,所以用指针的类型来区分在对指针做运算时如何修改指针中保存的地址(比如,对指针加1、减1运算,char类型实际按照加、减1修改地址,short int类型按照加2、减2修改地址,lon
9、g类型按照加4、减4修改地址等)。类型不同的指针变量不能混合使用。注意:严格来讲,指针和指针变量是有区别的,指针是指已经保存了地址的指针变量,而指针变量是一种变量,它可以被重新赋值。本书中对指针和指针变量没有严格的区分。15159.2.2 指针变量的赋值指针变量的赋值指针变量可以通过不同的方式获得一个确定的地址值,让指针指向一个具体的对象。1.通过求地址运算符通过求地址运算符(&)获取变量的地址值获取变量的地址值&运算符是一个单目运算符,用于求出其操作数的地址,这个操作数可以是某种类型的变量。比如有如下两条语句:int*q,*p,k=1;q=&k;1616第二条语句是把k的地址取出赋给指针q,
10、也就是说q指向k,或者说q指针保存了变量k的地址。求地址运算符&的操作数只能是内存中的对象变量或者数组元素,不能用于register型的变量、常量以及表达式等,所以,q=&9,q=&(k+2)都是错误的。求地址运算符&的操作数的类型必须和指针的类型相同。标准输入函数scanf()函数要求输入项必须是地址值,所以当有语句q=&k;时,scanf(%d,&k)和scnaf(%d,q)是等价的,因为q中保存的是变量k的地址值。17172.通过指针变量获取地址值通过指针变量获取地址值可以通过把一个指针变量赋给另外一个相同类型的指针变量来实现指针赋值,赋值后这两个指针指向同一个对象。如:int*q,*p
11、,k=1;q=&k;p=q;上述语句就使指针变量p和q指向同一个变量k,即p和q中保存的都是k的地址。18183.通过标准函数获得地址值通过标准函数获得地址值C语言提供了两个标准函数,分别为malloc()和calloc(),这两个函数用于在内存中开辟动态存储空间,这两个函数返回地址值。有关如何使用malloc()和calloc()函数的内容,我们在后续章节中介绍。19194.给指针变量赋一个空给指针变量赋一个空(NULL)值值前面我们看到,指针变量可以间接地赋地址值,我们也可以给指针变量直接赋一个常量NULL值。除此之外,标准语言不允许给指针直接赋一个整数,例如:p=NULL;/*是合法的。
12、*/p=1234;/*是不合法的。编译时会出现警告错误。*/NULL是一个预定义的符号,它的定义在头文件“stdio.h”中。一个指针被赋值NULL,我们称该指针为空指针。空指针是无效指针,不能通过无效指针间接引用。2020在C语言中,一个调用函数(调用者)通过传值调用另一个被调用函数(被调用者),在被调用函数中对形参的修改,对调用函数的实参不起作用。例如:在排序子程序中可能要通过调用swap函数交换两个不符合顺序的参数(实参)a和b,调用语句为swap(a,b);swap()函数的定义如下:void swap(int x,int y)/*交换x和y的值*/9.3 指针和函数参数指针和函数参数
13、2121 int temp;temp=x;x=y;y=temp;假设在排序子程序中,通过传值调用去调用该swap函数,swap函数并没有影响排序子程序中a和b的值,swap函数中只是对a和b的一份拷贝(x和y)进行了操作。为了取得期望的效果,可以给调用函数传递地址。调用语句为swap(&a,&b);2222其中,&a表示a的地址,&b表示b的地址,那么swap的定义相应地改为void swap(int*px,int*py)/*px和py是指针参数,是形参*/int temp;temp=*px;*px=*py;*py=temp;2323这样就可以实现a和b的交换。调用函数和被调用函数参数之间的关
14、系如图9-2所示。指针作为函数的参数能使该函数(被调用者)访问并改变调用它的那个函数(调用者)中的对象。让我们举例来说明。2424图9-2 调用函数和被调用函数参数之间的关系2525【例【例9.1】使用第6章例6.4中的冒泡排序算法,逆序时调用swap函数实现两个数的交换。程序如下:#include void swap(int*px,int*py);main()int num9,i,j;printf(Input 9 integer numbers arbitrarily:);for(i=0;i9;i+)2626 scanf(%d,&numi);printf(nOriginal 9 intege
15、r numbers:);for(i=0;i9;i+)printf(%d,numi);for(i=0;i8;i+)/*n-1趟冒泡操作*/for(j=0;jnumj+1)/*比较numj和numj+1,即相邻两整数*/swap(&numj,&numj+1);/*逆序时交换numj和numj+1,传两个元素地址*/printf(nSorting result:);2727 for(i=0;i9;i+)printf(%d,numi);printf(n);void swap(int*px,int*py)/*px和py是指针参数,是形参*/int temp;temp=*px;2828 *px=*py;*
16、py=temp;程序运行后输出:Input 9 integer numbers arbitrarily:23 24 3 2 34 2356 6 7 777Original 9 integer numbers:23,24,3,2,34,2356,6,7,777Sorting result:2,3,6,7,23,24,34,777,23562929本例中,调用函数为main()函数,被调用函数为swap()函数。请用本节开头的swap()函数试一试,看看能否得到正确结果。3030【例【例9.2】编写函数addx(int*a,int*b),函数中把指针变量a和b所指的存储单元中的两个值相加,然后将结
17、果值作为函数值返回。在主函数中输入两个数赋给两个变量,把变量的地址作为实参,传送给对应形参。int addx(int *a,int*b)int sum;sum=*a+*b;return sum;3131main()int x,y,z;printf(Enter x,y:);scanf(%d%d,&x,&y);z=addx(&x,&y);printf(%d+%d=%dn,x,y,z);3232程序运行结果:Enter x,y:23 4523+45=683333在C语言中,指针和数组有着密切的关系,常将指针和数组进行同步讨论;能够通过数组下标完成的任何运算,也可以通过指针来完成,一般用指针完成得更快
18、,但指针的使用方法往往掌握起来有些困难。9.4 指指针针和和数数组组34349.4.1 一维数组和数组元素的地址一维数组和数组元素的地址定义一个一维数组:int a10;现在定义了一个有10个元素的一维数组,即有一组命名为a0、a1、a2、a9的对象。3535符号ai称为数组的第i个元素。如果pa是一个指向int的指针,定义为int*pa;那么赋值语句:pa=&a0;设置pa指向数组a的元素0,即pa保存了a0的地址。3636现在我们可以通过指针pa引用数组a的所有元素,pa+1指向pa的下一个元素,pa+i指向pa后的第i个元素,pa-i指向pa之前的第i个元素;*(pa+1)就是a1的内容
19、,*(pa+i)就是ai的内容。3737我们在前面讲过,数组名是一个常量指针,其指向数组的第一个元素,常量指针不能修改,但可以引用,如a=&x;a+;这些语句都是不合法的;pa=a;是合法的,等价于pa=&a0。38389.4.2 通过一维数组名和指针引用数组元素通过一维数组名和指针引用数组元素数组名是数组的首地址,指向数组的第一个元素。由以上例子可知,我们可以通过数组a的数组名a来引用其元素,a的值等于&a0的值,a+1的值等于&a1的值,a+9的值等于&a9的值。因此,我们可以通过间接访问运算符“*”访问指针指向的对象,对于数组元素a0的引用,可以用表达式*&a0来代替,也可以用*(a+0
20、)来代替,也可以写成*a;对数组元素a1的引用,可以用表达式*&a1来代替,也可以用*(a+1)来代替;3939依此类推,对数组元素a9的引用,可以用表达式*&a9来代替,也可以用*(a+9)来代替;因此我们可以用如下语句输出数组a中所有的元素:for(k=0;k10;k+)printf(%4d,*(a+k);或者:for(k=0;k10;k+)printf(%4d,ak);4040【例【例9.3】假设一个一维数组a,有10个整型元素,请用其数组名输出数组中的所有元素。方法1:用数组名引用一维数组中的元素。#include main()int i,a10=1,2,3,4,5,6,7,8,9,1
21、0;for(i=0;i10;i+)printf(%d,*(a+i);4141 printf(n);程序运行结果:1,2,3,4,5,6,7,8,9,10,方法2:通过每个数组元素的地址,用间接运算符来引用一维数组中的元素。#include main()4242 int i,a10=1,2,3,4,5,6,7,8,9,10;for(i=0;i10;i+)printf(%d,*&ai);printf(n);4343程序运行结果:1,2,3,4,5,6,7,8,9,10,也可以通过指针来引用一维数组中的元素,我们在前面讲过,数组名是一个常量指针,虽然我们不能给它重新赋一个新值,但我们可以引用,我们可
22、以把它拷贝到另一个指针变量,然后通过修改该指针变量来引用数组中的元素,下面举例说明。4444【例【例9.4】假设一个一维数组a有10个整型元素,请通过修改指针,引用数组中的元素并输出。#includemain()int i,a10=1,2,3,4,5,6,7,8,9,10;int*ip;ip=a;for(i=0;i10;i+)printf(%d,*ip);4545 ip+;/*现在可以修改ip指针的值,指针a不允许被修改*/printf(n);程序运行结果:1,2,3,4,5,6,7,8,9,10,46469.4.3 引用一维数组元素的方法总结引用一维数组元素的方法总结如果有以下定义和语句:i
23、nt*p,a10,i;p=a;我们可以用&ai、a+i和p+i三种表达式来表示数组元素ai的地址,同时可以用ai、*(a+i)和*(p+i)三种表达式来表示数组元素ai,还可以用pi表示数组元素ai。所以表示数组元素ai的表达式应当有:ai;*(a+i);*(p+i);pi。4747将指针、数组、地址运算整合在一起是体现C语言强大功能的一个方面。如果p是指向数组元素的一个指针,那么p+对p自加1运算后,p指向下一个元素,而p+=i对p加i后,p指向与p原来指向的那个元素相距i个元素的那个元素。如果p是一个指针变量,它可以保存地址,地址是一个无符号的整数值,我们可以对该指针做一些运算。由于地址本
24、身的特殊性,因此这些运算受到一定的限制。对指针只能进行如下运算:9.5 地地 址址 运运 算算4848(1)与整数相加、减运算。(2)赋值运算。(3)同一数组中各元素地址间的关系运算与相减运算。49499.5.1 指针与整数相加、减运算指针与整数相加、减运算指针与整数相加、减,表示指针在内存空间向下、向上移动,移动单位与其指向的数据类型有关。int型指针的移动单位可能是2个字节(与系统有关),即int型指针加1,向下移动2个字节,减1向上移动2个字节。char型指针的移动单位是1个字节。图9-3为指针与整数相加减时的指针移动示意图。假设pf指针是int型的指针,移动单位是2个字节。5050图9
25、-3 指针与整数相加、减时的指针移动示意图5151我们打个比方,仓库里有货架,货架上有货箱,货箱里有隔档,货架、货箱、隔档都有编号,用不同类型的指针保存这些编号。对指向货架的指针加1、减1,相当于指针变动1个货架位置;对指向货箱的指针加1、减1,相当于指针变动1个货箱位置;对指向隔档的指针加1、减1,相当于指针变动1个隔档位置;货架、货箱、隔档在仓库里占用的空间是不同的。5252【例【例9.5】指向不同类型的指针做加、减运算后,指针值的变化情况。#include main()short int*pf,dat2=1,2;float*fp,f2=3.5f,3.6f;char*cp,c2=A,B;p
26、f=dat;fp=f;cp=c;5353 printf(start address of dat is%xn,pf);printf(start address of f is%xn,fp);printf(start address of c is%xn,cp);printf(pf+1=%xn,pf+1);printf(fp+1=%xn,fp+1);printf(cp+1=%xn,cp+1);printf(pf-1=%xn,pf-1);printf(fp-1=%xn,fp-1);printf(cp-1=%xn,cp-1);5454程序运行结果:start address of dat is 12
27、ff78start address of f is 12ff6cstart address of c is 12ff64pf+1=12ff7afp+1=12ff70cp+1=12ff65pf-1=12ff76fp-1=12ff68cp-1=12ff635555注意观察程序运行后的结果,本程序输出的是指针原来的值,做指针运算后输出运算后的结果。我们应把注意力放在指针值的变化上,至于为什么是这个值,这是系统分配的内存地址。5656【例【例9.6】假设一个一维数组a有10个float元素,请通过指针运算,求a5之后的第3个元素和a5之前的第2个元素。#includemain()float a10=1
28、.0,2.3,3.5,4.5,5,6,7,8,9,10.0;float*ip;ip=&a5;/*ip指向a5*/5757 ip=ip+3;printf(The third element after a5 is:%.3fn,*ip);ip=ip-5;printf(The second element before a5 is:%.3fn,*ip);程序运行结果:The third element after a5 is:9.000The second element before a5 is:4.50058589.5.2 指针赋值运算指针赋值运算指针可以通过赋值运算改变其所指向的对象,指针的赋
29、值运算有以下三种情形:(1)给指针赋以一个对应类型的变量地址。例如:int i1,i2;int*pi,*pf;pi=&i1;pf=&i2;5959(2)同类型指针间赋值。例如:int a,b;int*pa=&a;int*pb=&b;pa=pb;6060(3)指针增1、减1,即指针向后或向前移动一个所指向的数据类型空间。增1、减1运算与*运算符优先级别相同,它们在同一表达式中时,应按自右向左的顺序结合。例如:y=*+px 与y=+*px以上两个表达式中前一个表达式的结合顺序为:px自加1,然后对px做间接运算,最后给y赋值;后一个表达式的结合顺序为:对px做间接运算,然后对(*px)自加1,最后
30、给y赋值。6161例如:y=*px+此表达式等价于:y=*(px+)6262(4)给void类型指针赋值。void指针是一种特别的指针,其定义形式为void*指针变量名;说它特别是因为它没有类型,或者说这个类型不能判断出其指向对象的长度。任何类型的指针都能赋给void类型的指针,但void类型的指针不能间接访问,void指针也不能参与指针运算,除非通过强制类型转换,转换成可以间接访问的其它类型的指针。void类型的指针一般用于函数的返回类型不确定和函数形式参数类型不确定的定义中。6363例如:main()int i=9;int*ip=&i;void*p1;p1=ip;printf(%d,*(i
31、nt*)(p1);/*对p1进行强制类型转换后,再间接引用*/6464程序运行结果:9请将printf(%d,*(int*)(p1);语句改为printf(%d,*p1);后运行该程序,看看有什么结果。65659.5.3 同一数组中各元素地址间的关系运算与相减同一数组中各元素地址间的关系运算与相减运算运算同一数组中各元素地址间可以进行比较和相减运算。例如,p和q都指向某一个数组的元素,如果pq为真,则p指向的数组元素在q指向的数组元素之后;如果为假,则q指向的数组元素在p指向的数组元素之后或者q和p指向同一个元素。同样可以用p-q的值来判断p和q之间相隔多少个数组元素。6666【例【例9.7】
32、假设一个一维数组a有10个float元素,请通过指针的比较和相减运算,判断a5和a9之间相隔多少个元素?a9在a5之前还是之后?#includemain()float a10=1.0,2.3,3.5,4.5,5,6,7,8,9,10.0;float *p,*q;p=&a5;6767 q=&a9;printf(Between a5 and a9 has%d elementsn,q-p);if(pq)printf(a5 is after a9!n);else printf(a5 is before a9!n);6868程序运行结果:Between a5 and a9 has 4 elementsa
33、5 is before a9!69699.6.1 字符指针字符指针一个字符串常量“I am a string”是一个字符数组,在内部表示中,这个数组是以空字符结尾的,以便于程序能找到其尾部,实际存储的长度要比双引号之间的字符数多一个字符,这个字符就是空字符。如果定义:char*pmessage;9.6 字符指针和有关函数字符指针和有关函数7070那么语句pmessage=I am a student.;意味着赋给pmessage一个指向字符数组的指针,即pmessage是一个字符指针。注意,这不是字符串复制,仅仅与指针相关,C语言不提供将字符串作为单位的任何运算符。注意下面两种定义之间的明显差
34、别:char amessage=now is the time;/*定义一个字符数组并初始化。*/char*pmessage=now is the time;/*定义一个字符指针并初始化。*/7171根据数组一章的内容我们知道,amessage是一个字符数组,数组的大小和内容由初始化它的字符序列和0确定。数组中的字符可以分别进行修改,但amessage数组的地址不能改变,即数组名amessage是一个常量字符指针,不能被修改,只能被引用。而pmessage是一个指针,指向一个字符串常量,以后该指针可以通过修改指向其它地方,但通过该指针修改字符串内容,其结果是不确定的。7272【例【例9.8】字
35、符指针和字符数组。#include#includemain()int i;char*cp,arr10;cp=abcdef;for(i=0;cpi;i+)7373 arri=cpi;arri=0;printf(%st%st%dn,cp,arr,strcmp(cp,arr);程序运行结果:abcdefabcdef 0此例中,cp是字符指针,给它赋值;arr 是字符数组,通过for循环给它赋值,然后通过执行语句arri=0;,使它的值与cp所指字符串值相等。printf()语句分别输出cp和arr,是完全相同的两个字符串,最后输出这两个字符串比较函数strcmp()的值0,7474说明cp和arr指
36、向的内容完全相同。strcmp()函数是标准库函数,请见后面的详述。【例【例9.9】分别利用字符指针和字符数组下标,修改字符串中的字符。#includemain()char amessage =now is the time;/*字符数组*/char*pmessage=now is the time;/*字符指针*/7575 char*p1=NOW IS THE TIME;amessage0=N;amessage1=O;amessage2=W;printf(%sn,amessage);pmessage=p1;printf(%sn,pmessage);程序运行结果:NOW is the time
37、NOW IS THE TIME76769.6.2 常用字符串处理函数常用字符串处理函数下面我们介绍C语言标准库函数中提供的一些常用字符串处理函数。1.strcmp(s1,s2)该函数的功能是对s1和s2两个字符串的内容进行比较,s1和s2为字符指针。按字典顺序比较s1和s2,按大于、等于或小于的不同情况,分别返回一个大于0、等于0或小于0的整数。77772.strlen(s)该函数将返回字符串s的长度,不包括结束符0。s是指向char的指针。78783.index(s,c)该函数返回字符串s中第一次出现c的位置,即为一个指针,其中,s是指向char的指针,c是char型数据。如果c不在s中出现
38、,则返回NULL。79794.strcat(s1,s2)该函数返回将s2的副本附加在s1尾部以后的s1的指针。s1和s2是指向char的指针。s2经运行后不变。要求s1有足够大的空间来存放s2的副本。函数返回指向字符串s1的指针。80805.strcpy(s1,s2)该函数将s2的内容复制到s1中,返回指向char的指针。不管s1原先有什么内容都被覆盖掉。要求s1要有足够的空间来存放s2的有关内容。这些函数的申明在string.h头文件中,所以,程序中要使用这些函数,必须包含该头文件。8181【例【例9.10】字符串函数的使用。#include#includemain()char str120
39、=white;char str2=snow;char str320=Hello!;char*str4=Moto!;8282 strcpy(str1,str2);printf(str2 copy to str1:%sn,str1);printf(str3 string length is:%dn,strlen(str3);printf(str3 catenate str4:%sn,strcat(str3,str4);printf(str3 compare with str4,the result is:%dn,strcmp(str3,str4);8383程序运行结果:str2 copy to s
40、tr1:snowstr3 string length is:6str3 catenate str4:Hello!Moto!str3 compare with str4,the result is:-58484数组元素为指针的数组称为指针数组。指针数组是一系列有序的指针集合,其所有元素都是具有相同存储类型和指向相同数据类型的指针。9.7 指针数组和指向数组的指针指针数组和指向数组的指针85859.7.1 指针数组的定义指针数组的定义指针数组的定义格式如下:类型名*指针名常量表达式,;如:int*pa5;则pa是具有5个数组元素的指针数组,每个元素都是指向int型变量的指针,其内存分配如图9-4所
41、示。8686图9-4 指针数组的内存分配图8787其中,pa0pa4根据赋值情况可能指向一个整数或整数数组,也可能是一个空指针或随机的内存地址值(如果没有赋值的话)。88889.7.2 指针数组的初始化指针数组的初始化指针数组的初始化和赋值与普通数组的初始化和赋值一样。我们在数组章节中讲过,二维数组可以用一维数组来构造,二维数组可以认为是一个数组元素为一维数组的一维数组,二维数组的数组名是一个常量指针,指向一维数组。例如:int a23;其中,a是一个二维数组,共23个元素,它可以分成两个一维数组a0和a1,每个一维数组又各有3个元素,内存分配如图9-5所示。8989图9-5 二维数组的内存分
42、配图9090若有一个一维的指针数组:int*pa2;可以将a0和a1的首地址分别赋给pa0和pa1,即:pa0=a0;或 pa0=&a00;pa1=a1;或 pa1=&a10;由此可以得出,一维指针数组的数组名是一个指向指针的常量指针(指向指针的指针,称为二级指针);任何一个二维数组都可以表示成一个一维指针数组,二维数组可以化为二级指针。同样,一个三维数组也可以表示成为一个一维指针数组,而这种指针数组的每个元素是一个指向二维数组的指针,9191而这个二维数组又可以化为一个一维指针数组。依此类推,不难得出,三维数组可以化为三级指针,同理,n维数组可以化为n级指针。下面通过例子说明指针数组的应用。
43、9292【例【例9.11】指针数组赋值举例。#includemain()int a23,*pb2;int i,j;for(i=0;i2;i+)for(j=0;j3;j+)aij=(i+1)*(j+1);/*数组a的赋值*/pb0=a0;9393 pb1=a1;/*给指针数组pb2赋值*/for(i=0;i2;i+)for(j=0;j3;j+)printf(a%d%d:%2d%c,i,j,*pbi+,(j=2)?n:t);/*输出格式控制*/程序运行结果:a00:1a01:2a02:3a10:2a11:4a12:69494【例【例9.12】例9.11中数组a23的另一种表示形式。#include
44、main()static int a23;static int*pb2=a0,a1;int i,j;for(i=0;i2;i+)for(j=0;j3;j+)*(ai+j)=(i+1)*(j+1);/*数组a的赋值*/9595 for(i=0;i2;i+)for(j=0;j3;j+)printf(a%d%d:%2d%c,i,j,*(pbi+j),(j=2)?n:t);/*输出格式控制*/程序运行结果:a00:1a01:2a02:3a10:2a11:4a12:69696上例中,通过static int pb=a0,a1;给指针数组pb赋初值。用双重for循环给aij赋值,这里*(pbi+j)的表示
45、与aij是等价的。97979.7.3 字符指针数组字符指针数组如果一个指针数组的元素是指向char型数组的,这种指针数组又称为字符指针数组。字符指针数组多用来处理多个字符串。【例【例9.13】字符指针数组的应用。#includemain()char*weekname=,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday;9898 int week;while(1)printf(Enter week No.:);scanf(%d,&week);if(week7)break;printf(Week No.%d-%sn,week,week
46、nameweek);9999程序运行结果:Enter week No.:3Week No.3-WednesdayEnter week No.:4Week No.3-ThursdayEnter week No.:01001009.7.4 指向数组的指针指向数组的指针指向数组的指针是一种指针,这种指针所指向的变量是数组。与指针数组不同,指向数组的指针的定义如下:类型名(*数组名)常量表达式,;例如:int(*pa)5;pa是一个指向数组的指针,它所指向的是一个一维数组,有5个整型元素。实际上pa是一个行数不确定的二维数组,该数组一行具有5个元素,其内存分配示意图如图9-6所示。101101图9-6
47、 指向数组的指针的内存分配示意图102102使用指针作为函数的形参可以实现传址调用,而指向数组的指针常用作函数的参数,也可实现传址调用。关于传址调用的内容请参见下节。103103【例【例9.14】举例说明指向数组的指针作为函数参数。#includef(int(*a)3)printf(n%dt%dt%dn,a00,a10,a20);main()int t33=1,2,3,4,5,6,7,8,9;f(t);104104程序运行结果:1 47上例中,int(*a)3中,a是指向数组的指针,作为函数的形参,它所指的数组是从实参t33传送过来的,即指向二维数组。1051059.7.5 命令行参数命令行参
48、数在支持C语言的环境中,有一种向该命令执行的程序传递命令行参数(命令字以及后面所带参数组成命令行参数)的方式。例如:C:copy x1.c x2.c其中,copy是命令字,x1.c、x2.c是参数。命令行参数的个数是指包括命令字在内的参数个数。上例中参数个数是3。在C语言中,当调用main函数时,用两个参数:第一个叫argc,用于参数个数计数,是程序执行时命令行参数的个数;106106第二个叫argv,是一个一维指针数组,它的元素个数同argc的值相同,每个指针指向一个作为参数的字符串。其中,argv0总是指向命令字字符串,argi按先后顺序依次指向命令行的各个参数字符串。我们举例说明。107
49、107【例【例9.15】有一个程序命令echo,该命令回应命令行参数,参数间用空格分开。#include /*该程序命名为echo*/main(int argc,char*argv)int i;for(i=1;iargc;i+)printf(%s%c,argvi,(iargc-1)?:n);108108程序运行结果:在命令行提示符下输入echo prog1.c prog2.cprog1.c prog2.c109109指针可作为函数参数,实现传址调用,指针也可以作为函数的返回值,而这种函数为指针函数,也就是说,函数的返回值为指针类型。另外,指向函数的指针可作为参数。9.8 指指针针与与函函数数1
50、101109.8.1 指针作为函数的参数指针作为函数的参数指针可作为函数参数,实现传址调用。正如本章9.3节所讲,传址调用和传值调用有着很大的区别,主要在于传址调用使得被调用函数中参数值的改变影响调用函数的参数,而传值调用中被调用函数中参数值的改变不影响调用函数的参数。在传址调用中,调用函数的实参用地址值,而被调用函数的形参用指针。111111【例【例9.16】传值调用和传址调用的区别。#includemain()int i;i=50;void addr(int*a);void value(int v);printf(After assignment,i=%dn,i);addr(&i);112