C语言程序设计课件第09章_第1页
C语言程序设计课件第09章_第2页
C语言程序设计课件第09章_第3页
C语言程序设计课件第09章_第4页
C语言程序设计课件第09章_第5页
已阅读5页,还剩65页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第9章指针[Return]本章学习目标

掌握指针和指针变量的概念掌握指针变量的定义及运算掌握指针处理数组的方法掌握指针处理字符串的方法掌握函数指针的概念和含有指针参数的函数的定义及调用方法本章主要内容9.1指针与内存地址9.2变量的指针与指针变量9.3数组指针9.4字符串指针9.5函数指针9.6返回指针值的函数9.7指针数组和指向指针的指针9.1指针与内存地址9.1.1变量的地址在定义一个变量时,系统首先会在内存中为这个变量分配一块存储区用来存放这个变量的值。而这块存储区的大小是由变量类型所决定的。例如,在C系统中一个int型数据占2个字节,因此当我们定义一个int型变量时,系统就会为这个变量分配一个大小为2个字节的存储区。为了方便管理这些存储区,操作系统会给内存中的每一个字节进行编号,这个编号就叫做内存的“地址”,而每一个变量所占据存储区的第一个字节的编号称为是该变量的“地址”

例如,我们定义三个整型变量:intm,n,k;定义时,假设系统为这三个变量分配存储空间的情况如图9-1所示。由于int类型在C系统中占2个字节,所以在定义m,n,k三个变量时,系统将编号为1000和1001的两个字节分配给m,将1002、1003两个字节分配给n,将1004、1005两个字节分配给k。因此变量m的地址就是1000,变量n的地址是1002,变量k的地址是1004。mnk……100010021004图9-1系统分配地址……9.1.2变量在内存中的存取1、变量在内存中的存储系统对变量值的存取实际都是通过地址进行的。例如:m=20;n=25;k=30;程序在执行这三条赋值语句时,系统会根据变量名m、n、k的地址(1000、1002、1004)找到它们在内存中所对应的存储区,然后将数值20、25、30分别存放到这些存储区中,从而完成了赋值操作。如图9-2mnk……100010021004图9-2赋值后的内存情况……2025302、从变量中读取数据从变量中读取已存的数据也是通过地址进行的。例如,程序在执行printf(“%d”,m);时,系统会根据变量名m找到它所对应的地址1000,然后从编号为1000、1001的两个字节的存储区中取出所存放的数据20,然后将它输出到控制台中。由此可见,系统通过变量地址就可以方便地对变量单元进行访问(这种方式也称为“直接访问”方式),因此我们可以形象地说变量地址“指向”了该变量单元。在C语言中,也将地址叫做“指针”,变量的地址就称为变量的“指针”。[Return]9.2 变量的指针与指针变量9.2.1什么是指针变量访问某一变量单元还可以通过一种“间接访问”的方式,可以先将某一变量的地址存放到另外一个特殊的变量中,然后通过访问这个特殊变量中所存放的地址从而得到该地址所指向的变量单元中的值。例如:intm=3;假设系统为变量m分配的存储空间的地址为2000,现定义一个特殊变量p,将变量m的地址(2000)存放到p中。如图9-3所示。20003pm图9-3变量p的存储图由于特殊变量p中存放的是m的地址2000,所以通过访问p就可以间接得到m的值3。这样就好象在特殊变量p和变量m之间建立了一种联系,即通过p可以得到m的地址,从而得到m的值。这种联系一般称为“指向”,特殊变量p称为“指针变量”,p和m的关系就可以称为指针变量p指向变量m。指针变量:专门用来存放另一变量的地址(既指针)的变量。※注意:指针变量只能存放变量的地址(既指针),而不能存放其他类型的数据。9.2.2指针变量的定义在使用指针变量前必须要先定义,指针变量的定义格式为:指针变量所指变量的类型

﹡指针变量名定义格式中“﹡”表示所定义的变量是一个指针变量,例如:int﹡p;表示p是一个指向整型变量的指针变量,既只能存放整型变量的地址,而不能存放其他类型的地址。如果一行定义多个指针变量,不同的指针变量名之间要用“,”隔开。例如:char﹡p1,﹡p2,﹡p3;※注意:c语言的使用者和一些资料经常将定义指针变量简化说成定义指针,实际指针和指针变量是两个完全不同的概念:指针是指地址,是某种特定类型的数据在内存中的存放地址;指针变量是指存放指针(既地址)的变量。因此,对于平时所说的“定义一个指针”就是指定义一个指针变量。9.2.3指针变量的引用对指针变量进行赋值和引用主要通过“&”和“﹡”两个运算符实现的。1.“&”运算符“&”运算符又叫做取地址运算符,它是单目运算符,可以返回某一变量的内存地址,常用于对指针变量进行赋值的操作中。例如: intm; int﹡p; p=&m;

/*将变量m的地址获取并赋给指针变量p,表示p指向变量m*/赋值后的示意图如图9-4所示。

&mpm图9-4取变量m地址在对指针变量进行赋值时应该注意以下几点:(1)由于指针变量存放的是地址(指针),因此不能将其他非地址类型的数据赋给指针变量。例如以下的赋值不合法: int﹡p; p=100;/*错误,不能将一个整型数据赋给指针变量*/(2)对指针变量进行赋值时,注意不要将“p=&m”写成“﹡p=&m”。因为“﹡p=&m”不是将m的地址赋给p,而是将m的地址赋给指针变量p所指的变量。(3)对指针变量进行赋值时,指针变量的数据类型应该与指针变量中所要存放地址的变量的类型相同。例如,如果定义“char﹡q”,则q只能指向字符类型的变量,而不能指向其他类型的变量。例如: intn; char﹡q; q=&n;/*错误,指针变量q只能存放字符类型变量的地址*/2.“﹡”运算符“﹡”运算符又叫做“间接访问”或“取地址内容”运算符,该运算符也是单目运算符,它与指针变量结合,用来表示该指针变量所指变量的值。/*exam9-1*/#include“stdio.h”main(){intm;int﹡p;m=5;p=&m;/*将m的地址赋给p*/printf(“m=%d\n”,m);printf(“﹡p=%d\n”,﹡p);} /*

﹡p代表的就是p所指的变量单元m的值*/[演示]/*exam9-2*/#include“stdio.h”main(){intm;int﹡p;m=5;p=&m;/*将m的地址赋给p*/﹡p=10;

/*将10赋给p所指的变量单元*/printf(“m=%d\n”,m);printf(“﹡p=%d\n”,﹡p);} 本程序中,在将5赋给变量m后,虽然没有直接改变m的值,但由于指针变量p指向m,因此程序第5行改变﹡p的值实际就是改变p所指变量m的值,即m的输出结果为10。[演示]需要说明的是,当一个指针变量没有指向任何存储空间时(即没有对指针变量进行赋值),不能使用“﹡”运算符来进行指针取内容运算。例如: #include“stdio.h” main() { int﹡p; printf(“%d”,﹡p);/*错误,因为p没有指针任何存储空间*/ } 在以上的程序中,由于指针变量p没有指向任何的存储空间,即﹡p并不存在,因此直接引用输出﹡p是错误的。但如果运行以上的错误程序,读者会发现该程序不但可以正常运行,而且输出的结果会是一串数字。实际上这串数字结果是毫无意义的,它只是一个随机数,这是因为我们虽然没有给指针变量p进行赋值,但C语言系统在进行编译的时候会随机地赋给指针变量p一个地址值,因此输出的这个数字结果是一个没有任何意义的值。3.“&”和“﹡”的优先级关系“&”和“﹡”的优先级相同,它们与“++”是同一级别,仅次于“()”,因此当它们在一起使用的时候,按自右而左的方向进行结合运算。例如,假设指针变量p已指向变量m(p=&m),则运行&﹡p时,先进行﹡p的运算,即变量m,然后再进行&运算。因此&﹡p运算就等价于&m运算,表达式返回的值就是变量m的地址。同样,当执行﹡&m时,先执行&m,即返回变量m的地址,然后再进行*运算,最后表达式返回的值就是变量m的值。/*exam9-3*/#include“stdio.h”main(){intm;int﹡p;m=10;p=&m;printf(“m=%d,﹡p=%d,﹡&m=%d\n”,m,﹡p,﹡&m);printf(“&m=%x,p=%x,&﹡p=%x\n”,&m,p,&﹡p);} } [演示]m=10,*p=10,*&m=10&m=12ff7c,p=12ff7c,&*p=12ff7c程序运行结果:﹡p和﹡&m的值就是变量m的值;p和&﹡p的值就是变量m的地址指针变量应用举例例9-4通过指针变量实现将两个整型数按照从小到大的顺序输出/*exam9-4*/#include“stdio.h”main(){intm,n;int﹡p,﹡p1,﹡p2;scanf(“%d,%d”,&m,&n);/*从键盘输入m和n的值*/p1=&m;p2=&n;if(m>n)p=p1;p1=p2;p2=p;/*交换指针变量p1、p2所指向的对象*/printf(“m=%d,n=%d\n”,m,n);printf(“﹡p1=%d,﹡p2=%d\n”,﹡p1,﹡p2);}[演示]15,10↙m=15,n=10*p1=10,*p2=15程序运行结果:例9-4程序分析:本程序中,p1指向变量m,p2指向变量n。当输入m=15,n=10时,由于m>n,所以执行“p=p1;p1=p2;p2=p”,将指针变量p1与p2的值进行交换。在这里请注意,p1与p2交换的只是它们所指向变量的地址(即交换完后p1指向变量n,p2指向变量m),而变量m与n的值却没有改变。因此在输出﹡p1和﹡p2时,实际输出的是变量n和m的值。指针变量p1与p2交换过程如图9-4所示。10&m&n15指针变量p1指针变量p2变量m变量np1与p2交换前(a)&m10&n15指针变量p1指针变量p2变量m变量np1与p2交换后(b)图9-4指针变量p1与p2交换示意图例9-4通过指针变量实现将两个整型数按照从小到大的顺序输出/*exam9-4*/#include“stdio.h”main(){intm,n;int﹡p,﹡p1,﹡p2;scanf(“%d,%d”,&m,&n);

/*从键盘输入m和n的值*/p1=&m;p2=&n;if(m>n)p=p1;p1=p2;p2=p;/*交换指针变量p1、p2所指向的对象*/printf(“m=%d,n=%d\n”,m,n);printf(“﹡p1=%d,﹡p2=%d\n”,﹡p1,﹡p2);}15,10↙m=10,n=15*p1=10,*p2=15程序运行结果:对于指针变量p,当直接引用p时,代表的是p所指向变量的地址;当引用﹡p时,代表的是p所指向的变量的值。9.2.3指针变量的引用在第七章讨论函数的调用方式时,我们介绍了函数的参数可以是整型、实型、字符型等类型的数据,这种函数的调用一般被称为“传值调用”。除了“传值调用”的函数外,C语言还提供了一种调用函数的方式——“传址调用”,这种函数的特点是参数的类型是指针类型。“传值调用”的函数和“传址调用”的函数在形式上和功能上有什么区别呢?我们先来看两个例子。例9-6通过“传值调用”的方式,实现交换两个变量m、n的值/*exam9-6*/#include“stdio.h”voidswap(intx,inty){inttemp;temp=x;x=y;y=temp;}main(){intm,n;m=5;n=10;swap(m,n);/*将m,n的值作为实参传递给swap函数*/printf(“m=%d,n=%d\n”,m,n);}m=5,n=10程序运行结果:[演示]程序分析:本程序的意图是通过swap(intx,inty)函数实现对变量m和n进行值交换,但是在输出结果中m和n的值却没有改变。这是因为在函数调用时,m的值传给x,n的值传给y。在执行完“temp=x;x=y;y=temp;”后,互换的是x和y的值,main()函数中的m和n的值却没有互换。由此可见,“传值调用”中的单向传送的值传递方式并不能将形参值的改变传递给实参值。例9-6中的参数传递示意图如图9-5所示。551010mxnym、n的值传递给x、y(a)图9-5“传值调用”参数传递示意图510执行完swap函数后各变量的值(b)105mnxy因此,要想使被调函数中改变了的变量值能够被主调函数所用,就不能采用“传值调用”的函数方法,而应该将指针变量作为函数参数,即在函数的调用过程中将变量的地址作为实参值传递给函数,这样如果在函数执行过程中指针变量所指向的变量值发生了变化,那么当函数调用完毕后,主调函数的这些变量值的变化仍旧保留下来,这样就实现了通过被调函数修改主调函数中变量的值。这种采用指针变量作为形参的函数调用方法也称为“传址调用”。例9-7通过“传址调用”的方式,实现交换两个变量m、n的值/*exam9-7*/#include“stdio.h”voidswap(int﹡p,int﹡q)/*swap函数功能是交换指针变量p,q所指变量的值*/{inttemp;temp=﹡p;﹡p=﹡q;﹡q=temp;}main(){intm,n;int﹡p1,﹡p2;m=5;n=10;p1=&m;/*指针变量pointer_1指向变量m*/p2=&n;/*指针变量pointer_1指向变量n*/swap(p1,p2);/*将m,n的地址作为实参传递给swap函数*/printf(“m=%d,n=%d\n”,m,n);}[演示]程序分析:main函数中定义了两个整型变量m和n,定义了两个整型指针变量p1和p2。p1指向变量m,p2指向变量n,如图9-6(a)所示。p1,p2做为实参将它们所存放的m和n的地址传递给swap函数的参数p和q(此时p和q存放的是变量m和n的地址),如图9-6(b)所示。当swap函数中的“temp=*p;*p=*q;*q=temp”执行后,进行交换的是p和q所指向的变量的值,即变量m和n的值进行互换,如图9-6(c)所示。&n&m5&n10p1mp2n(a)图9-6指针变量做参数实现交换m和n的值&m5p1m&mp10p2n&n(b)&m10&n5p1mp2n(c)q因此,如果想通过被调函数来改变主调函数中变量的值,可以:(1)如果主调函数需要改变n个变量的值,那么在被调用函数中就相应地定义n个指针形参。(2)在主调函数调用被调函数时,主调函数将需要改变值的n个变量的地址作为实参传给被调用函数。(3)被调函数执行时,通过修改被调函数的指针形参的值从而改变主调函数中相应变量的值。例9-8输入三个整数,然后找出并输出最小的整数/*exam9-8*/#include“stdio.h”voidgetMin(int﹡pt1,int﹡pt2,int﹡pt3,int﹡pt){﹡pt=﹡pt1;if(﹡pt>﹡pt2) ﹡pt=﹡pt2;elseif(﹡pt>﹡pt3) ﹡pt=﹡pt3;}main(){inta,b,c,min;int﹡p1,﹡p2,﹡p3,﹡p;scanf(“%d%d%d”,&a,&b,&c);/*输入三个整数*/p1=&a;p2=&b;p3=&c;p=&min;getMin(p1,p2,p3,p);/*调用getMin函数找出最小整数*/printf(“最小整数为:%d”,min);/*输出最小整数min的值*/}[演示][Return]9.3 数组指针我们知道数组是由若干个数组元素组成的,而每个数组元素在内存中又有相应的地址,因此指针变量除了可以指向一般的变量外,也可以指向数组或数组元素。指针与数组有着密切的联系,使用指针的移动不但可以方便地访问数组元素还可以使程序在运行效率上有着显著地提高。9.3.1指向数组元素的指针指向数组元素指针的方法与指向普通变量指针的方法相同。例如: inta[5]; int﹡p; p=&a[0];该程序中定义了一个整型数组a和整型指针变量p,将数组元素a[0]

的地址赋给指针变量p,p就指向了数组a的第一个元素。如图9-7所示。a[0]a[1]a[2]a[3]a[4]&a[0]p图9-7指向一维数组的指针变量※注意:指针变量的类型必须要和数组类型一致,即如果数组为int类型,则指针变量也必须为int类型。在C语言中,同时规定数组名在使用时代表数组首元素的地址,因此p=&a[0]等价于p=a,即p指向数组第一个元素。和普通的指针变量一样,在定义指向数组元素的指针变量的同时也可以对它赋初值。例如例9-9中的程序就等价于: inta[5]; int﹡p=&a[0];※注意:千万不要把“p=a”认为是p指向a整个数组,在这里只是将数组a的首元素地址赋给了p。9.3.2通过指针引用数组元素引用数组中的元素除了可以使用常见的下标法之外,还可以使用指针法。在C语言中规定,如果指针变量p指向数组中的某一个元素,则p+1就指向该数组的下一个元素。例如,如果指针变量p指向数组a的第一个元素a[0],则p+1就指向下一个元素a[1],p+2指向数组元素a[2]。依次类推,p+i就指向数组元素a[i]。因此我们可以得出:(1)&a[i],p+i,a+i均表示a数组的第i个元素的地址。注意,这里的p+i不是将p的值简单地加i,而是在p所指元素的地址上加上i个数组元素所占的字节数。例如,假设数组a是一个整型数组,则p+i所代表的地址就为p+i*2(2为一个整型元素所占的字节数),即p+i指向数组的第i个元素a[i]。(2)a[i],﹡(p+i),﹡(a+i)均表示a数组的第i个元素的值。由于p+i,a+i均表示a[i]元素的地址,因此﹡(p+i),﹡(a+i)就代表的是元素a[i]的值。这种引用数组元素的方法一般也称为指针法。例9-10分别使用下标法和指针法输出数组中各元素的值/*exam9-10*/#include“stdio.h”main(){inta[10];inti,﹡p;for(i=0;i<10;i++)scanf(“%d”,&a[i]);printf(“\n”);for(i=0;i<10;i++)printf(“%d”,a[i]);/*利用数组下标法输出数组中的元素*/printf(“\n”);for(i=0;i<10;i++)printf(“%d”,﹡(a+i));/*利用指针法输出10个元素的值,﹡(a+i)等价于a[i]*/printf(“\n”);for(p=a;p<(a+10);p++)printf(“%d”,﹡p);/*利用指针法输出10个元素的值,通过指针变量p的移动*//*来依次输出数组中的10个元素*/} [演示]在此需要注意的是:(1)使用下标法和使用﹡(a+i)的执行效率是相同的,因为当使用下标法访问数组元素时,C语言的编译系统会将a[i]自动转换为﹡(a+i)进行处理,即每次都需要计算元素地址,因此在执行起来比较费时。而使用指针变量直接指向数组元素,然后通过p++的移动来访问各元素的方法则不需要计算地址,因此执行效率大大提高。(2)当通过指针法来访问数组元素时,注意不能使用a++来移动指针变量,因为a是数组名,它代表数组的首地址,是一个常量,因此无法实现自加操作。例9-11使用指针法输出数组中各元素的值/*exam9-11*/#include“stdio.h”main(){inta[10];inti,﹡p;for(p=a;p<(a+10);p++)scanf(“%d”,p);printf(“\n”);for(i=0;i<10;i++,p++)printf(“%d”,﹡p);}[演示]从程序运行结果可以发现最后输出的并不是我们期望的数组a的10个元素的值,而是毫无意义的一组数字。这是因为当程序执行完第一个for循环后,指针变量p指向的是数组a的最后一个元素a[9],而并非是第一个元素a[0]。因此在执行第二个for循环的时候,p++的值就超出了数组的范围,从而使指针变量p指向了一组不可预料的存储单元,导致最后输出结果是一组毫无意义的数字。要解决这个问题,我们只需要在程序执行完第一个for循环后,再让p指向数组a的第一个元素就可以了。见程序9-12。例9-12/*exam9-12*/#include“stdio.h”main(){inta[10];inti,﹡p;for(p=a;p<(a+10);p++)scanf(“%d”,p);

p=a;/*令指针变量p指向数组a的一个元素*/for(i=0;i<10;i++,p++)printf(“%d”,﹡p);}[演示]9.3.3数组名作函数参数我们在第7章“函数的调用”时曾讨论过,当使用数组名作函数的参数时,如果形参数组元素的值发生变化,实参数组元素的值也会随之变化。例如:例9-13利用数组名作函数参数实现对10个整数进行排序/*exam9-13*/#include“stdio.h”voidsort(intarray[]){ inti,j;intt,k;for(i=0;i<9;i++){k=i;for(j=i+1;j<10;j++){if(array[j]<array[k])k=j;} t=array[k];array[k]=array[i];array[i]=t;}}

main(){inta[10];inti;printf(“pleaseinput10numbers:\n”);for(i=0;i<10;i++)scanf(“%d”,&a[i]);printf(“\n”);sort(a);/*将数组名a作为实参传给函数sort进行排序*/printf(“thearrayhasbeensorted:\n”);for(i=0;i<10;i++)/*输出排序后的数组a中的各元素*/printf(“%d”,a[i]);}pleaseinput10numbers:9876543210↙thearrayhasbeensorted:0123456789程序运行结果:程序分析:之所以实参数组a的各元素值会随着形参数组array各元素值的改变而改变,主要是因为数组名代表的是数组的首地址,因此如果使用数组名作为实参,在调用函数时就会把数组的首地址传给形参,这样形参数组array和实参数组a就会共享同一段内存空间,所以当数组array中的元素值发生改变时,数组a中的各元素值也会随之改变。如果想通过函数调用来改变实参数组中的元素值,除了可以使形参和实参都采用数组名之外,还可以使用以下三种方法:1、函数实参用数组名,形参用指针变量2、函数实参用指针变量,形参用数组3、函数实参、形参均用指针变量[演示][演示][演示]9.3.4二维数组的指针和指向二维数组的指针变量1、二维数组的指针二维数组的指针(地址)相比一维数组的指针有着很大的不同。在C语言中,系统是将二维数组当作一种特殊的一维数组来进行处理的。例如: inta[2][3]={{1,2,3},{4,5,6}};这是一个两行三列的二维数组,C语言将该数组的每一行看作是一个一维数组,用数组名a[0]、a[1]来表示。这两个一维数组分别又含有三个元素(每一行的三个列元素),即第一行数组a[0]含有:a[0][0]、a[0][1]、a[0][2];第二行数组a[1]含有:a[1][0]、a[1][1]、a[1][2]。这样这两行一维数组就构成了一个二维数组。如图9-10所示。※注意:这里的a[0]、a[1]并不是实际存在的数组元素,它们代表的是每一行的数组名,而C语言又规定数组名代表数组首元素的地址,因此a[0]、a[1]分别代表第0行和第1行数组元素的首地址,即&a[0][0]和&a[0][1]除了可以使用每一行的一维数组名来代表该行的首地址之外,还可以使用二维数组名加下标的方法来表示各行元素的首地址。例如,二维数组名a代表第0行元素的首地址,那么a+1就代表第1行元素的首地址,a+2代表第2行元素的首地址,a+n代表第n行元素的首地址。同时,C语言又规定二维数组的行数组名a[0]与﹡(a+0)等价,a[1]与﹡(a+1)等价,因此二维数组的第i行元素的首地址还可以用﹡(a+i)来表示。※注意:由于a+i本身只是一个地址而不是一个实际的指针变量,所以﹡(a+i)并不代表指向a+i单元的值。请牢记,这里的﹡(a+i)只与一维数组名a[i]等价,只代表二维数组a的第i行元素的首地址。下面我们讨论如何表示每行数组中各列元素的地址。以二维数组a为例,由于已知第0行元素的首地址为a[0],因此可以用a[0]+1表示第0行第1列元素的地址,对于第i行第j列元素的地址,可以用a[i]+j进行表示。又因为第i行数组元素的首地址a[i]与﹡(a+i)等价,所以第i行第j列元素的地址也可以用﹡(a+i)+j表示。表9-1显示了二维数组各元素的地址、值的表示形式。例9-17采用不同的地址计算方法输出二维数组元素/*exam9-17*/#include“stdio.h”main(){inti,j;inta[2][3]={{1,2,3},{4,5,6}};for(i=0;i<2;i++){ for(j=0;j<3;j++){ printf(“%d”,a[i][j]);//使用数组下标法输出数组元素printf(“%d”,﹡(a[i]+j));//使用地址法输出数组元素 printf(“%d”,﹡(﹡(a+i)+j));//使用地址法输出数组元素} printf(“\n”);}}[演示]2、指向二维数组的指针变量使用指向二维数组的指针变量既可以顺序访问二维数组的元素值也可以访问某个指定二维数组的元素值。(1)使用指针变量顺序访问二维数组的元素值。先看一个例子:例9-18使用指针变量顺序输出二维数组元素/*exam9-18*/#include“stdio.h”main(){inta[2][3]={{1,2,3},{4,5,6}};int﹡p;for(p=a[0];p<a[0]+6;p++) printf(“%d”,﹡p);}123456程序运行结果为:在上例中,指针变量p首先指向二维数组a的第0行0列元素,然后通过p++使指针变量依次移动到下一个元素,从而实现对二维数组所有元素的顺序访问。(2)使用指针变量访问某个指定的二维数组元素程序例9-18的方法虽然可以顺序访问二维数组的元素,但无法访问某个指定的元素(例如a[2][1])。为了解决这个问题,C语言提供了一种可以指向一维数组的指针变量(二维数组就是由若干行一维数组构成的),使用这种指针变量可以方便地访问二维数组中的任意一个元素,该指针变量也称为二维数组的行指针变量。以下是行指针变量的定义形式:类型(﹡指针变量名)[n]说明:(1)()不能省略,如果省略了,指针变量就不再指向一维数组,而变成了一个指针数组。(2)n代表指向的一维数组所包含的元素数。例如,int(﹡p)[4]代表指针变量p指向一个含有4个元素的一维数组。(3)假设一个行指针变量p指向一个二维数组a,那么可以使用﹡(﹡(p+i)+j)来访问a的第i行第j列元素。(因为p指向a的第0行,﹡(p+i)就代表a的第i行数组的首地址,﹡(p+i)+j代表a的第i行第j列元素的地址,因此﹡(﹡(p+i)+j)就代表数组a的第i行第j列元素的值。)例9-19使用行指针变量输出二维数组的任意元素/*exam9-19*/#include“stdio.h”main(){inta[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};inti,j;int(﹡)p[4];scanf(“%d,%d”,&i,&j);//输入要输出的元素所在的行数和列数

p=a;

//p指向数组a的第0行printf(“a[%d][%d]=%d”,i,j,﹡(﹡(p+i)+j));//输出第i行第j列元素的值}2,3↙a[2][3]=12程序运行结果为:[Return]9.4 字符串指针9.4.1字符串的指针表示和引用 6.3节介绍过,一个非空的一维字符数组实际上是一个字符串,因此我们可以定义一个字符型的指针变量指向该字符数组,从而可以通过该指针变量对字符串进行引用。例如:

例9-20利用指针变量输出字符数组的内容/*exam9-20*/#include“stdio.h”main(){charstring[]=“Iamaboy”;char﹡str;str=string;/*指针变量str指向字符数组的首元素*/for(;﹡str!=’\0’;str++)/*逐个输出字符数组中的各个字符*/ printf(“%c”,﹡str);str=string;/*指针变量str指向string的首地址*/printf(“\n”);printf(“%s”,str);/*将字符数组内容一次性输出*/}IamaboyIamaboy程序运行结果为:从例9-20可以看出,当指针变量str指向字符数组string时(见图9-11),若要输出字符数组的内容可以采用两种方法:一种是通过指针变量的移动逐个输出字符数组的各字符;另一种方法是使用格式控制符“%s”将字符串内容一次性输出。

※注意:(1)如要逐个输出字符串中的字符时,printf函数的格式控制符使用“%c”。(2)如要一次性输出整个字符串时,printf函数的格式控制符使用“%s”,输出项使用指针变量名。(实际上系统首先输出指针变量所指向的字符,然后使指针变量逐步后移,依次输出剩余的字符,直至遇到字符串的结束标志’\0’为止)。(3)字符串在输出时,不包括结束字符’\0’。

字符型指针变量除了可以指向一维字符数组外,还可以直接指向一个字符串。例如:

char﹡str=”Iamaboy”; printf(“%s”,str);程序输出的结果是”Iamaboy”。在这里虽然没有定义字符数组,但C语言实际在内存中就是使用字符数组来存放字符串的,因此: char﹡str=”Iamaboy”;等价于:char﹡str;str=”Iamaboy”;该程序是将”Iamaboy”的首地址赋给指针变量str,然后通过格式控制符”%s”将字符串的内容一次性输出。※注意:由于a+i本身只是一个地址而不是一个实际的指针变量,所以﹡(a+i)并不代表指向a+i单元的值。请牢记,这里的﹡(a+i)只与一维数组名a[i]等价,只代表二维数组a的第i行元素的首地址。例9-21利用指针变量将字符串中的大写字母转换成小写字母/*exam9-21*/#include“stdio.h”main(){charstring[];char﹡str;scanf(“%s”,string);/*输入字符串内容*/str=string;for(;﹡str!=’\0’;str++)/*逐个输出字符数组中的各个字符*/{if(﹡str>=’A’&&﹡str<=’Z’)﹡str=﹡str+32;/*将字符串中的大写字母转换为小写字母*/}str=string;/*指针变量str重新指向string的首地址*/printf(“%s”,str);}aBcDEF↙abcdef程序运行结果为:9.4.2字符串指针作函数参数

和其他指针一样,字符串指针也可以作为函数参数,可以通过地址传递的方法将字符串从一个函数传递到另一个函数。在函数的调用过程中如果被调函数改变了字符串的内容,那么主调函数中字符串内容也会相应发生改变。如果函数形参为字符指针变量,那么主调函数可以使用数组名或指针变量名作为函数的实参:(1)函数形参使用指针变量,实参使用一维数组名。(2)函数形参实参均使用字符指针变量。[例9-22演示][例9-23演示][Return]9.5函数指针9.5.1指向函数的指针变量

由于函数本身也有地址,所以指针变量除了可以指向变量、字符串和数组外,还可以指向函数。函数是一组指令的集合,系统在编译函数时会将这些指令装入内存,C语言规定这些指令的首地址称为函数的入口地址,也称为函数的指针。因此,可以定义一个指针变量来存放某一个函数的指针,从而达到间接调用该函数的目的,这种指针变量也称为指向函数的指针变量。定义形式为:数据类型(﹡变量名)(参数类型列表)说明:(1)“数据类型”指的是指针变量所指函数的返回值类型。(2)(﹡变量名)的()不能省略。例如:(﹡p)()不能写成﹡p(),﹡p()代表的是一个返回指针类型的函数。(3)参数类型列表指的是定义指针变量时,指针变量的参数类型表必须和所指函数的形参列表一致,即形参的个数相同、类型相同。例如,有函数intfunction(inta,intb),在定义指向该函数的指针变量时,应当定义为:int(﹡p)(int,int)。(4)C语言规定函数名就代表函数的入口地址,因此给函数指针变量赋值时,只要将函数名赋给指针变量就可以了,而不必给出函数的参数。例如:intfuction(inta,intb){………}main(){int(﹡p)(int,int);/*定义指向函数的指针变量*/

p=function;

/*指针变量p指向函数function*/}9.5.2用函数指针变量调用函数当一个函数指针变量指向一个函数后,就可以使用这个指针变量来间接调用函数。下面先看一个普通函数调用的例子。

例9-25编写函数求两个整型数的和/*exam9-25*/#include“stdio.h”intadd(inta,intb){intc;c=a+b;return(c);}main(){intx,y,z;scanf(“%d,%d”,&x,&y);/*输入两个整数*/z=add(x,y);printf(“%d+%d=%d”,x,y,z);}本程序中,通过将x,y的值作为实参传递给add函数,从而完成对add函数的调用,这也是一般调用函数的方法。如果使用函数指针变量调用add函数,只需在函数调用时将函数名add改为(﹡变量名)即可。例如,将main函数改为: int(﹡p)(int,int); p=add;

z=(﹡p)(x,y);因此,使用指针变量调用函数时,除了使用(﹡变量名)代替函数名外,还应该根据需要加上函数的实参。另外需要注意的是,由于函数指针变量只能指向函数的入口地址而不能指向其他的任何地方,因此在使用函数指针变量时,不能够做类似于p++或p--的操作。例9-26使用函数指针变量调用函数找出任意两个数中的小数/*exam9-26*/#include“stdio.h”intmin(intm,intn){ints;if(m<n)s=m;elses=n;return(s);}main(){inta,b,c;int(﹡p)(int,int);printf(“Inputthenumber:\n”);scanf(“%d,%d”,&a,&b);p=min;c=(﹡p)(a,b);printf(“Theminimumnumberis:\n%d”,c);}Inputthenumber:9,6↙Theminimumnumberis:6程序运行结果:9.5.3用指向函数的指针作函数参数 指向函数的指针最常见的用途就是将该指针作为函数的参数传递给被调函数,通过函数地址值的传递,实现被调函数可以根据不同的情况来调用不同的函数,从而增强程序的灵活性。其一般的使用方法为:假设有两个普通函数:intfun1(intx1,inty1)、intfun2(intx2),现定义一个形参为函数指针的函数来完成对fun1和fun2的调用。例如:

intfun1(intx1,inty1){ ……}intfun2(intx2){ ……}fun(int(﹡p1)(int,int),int(﹡p2)(int)){ inta,b;

a=(﹡p1)(5,6);

}main(){

fun(fun1,fun2);}在main()函数中,程序将函数fun1和fun2的入口地址传给了函数fun的两个参数p1和p2,这样p1、p2就分别指向了函数fun1和fun2(见图9-13),这也意味着就可以使用函数指针p1和p2间接访问函数fun1和fun2了。在这里需要注意的是,如果main函数放在了函数fun1和fun2的上面,那么在main函数调用fun(fun1,fun2)之前需要对实参函数fun1和fun2进行声明,即:

main() { intfun1(int,int); intfun2(int); fun(fun1,fun2); }因此,由上可以看出使用含有函数指针参数的函数最大的好处就是可以灵活地调用函数,特别是当经常调用的函数是不固定的时候。例如,假设程序第一次需要调用fun1和fun2函数,第二次需要调用fun3和fun4函数,这时如果使用传统的直接调用函数的方法就会需要每次都得修改fun函数,这样就显得十分繁琐。但是如果使用函数指针变量来调用这些函数,那么每次只要赋给fun函数不同的实参值就可以了,而不必修改fun函数本身。[Return]9.6 返回指针值的函数函数的返回值可以有多种类型,除了此前学习的整型、实型和字符型以外,函数还可以返回指针型数据,即返回的数据是一个地址。返回值为指针类型的函数的定义形式一般如下:

类型﹡函数名(形参列表)

{

…… }说明:(1)类型指的是返回的指针指向的是什么类型的数据。(2)函数名前的﹡代表的是该函数返回值是一个指针数据,即调用该函数的程序可以得到一个地址。例如: int﹡fun(inta) {

…… }代表定义了一个函数,该函数的返回值是一个指向整型数据的地址。

[例9-28]根据例9-28可以看出,main函数在调用fun函数的时候,首先将字符串string的首地址赋给fun函数的形参﹡str,然后通过对str的操作将字符串中的大写字母转换成小写字母,最后将转换好的字符串首地址返回给主调函数main,因此最后输出的s所指的数据就是已经转换好的字符串string。【思考】在fun函数中是否有必要定义指向形参str的字符型指针变量p?[例9-29]※注意:函数返回的指针不能指向函数返回后便不存在的变量。例如: int﹡f(inta) { return&a; }这个函数返回的结果是非法的,主要因为变量a为形参变量,该变量的生存周期是函数f的运行阶段,即当f执行完后,形参变量a也会随之被释放,因此主调函数所获得的返回值是不可靠的。9.7 指针数组和指向指针的指针9.7.1指针数组 普通数组元素除了可以存储整型、实型和字符型数据外,还可以存放指针型数据。如果某种数组存放的所有元素都是指向同一类型的指针变量,则称该数组为指针数组。指针数组的一般定义形式为:

类型标识符﹡数组名[长度]例如:

int﹡p[3];该语句定义了一个长度为3的指针数组,每个指针元素都可以指向整型变量。除此之外,C语言还允许在定义指针数组的同时就可以完成对数组的初始化操作。例如:

char﹡str[3]={“cat”,“dog”,“pig”};该语句定义了指针数组str,并且数组的3个元素分别指向3个字符串常量,即str的三个元素分别存放了三个字符串(字符串在内存中的存储形式是一维字符数组)的首地址。如图9-14。※注意:在定义指针数组时,一定不能将int﹡p[3]写成int(﹡p)[3],因为int(﹡p)[3]代表的是指向一维数组的指针变量。

指针数组最常被用于处理多个字符串,虽然使用普通二维字符数组也可以存放和管理多个字符串(由于每个字符串都是一个一维字符数组,因此需要使用二维数组来存储多个字符串),但是在效率方面要比使用指针数组低的多。主要理由如下:(1)使用二维数组存放多个字符串时,由于二维护数组在定义时列数就是固定的,即每一行数组的元素个数相同,因此往往使用最长长度来定义二维数组的列数。而实际上每个字符串的长度可能是不等的,这样就会造成内存空间的浪费,例如:charstr[3][9]={“dog”,“duck”,“elephant”}在内存中的存储形式如图9-15所示;而字符指针数组的各个指针元素只是指向各个一维字符数组,指针数组的长度与字符串的长度无关,因此不存在内存浪费的问题。(2)使用二维数组来处理多个字符串时,特别当涉及字符串的交换、复制和排序等操作时,需要将字符串整个进行移动;而使用字符指针数组来处理这些操作时,不必改变各字符串在内存中的位置,只需改动指针数组中各元素的指向就可以了。因此使用字符指针数组来处理多个字符串的效率要比二维字符数组高得多。例9-30编写程序用来找出4个字符串中按字母排序最大的字符串/*exam9-30*/#include“stdio.h”#include“string.h”main(){ char﹡str[4]={“bee”,“dog”,“pig”,“elephant”}; inti,j,k; char﹡s; for(i=0;i<3;i++) { k=i; for(j=i+1;j<4;j++) { if(strcmp(str[k],str[j])>0)k=j; } if(k!

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论