计算机二级C语言程序设计第9讲.ppt
第9讲 指 针,指针是语言中的重要概念,也是语言的重要特色。使用指针,可以使程序更加简洁、紧凑、高效。9.1 指针和指针变量的概念9.2 指针变量的定义与应用9.3 数组的指针和指向数组的指针变量 9.4 字符串的指针和指向字符串的指针变量9.5 返回指针值的函数 9.6 指针数组与主函数main()的形参 9.7 函数的指针和指向函数的指针变量,9.1 指针和指针变量的概念,1.内存地址内存中存储单元的编号1)计算机硬件系统的内存储器中,拥有大量的存储单元(容量为字节)。为了方便管理,必须为每一个存储单元编号,这个编号就是存储单元的“地址”。每个存储单元都有一个惟一的地址。2)在地址所标识的存储单元中存放数据。注意:内存单元的地址与内存单元中的数据是两个完全不同的概念。,例:int a;a=12;变量 a 内的“内容”(变量值)是12;变量值可由键盘等方法录入a变量的地址&a,是3000,变量的地址&a是编译时系统统一编码赋予的。,2.变量地址系统分配给变量的内存单元的起始地址假设有这样一个程序:main()int num;scanf(%d,”时,存取变量num值的方式可以有两种:,(1)直接访问直接利用变量的地址进行存取 1)上例中scanf(“%d”,&num)的执行过程是这样的:用变量名num作为索引值,检索符号表,找到变量num的起始地址3000;然后将键盘输入的值(假设为12)送到内存单元3000至3003中。此时,变量num在内存中的地址和值,如图9-1所示。2)printf(num=%dn,num)的执行过程,与scanf()很相似:首先找到变量num的起始地址3000,然后从3000至3003中取出其值,最后将它输出。(2)间接访问通过另一变量访问该变量的值 语言规定:在程序中可以定义一种特殊的变量(称为指针变量),用来存放其它变量的地址。,例如,假设定义了这样一个指针变量p,它被分配到4000至4003单元,其值可通过赋值语句“p=num;”得到。此时,指针变量p的值就是变量num在内存中的起始地址3000,如图9-2所示(见下一页)。通过指针变量p存取变量num值的过程如下:首先找到指针变量p的地址(4000),取出其值3000(正好是变量num 的起始地址);然后从3000至3003中取出变量num的值(3)。(3)两种访问方式的比较 两种访问方式之间的关系,可以用某人甲(系统)要找某人乙(变量)来类比。一种情况是,甲知道乙在何处,直接去找乙就是(即直接访问)。另一种情况是,甲不知道乙在哪,但丙(指针变量)知道,此时甲可以这么做:先找丙,从丙处获得乙的地址,然后再找乙(即间接访问)。,main()int num=12;int*p;/*p是专门用于存放其他变量(num)地址的变量,(存放整型变量地址)*/p=),p=num的首地址是3000,将num地址装入了指针变量 p,4000 4003,3000,12,p,num,3000 3003图9-2,p=num的首地址是3000p指向了(首地址为3000)变量num,4.指针与指针变量(1)指针即地址(指针变量的值,称为指针,这个值是其他变量的地址)一个变量的地址称为该变量的指针。通过变量的指针能够找到该变量。(2)指针变量专门用于存储其它变量地址的变量指针变量p的值就是变量num的地址。指针与指针变量的区别/关系,就是变量值与变量(名)的区别/关系。(3)为表示指针变量和它指向的变量之间的关系,用指针运算符“*”表示。例如,指针变量p与它所指向的变量num的关系,(在上例中)表示为:*p,即*p等价于变量num。而p等价于变量/*将3赋给指针变量p所指向的变量*/Return,指针变量的概念 与 小 结:指针:变量a的地址,称为变量a的指针;即1.对指针变量,定义语句中各符号的说明 类型符号“*”:说明p 是指针变量 基类型符号 int:说明p 存储整型变量的地址。(基类型,用于指针运算时的步长单位,稍后可见)变量名:p(不要用“*P”作变量名,符号“*”只是类型说明)。,2.对指针变量,引用语句中各符号的说明 常用运算符号:printf(“%d”,*p)/*引用*/3.对于指针变量的符号“*”,出现在不同位置,其作用不同(1)定义中:“*”说明所定义的变量是指针变量(2)引用中:“*”是指针运算符,取指针变量指向的变量的数值,9.2 指针变量的定义与应用,9.2.1 指针变量的定义与相关运算案例9.1 指针变量的定义与相关运算示例。/*案例代码文件名:AL9_1.C*/#include main()int num_int=12,*p_int;/*定义一个指向int型数据的指针变量p_int*/float num_f=3.14,*p_f;/*定义一个指向float型数据的指针变量p_f*/char num_ch=p,*p_ch;/*定义一个指向char型数据的指针变量p_ch*/p_int=AL9_1.C 程序演示,3000,12,p_int,*p_int,3000 3003图9-2,num_int,4000 4003,程序运行结果:num_int=12,*p_int=12num_f=3.14,*p_f=3.14num_ch=p,*p_ch=p 程序说明:(1)头三行的变量定义语句指针变量的定义 与一般变量的定义相比,除变量名前多了一个星号“*”(指针变量的定义标识符)外,其余一样:基(数据)类型*指针变量,*指针变量2;注意:此时的指针变量p_int、p_f、p_ch,并未指向某个具体的变量(称指针是悬空的)。使用悬空指针很容易破坏系统,导致系统瘫痪。,(2)中间三行的赋值语句取地址运算()取地址运算的格式:变量例如,&num_int、&num_f、&num_ch的结果,分别为变量num_int、num_f、num_ch的地址。注意:指针变量只能存放指针(地址),且只能是相同类型变量的地址。例如,指针变量p_int、p_f、p_ch,只能分别接收int型、float型、char型变量的地址,否则出错。(3)后三行的输出语句指针运算(*)使用直接访问和间接访问两种方式,分别输出变量num_int、num_f、num_ch的值。注意:这三行出现在指针变量前的星号“*”是指针运算符,访问指针变量所指向的变量的值,而非指针定义标识符。,理解:如果已定义point1是指向变量a的指针变量,(int a=12,*point1;point1=3、(*point1)+:即a+*point1+:等价*(point1+),案例9.2 使用指针变量求解:输入2个整数,按升序(从小到大排序)输出。/*案例代码文件名:AL9_2.C*/main()int num1,num2;int*num1_p=AL9_2.C 程序演示,程序运行情况:Input the first number:9Input the second number:6num1=9,num2=6min=6,max=9num1=9,num2=6程序说明:(1)第5行的if语句 如果*num1_p*num2_p(即num1num2),则交换指针,使num1_p指向变量num2(较小值),num2_p指向变量num1(较大值)。(2)printf(“min=%d,max=%dn”,*num1_p,*num2_p);语句:通过指针变量,间接访问变量的值。,9.2.2 指针变量作函数参数1.指针变量,既可以作为函数的形参,也可以作函数的实参。2.指针变量作实参时,与普通变量一样,也是“值传递”,即将指针变量的值(一个地址)传递给被调用函数的形参(必须是一个指针变量)。注意:被调用函数不能改变实参指针变量的值,但可以改变实参指针变量所指向的变量的值。案例9.3 使用函数调用方式改写案例9.2,要求实参为指针变量。/*案例代码文件名:AL9_3.C*/*/*exchange()功能:交换2个形参指针变量所指向的变量的值*/*形参:2个,均为指向整型数据的指针变量*/*返回值:无*/*/,void exchange(int*pointer1,int*pointer2)int temp;temp=*pointer1,*pointer1=*pointer2,*pointer2=temp;/*主函数main()*/main()int num1,num2;int*num1_p=AL9_3.C 程序演示,案例9.3 与案例9.2的主要区别是 通过函数exchange()的调用,交换2个形参指针变量所指向的变量的值。功能:/*在函数exchange()的调用中,实参把地址传给形参,形参指针变量所指向的(变量)存储单元地址与实参指针变量所指向的(变量)存储单元地址是同一单元。其效果是:交换2个形参指针变量所指向的变量的值,实参指针变量所指向的变量的值 也随之交换。原因是:实参、形参指向的是同一地址单元,num2_p,&num 2,pointer2,&num 2,num2,6,num1_p,&num 1,pointer1,&num 1,num 1,8,函数的调用时,把实参指针变量的值(地址),传给了形参指针变量,形、实二者指向的是同一地址单元:,-图 9-5-,调用函数exchange()之前、之时、结束时和结束后的情况,如图所示(见前一页)。形参指针变量pointer1(指向变量num1)和pointer2(指向变量num2),在函数调用开始时才分配存储空间,函数调用结束后立即被释放。虽然被调用函数不能改变实参指针变量的值,但可以改变它们所指向的变量的值。,9.3 数组的指针和指向数组的指针变量,9.3.1 概述1.概念数组的指针数组在内存中的起始地址,数组元素的指针数组元素在内存中的起始地址。2.指向数组的指针变量的定义指向数组的指针变量的定义,与指向普通变量的指针变量的定义方法一样。例如,int array10,*pointer=array(或注意:数组名代表数组在内存中的起始地址(与第1个元素的地址相同),所以可以用数组名给指针变量赋值。,3.数组元素的引用数组元素的引用,既可用下标法,也可用指针法。使用下标法,直观;而使用指针法,能使目标程序占用内存少、运行速度快。9.3.2 通过指针引用数组元素如果有“int a 10,*p=a;”,则:(1)p+i和a+i都是数组元素a i的地址,如图9-6(见下一页)所示。实际地址为p+i*d,d为每个数组元素所占的字节数。(2)*(p+i)和*(a+i)就是数组元素a i。实际上,在编译时,对数组元素a i就是处理成*(a+i)。(3)指向数组的指针变量,也可将其看作是数组名,因而可按下标法来使用。例如,p i等价于*(p+i)。注意:p+1指向数组的下一个元素,而不是简单地使指针变量p的值+1。其实际变化为p+1*size(size为一个元素占用的字节数)。例如,假设指针变量p的当前值为1000,则p+1为1000+1*4=1004,而不是1001。如图9-6(见下一页)所示,元素起始地址a0 1000a1 1004a2 1008a3 1012a i 1016 a5 1020a6 1024a7 1028a8 1032a9 1036,a 数组,如果有“int a 10,*p=a;”,则:(1)p+i和a+i都是数组元素a i的地址,如图所示。实际地址为p+i*d,d为每个数组元素所占的字节数。(2)*(p+i)和*(a+i)就是数组元素a i。实际上,在编译时,对数组元素a i就是处理成*(a+i)。(3)指向数组的指针变量,也可将其看作是数组名,因而可按下标法来使用。例如,p i等价于*(p+i)。(有条件:p=a;)等价元素a i。,p 1000,p+1,a+1,图9-6,案例9.5 使用指向数组的指针变量来引用数组元素。/*案例代码文件名:AL9_5.C*/*程序功能:使用指向数组的指针变量来引用数组元素*/#include main()int array10,*p=array,i;printf(“Input 10 numbers:”);for(i=0;i10;i+)scanf(“%d”,p+i);/*使用指针变量来输入数组元素的值*/printf(“array10:”);for(i=0;i10;i+)printf(“%d”,*(p+i);/*使用指向数组的指针变量输出数组元素的值*/printf(“n”);AL9_5.C 程序演示程序运行情况:Input 10 numbers:0 1 2 3 4 5 6 7 8 9array10:0 1 2 3 4 5 6 7 8 9,说明:(1)指针变量的值是可以改变的,所以必须注意其当前值,否则容易出错。(2)指向数组的指针变量,可以指向数组以后的内存单元,虽然没有实际意义。(3)对指向数组的指针变量(px和py)进行算术运算和关系运算的含义 1)可以进行的算术运算,只有以下几种:pxn,px+/+px,px-/-px,px-py pxn:将指针从当前位置向前(+n)或回退(-n)n个数据单位,而不是n个字节。显然,px+/+px和px-/-px是pxn的特例(n=1)。px-py:两指针变量的值之差,其意义是两指针之间的数据(数组元素的)个数,而不是指针的地址之差。,思考题:若有以下定义和语句,且0i10,则对数组元素的错误引用是:int a=1,2,3,4,5,6,7,8,9,10,int*p=a,i;A*(a+i)B ap-a C p+i D*(&ai),2)关系运算 表示两个指针所指地址之间、位置的前后关系:前者为小,后者为大。例如,如果指针px所指地址在指针py所指地址之前,则px py的值为1。9.3.3 再论数组名作函数参数数组名作形参时,接收实参数组的起始地址;作实参时,将数组的起始地址传递给形参数组。引入指向数组的指针变量后,数组及指向数组的指针变量作函数参数时,可有种等价形式(本质上是一种,即指针数据作函数参数):(1)形参、实参都用数组名(2)形参、实参都用指针变量(3)形参用指针变量、实参用数组名(4)形参用数组名、实参用指针变量,2007年4月,1、有以下程序 void f(int*q)int i=0;for(;i5;i+)(*q)+;main()int a5=1,2,3,4,5,i;f(a);for(i=0;i5;i+)printf(“%d,”,ai);程序运行后的输出结果是()A)2,2,3,4,5,B)6,2,3,4,5,C)1,2,3,4,5,D)2,3,4,5,6,2007年4月,2、以下程序的输出结果是 _ int fun(int*x,int n)if(n=0)return x0;else return x0+fun(x+1,n-1);main()int a=1,2,3,4,5,6,7;printf(“%dn”,fun(a,3);,10,2007年9月,1、若在定义语句:inta,b,c,*p=,2007年9月,2、有以下程序#includevoidfun(int*a,intn)/*函数功能是将a所指数组元素从大到小排序*/intt,i,j;for(i=0;in-1;i+)for(j=i+1;jn;j+)if(aiaj)t=ai;ai=aj;aj=t;main()intc10=1,2,3,4,5,6,7,8,9,0,i;fun(c+4,6);for(i=0;i10;i+)printf(%d,ci);printf(n);程序运行的结果是A)1,2,3,4,5,6,7,8,9,0,B)0,9,8,7,6,5,1,2,3,4,C)0,9,8,7,6,5,4,3,2,1,D)1,2,3,4,9,8,7,6,5,0,2007年9月,3、有以下程序#includevoidfun(intn,int*p)intf1,f2;if(n=1|n=2)*p=1;else fun(n-1,程序的运行结果是A)2 B)3 C)4 D)5,2008年4月,1、以下定义语句中正确的是A)int a=b=0;B)char A=65+1,b=b;C)float a=1,*b=程序运行的结果是A)y=0B)y=1C)y=2D)y=3,2008年4月,3、有以下程序#includevoidfun(int*s,int n1,int n2)inti,j,t;i=n1;j=n2;while(ij)t=si;si=sj;sj=t;i+;j-;main()inta10=1,2,3,4,5,6,7,8,9,0,k;fun(a,0,3);fun(a,4,9);fun(a,0,9);for(k=0;k10;k+)printf(%d,ak);printf(n);程序运行得结果是 A)0987654321B)4321098765 C)5678901234D)0987651234,2008年4月,4、以下程序的输出结果是【3 5】。#includevoidswap(int*a,int*b)int*t;t=a;a=b;b=t;main()int i=3,j=5,*p=,2008年4月,5、以下程序的输出结果是【4】。#includemain()inta5=2,4,6,8,10,*p;p=a;p+;printf(%d,*p);,2008年9月,1、若有定义语句:doublex5=1.0,2.0,3.0,4.0,5.0,*p=x;则错误引用x数组元素的是A)*pB)x5C)*(p+1)D)*x2、以下程序的输出结果是【9】#includemain()int j,a=1,3,5,7,9,11,13,15,*p=a+5;for(j=3;j;j-)switch(j)case 1:case 2:printf(%d,*p+);break;case 3:printf(%d,*(-p);,9 9 11,2008年9月,3、以下程序的输出结果是【10】#include#defineN5intfun(int*s,inta,intn)intj;*s=a;j=n;while(a!=sj)j-;returnj;main()intsN+1;intk;for(k=1;k=N;k+)sk=k+1;printf(%dn,fun(s,4,N);,3,2009年3月,1、若有定义语句:double x,y,*px,*py;执行了px=,2009年3月,2、有以下程序#includevoidfun(int*a,int*b)int*c;c=a;a=b;b=c;main()intx=3,y=5,*p=程序运行后输出的结果是A)3,5,5,3 B)3,5,3,5 C)5,3,3,5 D)5,3,5,3,2009年3月,3、有以下程序#includevoidf(int*p,int*q);main()intm=1,n=2,*r=程序运行后的输出结果是A)1,3 B)2,3 C)1,4 D)1,2,2009年3月,4、以下函数按每行8个输出数组中的数据#includevoidfun(int*w,intn)inti;for(i=0;in;i+)_ printf(%d,wi);printf(n);下划线出应填入的语句是A)if(i/8=0)printf(“n”);B)if(i/8=0)continue;C)if(i%8=0)printf(n);D)if(i%8=0)continue;,2009年3月,5、若有以下定义int x10,*pt=x;则对数组元素的正确引用是A)*&x10 B)*(x+3)C)*(pt+10)D)pt+3,2009年3月,6、有以下程序#includeintb=2;intfun(int*k)b=*k+b;return(b);main()inta10=1,2,3,4,5,6,7,8,i;for(i=2;i4;i+)b=fun(程序运行后的输出结果是A)10 12 B)8 10 C)10 28 D)10 16,2009年9月,1、有以下程序#includemain()intm=1,n=2,*p=程序运行后的输出结果是A)1,2,1,2 B)1,2,2,1 C)2,1,2,1 D)2,1,1,2,9.3.4 2维数组的指针及其指针变量 1.2维数组的指针 假设有如下数组定义语句:int a34=1,3,5,7,9,11,13,15,17,19,21,23;(1)从2维数组角度看,数组名a代表数组的起始地址,是一个以行为单位进行控制的行指针:a+i:行指针值,指向2维数组的第i行。*(a+i):等价于ai,(列)指针值,指向第i行第列(控制由行转为列,但仍为指针)。*(*(a+i):数组元素ai0的值。用a作指针访问数组元素aij的格式:*(*(a+i)j)注意:行指针是一个级指针,如图所示。(2)从1维数组角度看,数组名a和第1维下标的每一个值,共同构成一组新的1维数组名a0、a1、a2,它们均由4个元素组成。,a0,a0+1,a0+3,语言规定:数组名代表数组的地址,所以ai是第i行1维数组的地址,它指向该行的第0列元素,是一个以数组元素为单位进行控制的列指针:ai+j:(列)指针值,指向数组元素aij。*(ai+j):数组元素aij的值。如果有“int a34,*p=a0;”,则p+1指向下一个元素,如图所示。用p作指针访问数组元素aij的格式:*(p+(*每行列数+j)2.行指针变量指向由n个元素组成的一维数组的指针变量(1)定义格式 数据类型(*指针变量)n;注意:“*指针变量”外的括号不能缺,否则成了指针数组数组的每个元素都是一个指针指针数组(本章第6节介绍)。(2)赋值 行指针变量 2维数组名|行指针变量;,案例9.6 使用行指针和列指针两种方式输出2维数组的任一元素。(1)使用行指针/*案例代码文件名:AL9_6_1.C*/*程序功能:使用行指针输出2维数组的任一元素*/main()int a34=1,2,3,4,5,6,7,8,9,10,11,12;int(*pointer)4,row,col;pointer=a;printf(“Input row=”);scanf(“%d”,程序运行情况:Input row=1 Input col=2 array12=7思考题:本题也可以直接使用数组名a作指针,应如何修改?,(2)使用列指针/*案例代码文件名:AL9_6_2.C*/*程序功能:使用列指针输出2维数组的任一元素*/main()int a34=1,2,3,4,5,6,7,8,9,10,11,12;int*pointer,row,col;/*定义一个(列)指针变量pointer*/pointer=a0;/*给(列)指针变量pointer赋值*/printf(“Input row=”);scanf(“%d”,程序演示,3.2维数组指针作函数参数 一维数组的地址可以作为函数参数传递,多维数组的地址也可以作为函数参数传递。在用指针变量作形参以接受实参数组名传递来的地址时,有两种方法:用指向变量的指针变量 用指向一维数组的指针变量。,2008年9月,1、有以下程序#includeintfun(int(*s)4,int n,int k)int m,i;m=s0k;for(i=1;im)m=sik;returnm;main()int a44=1,2,3,4,11,12,13,14,21,22,23,24,31,32,33,34;printf(%dn,fun(a,4,0);程序的运行结果是A)4B)34C)31D)32,9.3.5 动态数组的实现在程序运行过程中,数组的大小是不能改变的。这种数组称为静态数组。静态数组的缺点是:对于事先无法准确估计数据量的情况,无法做到既满足处理需要,又不浪费内存空间。所谓动态数组是指,在程序运行过程中,根据实际需要指定数组的大小。在C语言中,可利用内存的申请和释放库函数,以及指向数组的指针变量可当数组名使用的特点,来实现动态数组。,动态数组的本质是:一个指向数组的指针变量。案例9.7 动态数组的实现。/*案例代码文件名:AL9_7.C*/*程序功能:实现动态数组*/#include#include main()int*array=NULL,num,i;printf(“Input the number of element:”);scanf(“%d”,/*exit():终止程序运行,返回操作系统*/,/*提示输入num个数据*/printf(“Input%d elements:”,num);for(i=0;inum;i+)scanf(“%d”,/*释放由malloc()函数申请的内存块*/程序演示程序运行情况:Input the number of element:3 Input 3 elements:1 2 3 3 elements are:1,2,3,程序说明:(1)array=(int*)malloc(sizeof(int)*num);语句malloc()函数和sizeof运算符1)库函数malloc()用法:void*malloc(unsigned size)功能:在内存的动态存储区分配个长度为size的连续空间。返回值:申请成功,则返回新分配内存块的起始地址;否则,返回NULL。函数原型:stdlib.h。malloc()函数的返回值是一个无类型指针,其特点是可以指向任何类型的数据。但在实际使用malloc()函数时,必须将其返回值强制转换成被赋值指针变量的数据类型,以免出错。2)运算符sizeof格式:sizeof(变量名类型名)功能:求变量类型占用的内存字节数(正整数)。例如,在32位编译系统中,sizeof(int)=4。,思考题:在该语句中,使用sizeof(int)求出1个int型数据占用的内存字节数,而不是使用常量“4”,为什么?(2)scanf(“%d”,语句“b”在该语句中的作用是,使光标定位到最后一个数据后的分隔符“,”上,然后再输出一个空格,以达到删除之目的。,(4)free(array);语句库函数free()用法:void free(void*ptr)功能:释放由ptr指向的内存块(ptr是调用malloc()函数的返回值)。返回值:无。函数原型:stdlib.h。原则上,使用malloc()函数申请的内存块,操作结束后,应及时使用free()函数予以释放。尤其是循环使用malloc()函数时,如果不及时释放不再使用的内存块,很可能很快就耗尽系统的内存资源,从而导致程序无法继续运行。Return,2008年9月,1、有以下程序#include#includeintfun(intn)int*p;p=(int*)malloc(sizeof(int);*p=n;return*p;main()inta;a=fun(10);printf(%dn,a+fun(10);程序的运行结果是:A)0B)10C)20D)出错,