C语言程序设计教程(三)231_第1页
C语言程序设计教程(三)231_第2页
C语言程序设计教程(三)231_第3页
C语言程序设计教程(三)231_第4页
C语言程序设计教程(三)231_第5页
已阅读5页,还剩226页未读 继续免费阅读

下载本文档

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

文档简介

C语言程序设计教程(三)第8章指针第9章复合数据类型第8章指针C语言的重要特性之一就是指针操作。利用指针可以直接而快速的处理内存中各种数据结构的数据,特别是数组、字符串、内存的动态分配等,并能象汇编语言一样处理内存地址。它为函数间各类数据的传递提供了简捷便利的方法。指针使C程序简洁、紧凑、高效,但指针使用上的灵活性容易导致指针滥用而可能使程序失控。本章主要介绍如下内容:指针的概念和运算、指针和数组、指针函数和函数指针、多级指针以及指针的实例举例。8.1指针根本概念及其指针变量的声明在C语言中,对于变量的访问形式之一,就是先求出变量的地址,然后再通过地址对它进行访问,这就是本节所要论述的指针及其指针变量。8.1.1指针、变量的指针和指针变量例8-1指针及其指针变量应用举例。#include<stdio.h>voidmain(){ inti=20,x; int*ip; ip=&i; x=*ip; printf("i'saddressis:%d\n",&i); printf("ip'saddressis:%d\n",ip); printf("i'sdatais:%d\n",i); printf("ip'sdatais:%d\n",*ip); printf("x=%d\n",x);}8.1.1指针、变量的指针和指针变量(续)运行结果:i'saddressis:1310588ip'saddressis:1310588i'sdatais:20ip'sdatais:20x=20现在我们对例8-1进行代码分析,逐步深入了解什么是指针及其指针变量的应用意义。inti=20,x;int*ip;声明了两个整型变量i,x,并对整型变量i进行初始化,赋初值为20,同时还声明了一个指向整型数的指针变量ip。i,x中可存放整数,而ip中只能存放整型变量的地址。我们把i的地址赋给ip,即:ip=&i;8.1.1指针、变量的指针和指针变量(续)此时指针变量ip指向整型变量i,由运行结果可知变量i的地址为1310588〔该地址是在64位计算机上运行得出的结果,不同的计算机得出的地址不同〕,这个赋值可形象理解为图8-1所示的联系。1310588变量i指针ip131058820图8-1给指针变量赋值8.1.1指针、变量的指针和指针变量(续)以后我们便可以通过指针变量ip间接访问变量i,例如:x=*ip;运算符*访问以ip为地址的存贮区域,而ip中存放的是变量i的地址。因此,*ip访问的是地址1310588的存贮区域(因为是整数,实际上是从1310588开始的两个字节),它就是i所占用的存贮区域,所以上面的赋值表达式等价于:x=i;所谓变量的指针,实际上指变量的地址。变量的地址虽然在形式上好似类似于整数,但在概念上不同于以前介绍过的整数,它属于一种新的数据类型,即指针类型。8.1.1指针、变量的指针和指针变量(续)在C语言中,一般用“指针〞来指明这样一个表达式:&x的类型,而用“地址〞作为&x的值。也就是说,假设x为一整型变量,那么表达式&x的类型是指向整数的指针,而&x的值是变量x的地址。同样,假设double

d;那么&d的类型是指向双精度数d的指针,而&d的值是双精度变量d的地址。严格地说,一个指针就是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。为了防止混淆,我们约定:“指针〞是指地址,是常量;“指针变量〞是指取值为地址的变量。声明指针的目的是为了通过指针去访问内存单元。8.1.2指针的声明在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。下面介绍两个与地址有关的运算符:&:取地址运算符。*:指针运算符〔或称“间接访问〞运算符〕。①C语言中提供了地址运算符&来表示变量的地址。其一般形式为:&变量名或数组元素名;如:&a表示变量a的地址,&b[2]表示数组元素b[2]的地址。变量本身必须预先说明。设有指向整型变量的指针变量p,如要把整型变量a的地址赋予p可以有以下两种方式:8.1.2指针的声明(续)1.指针变量初始化的方法inta;int*p=&a;2.赋值语句的方法inta;int*p;p=&a;②C语言中另一种与地址有关的运算符是*,其一般形式为:*指针变量名或目标变量地址;*运算符要求运算量是地址,含义为访问指定地址的目标变量。8.1.2指针的声明(续)例如:&(*px)含义为取指针px的目标变量的地址,就是px。*(&x)含义为访问变量x的地址指向的目标变量,就是x。可见,&运算和*运算互为逆运算。指针变量同普通变量一样,使用之前不仅要声明说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否那么将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否那么将引起错误。指针变量的一般声明为:类型标识符*标识符;其中标识符是指针变量的名字,标识符前加了"*"号,表示该变量是指针变量,而最前面的“类型标识符〞表示该指针变量所指向的变量的类型。8.1.2指针的声明(续)对于外部或静态指针变量在声明中假设不带初始化项,指针变量被初始化为NULL,它的值为0。在C语言中规定,当指针值为零时,指针不指向任何有效数据,有时也称该指针为空指针。因此,当调用一个要返回指针的函数时(以后会讲到),常使用返回值为NULL来指示函数调用中某些错误情况的发生。既然在指针变量中只能存放地址,那么在使用中就不能将一个整数值赋给一个指针变量。下面的赋值是不合法的:

int*ip;ip=100;另外,指针变量和一般变量一样,存放在它们之中的值是可以改变的,也就是说可以改变它们的指向。8.1.2指针的声明(续)例8-2指针应用举例。#include<stdio.h>voidmain(){ boolFlag=true; charn,m,*p1,*p2; n='a'; m='b'; p1=&n; p2=&m; printf("n=%c,m=%c,p1=%c,p2=%c\n",n,m,*p1,*p2);8.1.2指针的声明(续) if(Flag) { p2=p1; printf("p2=p1=>n=%c,m=%c,p2=%c,p1=%c\n",n,m,*p2,*p1); } if(!Flag) { *p2=*p1; printf("*p2=*p1=>n=%c,m=%c,p2=%c,p1=%c\n",n,m,*p2,*p1); }}8.1.2指针的声明(续)由上述程序描述,建立如图8-2所示的联系:‘a’p2m‘b’p1n图8-2赋值运算结果8.1.2指针的声明(续)①当Flag=true时,这时赋值表达式:

p2=p1;‘a’p2m‘b’p1n图8-3p2=p1时的情形就使p2与p1指向同一对象n,此时*p2就等于n,而不是m,图8-2.就变成图8-3所示:‘a’p2m‘b’p1n图8-3p2=p1时的情形8.1.2指针的声明(续)运行结果:n=a,m=b,p1=a,p2=bp2=p1=>n=a,m=b,p2=a,p1=a②当Flag=false时,执行如下表达式:*p2=*p1;那么表示把p1指向的内容赋给p2所指的区域,此时图8-2就变成图8-4所示

:‘a’p2m‘a’p1n图8-4*p2=*p1时的情形8.1.2指针的声明(续)运行结果:n=a,m=b,p1=a,p2=b*p2=*p1=>n=a,m=a,p2=a,p1=a通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观。因为通过指针要访问哪一个变量,取决于指针的值(即指向),例如:"*p2=*p1;",实际上就是"m=n;",前者不仅速度慢而且目的不明确。但由于指针是变量,我们可以通过改变它们的指向,用以间接访问不同的变量,这就使程序代码编写得更为简洁、有效。指针变量也可以出现在表达式中。8.1.2指针的声明(续)例如:intx,y,*px=&x;指针变量px指向整数x,那么*px可出现在x能出现的任何地方。y=*px+5;/*表示把x的内容加5并赋给y*/y=++*px;/*px的内容加上1之后赋给y[++*px相当于++(*px)]*/y=*px++;/*相当于y=*px,(*px)++*/当指针作为函数参数传递时,参数传递的不是数据本身,而是数据的存储地址。因此,在形实结合传送地址时,要求实参和形参都是相同数据类型的地址量。例如:8.1.2指针的声明(续)例8-3指针作为函数参数传递的应用。#include<stdio.h>intswap(int*iData1_ptr,int*iData2_ptr);voidmain(){ intiTest1=88,iTest2=66; printf(“调用swap()前:iTest1=%d,iTest2=%d\n",iTest1,iTest2); swap(&iTest1,&iTest2); printf(“调用swap()后:iTest1=%d,iTest2=%d\n",iTest1,iTest2);}8.1.2指针的声明(续)intswap(int*iData1_ptr,int*iData2_ptr){ intiTemp; printf(“调用swap()中交换前:*iData1_ptr=%d,*iData2_ptr=%d\n",*iData1_ptr,*iData2_ptr); iTemp=*iData1_ptr; *iData1_ptr=*iData2_ptr; *iData2_ptr=iTemp; printf(“调用swap()中交换后:*iData1_ptr=%d,*iData2_ptr=%d\n",*iData1_ptr,*iData2_ptr); return0;}8.1.2指针的声明(续)运行结果:调用swap()前:iTest1=88,iTest2=66调用swap()中交换前:*iData1_ptr=88,*iData2_ptr=66调用swap()中交换后:*iData1_ptr=66,*iData2_ptr=88调用swap()后:iTest1=66,iTest2=88在例8-3中,由于实参传递给形参的不是数据而是地址,被调函数swap()实际上是通过指针对调用函数中的变量iTest1,iTest2所存储的数据进行操作,操作的结果改变了调用函数中有关变量的数据〔如:对变量iTest1,iTest2实行了数据交换〕。8.2地址运算

指针允许的运算方式有:

(1).

指针在一定条件下可进行比较这里所说的一定条件是指两个指针指向同种类型对象才有意义。例如两个指针变量p,q指向具有同种数据类型数组,那么<,>,>=,<=,==等关系运算符都能进行正常运算。假设p==q为真,那么表示p,q指向数组的同一元素;假设p<q为真,那么表示p所指向的数组元素在q所指向的数组元素之前(对于指向数组元素的指针在下面将作详细讨论)。

(2).

指针和整数可进行加、减运算设p是指向某一数组元素的指针,开始时指向数组的第0号元素,设n为一整数,那么p+n就表示指向数组的第n号元素(即下标为n的元素)。不管指针变量指向何种数据类型,指针和整数进行加、减运算时,编译程序总根据所指对象的数据长度对n进行放大,在一般微机上,char放大因子为1,int、short放大因子为2,long和float放大因子为4,double放大因子为8。对于下面章节所讲述到的结构或共用体,也仍然遵守这一原那么。8.2地址运算

(续)(3).

两个指针变量在一定条件下可进行减法运算设p,q指向同一数组,那么p-q的绝对值表示p所指对象与q所指对象之间的元素个数。其相减的结果遵守对象类型的字节长度进行缩小的规那么。注意:不同数据类型指针之间、指针与一般常数之间的关系运算是没有意义的。但是指针和零之间的相等与不相等运算可用于判断指针是否为空指针。如判断指针p是否为空指针,可用p==0或p!=0来判断,也可以用p==NULL或p!=NULL来判断。例8-4指针的应用——地址运算。#include<stdio.h>8.2地址运算

(续)voidmain(){ intiArray[10]; int*iPoint1,*iPoint2; intiNumber; printf("数组iArray的初始化值:"); for(inti=0;i<10;i++) { iArray[i]=i; if((i%5)==0) printf("\n"); printf("iArray[%d]=%d\t",i,iArray[i]); } printf("\n"); iPoint1=&iArray[0]; iPoint2=&iArray[5]; iNumber=3;8.2地址运算

(续) /*指针比较运算:*/ printf("指针比较运算:\n"); printf("iPoint1=%d\tiPoint2=%d\n",iPoint1,iPoint2); printf("iPoint1=%d\tiPoint2=%d\n",*iPoint1,*iPoint2); if(iPoint1>iPoint2) printf("iPoint1>iPoint2\n"); elseif(iPoint1<iPoint2) printf("iPoint1<iPoint2\n"); else printf("iPoint1==iPoint2\n"); printf("\n"); /*指针与整数相加运算:*/ printf("指针与整数相加运算:\n"); printf("iPoint1=%d\tiPoint2=%d\tiNumber=%d\n",iPoint1,iPoint2,iNumber); printf("iPoint1=%d\tiPoint2=%d\tiNumber=%d\n",*iPoint1,*iPoint2,iNumber);8.2地址运算

(续) iPoint1=iPoint1+iNumber; iPoint2=iPoint2+iNumber; printf("iPoint1=iPoint1+iNumber=>iPoint1=%d\n",iPoint1); printf("iPoint1=iPoint1+iNumber=>iPoint1=%d\n",*iPoint1); printf("iPoint2=iPoint2+iNumber=>iPoint2=%d\n",iPoint2); printf("iPoint2=iPoint2+iNumber=>iPoint2=%d\n",*iPoint2); printf("\n"); /*指针减法运算:*/ printf("指针减法运算:\n"); iNumber=iPoint1-iPoint2; printf("iPoint1=%d\tiPoint2=%d\n",iPoint1,iPoint2); printf("iNumber=iPoint1-iPoint2=>iNumber=%d\n",iNumber); printf("\n");}8.2地址运算

(续)运行结果:数组iArray的初始化值:iArray[0]=0iArray[1]=1iArray[2]=2iArray[3]=3iArray[4]=4iArray[5]=5iArray[6]=6iArray[7]=7iArray[8]=8iArray[9]=9指针比较运算:iPoint1=1310552iPoint2=1310572iPoint1=0iPoint2=5iPoint1<iPoint2指针与整数相加运算:iPoint1=1310552iPoint2=1310572iNumber=3iPoint1=0iPoint2=5iNumber=3iPoint1=iPoint1+iNumber=>iPoint1=1310564iPoint1=iPoint1+iNumber=>iPoint1=3iPoint2=iPoint2+iNumber=>iPoint2=1310584iPoint2=iPoint2+iNumber=>iPoint2=8指针减法运算:iPoint1=1310564iPoint2=1310584iNumber=iPoint1-iPoint2=>iNumber=-58.3指向数组元素的指针例8-5数组元素的等价引用。#include<stdio.h>voidmain(){ int*iPoint; intiNum[5]; iPoint=iNum; /*建立指针和数组关联*/ for(inti=0;i<5;i++) *(iPoint+i)=i+1; /*用指针地址法引用数组元素*/ for(i=0;i<5;i++) printf("iNum[%d]=%d\t",i,iNum[i]); /*用下标法引用数组元素*/ printf("\n");}运行结果:iNum[0]=1iNum[1]=2iNum[2]=3iNum[3]=4iNum[4]=58.3指向数组元素的指针(续)下面我们通过上例来详细说明指针与数组的关系。首先声明一个整型数组和一个指向整型的指针变量:int*iPoint;intiNum[5;和前面介绍过的方法相同,可以使整型指针iPoint指向数组中的任何一个元素,假定给出赋值运算:iPoint=&iNum[0];此时,iPoint指向数组中的第0号元素,即iNum[0],指针变量iPoint中包含了数组元素iNum[0]的地址,由于数组元素在内存中是连续存放的,因此,我们就可以通过指针变量iPoint及其相关运算间接访问数组中的任意一个元素。在C语言中,数组名是数组的第0号元素的地址,因此下面两个语句是等价的。iPoint=&iNum[0];iPoint=iNum;8.3指向数组元素的指针(续)这里我们用第二种方法建立指针iPoint与数组iNum的关联。由上例结果我们知道,当指针与数组建立关联以后,对指针的操作就是对关联数组的操作。下面我们用指针给出数组元素的地址和内容的几种表示形式:

(1).

根据地址运算规那么,arry+1为arry[1]的地址,arry+i就为arry[i]的地址。同理,p+i和arry+i均表示arry[i]的地址。或者说,它们均指向数组第i号元素,即指向arry[i]。

(2).

*(p+i)和*(arry+i)都表示p+i和arry+i所指对象的内容,即为arry[i]。

(3).

指向数组元素的指针,也可以表示成数组的形式,也就是说,它允许指针变量带下标,如p[i]与*(p+i)等价。假假设:p=arry+5;那么p[2]就相当于*(p+2),由于p指向arry[5],所以p[2]就相当于arry[7]。而p[-3]就相当于*(p-3),它表示arry[2]。8.3指向数组元素的指针(续)例8-6输入10个数,存入data数组,求出这10个数据的最大值和最小值。#include<stdio.h>voidmain(){ float*fPoint,*fPend; floatfData[10]; floatfMax,fMin; fPoint=fData; /*建立指针和数组的关联*/ fPend=fData; printf("Enter10number:\n"); for(inti=0;i<10;i++) scanf("%f",fPend++); /*fPend++结果为地址,依次指向各个数组元素*/ /*循环结束后,fPend指向数组末尾元素fData[9]之后*/ fMax=*fData; /*先假定fData[0]为最大值和最小值*/ fMin=*fData;8.3指向数组元素的指针(续)/*循环中,fPoint的指向从fData[1]直至fData[9]*/ for(fPoint=fData+1;fPoint<fPend;fPoint++) if(*fPoint>fMax) fMax=*fPoint; elseif(*fPoint<fMin)

fMin=*fPoint; printf("Max=%f\tMin=%f\n",fMax,fMin);}运行结果:Enter10number:4.26.3-1.55.543.53.72.7-2.88.6↙Max=8.600000Min=-2.8000008.3指向数组元素的指针(续)例8-7求数组平均值〔用形实结合传送数组首地址方式传送数组数据〕。#include<stdio.h>#defineN5floatmean(int*data,intn) /*形参data为指针变量*/{ floataver=0.0; for(inti=0;i<n;i++,data++) /*求和*/ aver+=*data; aver/=n; /*求平均值*/ returnaver; /*返回平均值*/}8.3指向数组元素的指针(续)voidmain(){ inta[N]; floatav; printf("输入数组元素:\n"); for(inti=0;i<N;i++) /*输入数组元素值*/ scanf("%d",&a[i]); av=mean(a,N); /*调用mean()函数,数组名a为实参,与形参data对应*/ printf("Mean:%f\n",av); /*输出结果*/}运行结果:输入数组元素:10.31520.725.6↙Mean:15.48.3指向数组元素的指针(续)在例8-7中,用形实结合传送地址的方式将数组名a作为实参传递给形参data,使指针变量data指向数组a的首地址,这样,被调函数mean()中*data的操作实际上就是对调用函数中的a数组元素操作。通常数组含有众多元素,不可能将他们一并作为数组传送给被调函数。只有采取传送数组首地址的方式,使得被调函数和调用函数都能对同一数组数据空间操作,这样就解决了大批量数据在函数调用时互相传递的问题。8.3指向数组元素的指针(续)例8­-8求数组中的最大值元素,并将它移到该数组最前面,其余元素顺序后移〔用形实结合传送数组首地址方式传送数组数据〕。#include<stdio.h>#defineN5intMaxData(intiData[],intiPosition);intMoveData(int*iData,intiPosition);voidmain(){ inta[N],m; printf("移动前数组元素:");8.3指向数组元素的指针(续) for(inti=0;i<N;i++) /*输入数组*/ { a[i]=i; printf("%d\t",a[i]); } printf("\n"); m=MaxData(a,N); /*调用MaxData()函数,得到最大元素下标值*/ MoveData(a,m); /*调用MoveData()函数,完成要求的移动*/ printf("移动后数组元素:"); for(i=0;i<N;i++) /*输出移动后的数组*/ printf("%d/t",a[i]); printf("\n");}8.3指向数组元素的指针(续)/*定义求最大元素下标值函数,形参iData[]为虚数组首指针*/intMaxData(intiData[],intiPosition) { intmax,mi; max=iData[0]; for(inti=1;i<iPosition;i++)/*求最大元素下标*/ if(max<iData[i]) { mi=i; /*记下最大的元素下标*/ max=iData[i]; } returnmi; /*返回最大元素下标值*/}8.3指向数组元素的指针(续)/*定义完成移动最大元素在数组最前面,其余元素顺序后移的函数,形参iData为指针,准备接受数组首地址*/intMoveData(int*iData,intiPosition) { intmx; mx=iData[iPosition]; /*mx暂存最大元素值*/ for(inti=iPosition;i>0;i--) /*顺序后移*/ iData[i]=iData[i-1]; iData[0]=mx; /*最大元素放在最前面*/ return0;}运行结果:移动后数组元素:0 1 2 3 4移动后数组元素:4 0 1 2 38.4二维数组元素的地址例8-9输出二维数组的数组元素值。#include<stdio.h>voidmain(){ intarry[3][4]={ {1,2,3,4}, {11,12,13,14}, {21,22,23,24}}; for(inti=0;i<3;i++) { for(intj=0;j<4;j++) printf("%4d",*(*(arry+i)+j)); /*输出数组元素arry[i][j]*/ printf("\n"); }}8.4二维数组元素的地址(续)运行结果:12341112131421222324通过例8-9我们来说明问题,首先声明一个二维数组。如下所示:intarry[3][4]={{1,2,3,4},{11,12,13,14},{21,22,23,24}};arry为二维数组名,此数组有3行4列,共12个元素。换句话说,数组arry是由三个元素组成:arry[0],arry[1],arry[2]。而每个元素又是一个一维数组,且都含有4个元素(相当于二维数组的列)。8.4二维数组元素的地址(续)例如:arry[0]所代表的一维数组包含的4个元素为:arry[0][0],arry[0][1],arry[0][2],arry[0][3]。如图8-5所示:

arry[0]arry[1]arry[2]12341112131421222324arry8.4二维数组元素的地址(续)但从二维数组的角度来看,arry代表二维数组的首地址,当然也可看成是二维数组第0行的首地址。arry+1就代表第1行的首地址,arry+2就代表第2行的首地址。如果此二维数组的首地址为1000,由于第0行有4个整型元素,所以arry+1为1016,arry+2也就为1032。如图8-6所示:(1000)(1016)(1032)123411121314212223248.4二维数组元素的地址(续)既然把arry[0],arry[1],arry[2]看成是一维数组名,可以认为它们分别代表它们所对应的数组的首地址,也就是说:arry[0]代表第0行中第0列元素的地址,即&arry[0][0],arry[1]是第1行中第0列元素的地址,即&arry[1][0]。根据地址运算规那么,arry[0]+1即代表第0行第1列元素的地址,即&arry[0][1]。一般而言,arry[i]+j即代表第i行第j列元素的地址,即&arry[i][j]。另外,在二维数组中,我们还可用指针的形式来表示各元素的地址。如前所述,arry[0]与*(arry+0)等价,arry[1]与*(arry+1)等价,因此arry[i]+j就与*(arry+i)+j等价,它表示数组元素arry[i][j]的地址。因此,二维数组元素arry[i][j]可表示成*(arry[i]+j)或*(*(arry+i)+j),它们都与arry[i][j]等价,或者还可写成(*(arry+i))[j]。

8.4二维数组元素的地址(续)例8-10另一种输出二维数组方法。#include<stdio.h>voidmain(){ intarry[3][4]={ {1,2,3,4}, {11,12,13,14}, {21,22,23,24}}; int*iPoint; iPoint=arry[0]; while(iPoint<arry[0]+12) printf((((iPoint-arry[0])+1)%4)?"%4d":"%4d\n",*iPoint++);}8.4二维数组元素的地址(续)运行结果:123411121314212223248.5指向一个由n个元素所组成的数组指针例8-11输出二维数组任意行任意列元素值。#include<stdio.h>voidmain(){ intarry[3][4]={ {1,2,3,4}, {11,12,13,14}, {21,22,23,24}}; int(*iPoint)[4]; /*声明iPoint是一个指向包含4个元素的一维数组的指针*/ intiRow,iLine; iPoint=arry; /*iPoint作为行指针*/ printf("EnterRow,Line:"); scanf("%d,%d",&iRow,&iLine); printf("arry[%d][%d]=%d\n",iRow,iLine,(*(iPoint+iRow))[iLine]);}8.5指向一个由n个元素所组成的数组指针(续)运行结果:EnterRow,Line:2,3arry[2][3]=24分析例8-11:声明指针变量如下:

int(*iPoint)[4];指针iPoint表示为指向一个由4个元素所组成的整型数组的指针。在声明中,“〔〕〞是不能缺少的,否那么它是指针数组,而不是数组指针。这种数组的指针不同于前面介绍的整型指针,当整型指针指向一个整型数组元素时,进行指针(地址)加1运算,表示指向数组的下一个元素,此时地址值增加了4(因为整型数据类型在VC++6.0中占4个字节,既放大因子为4),而如上所声明的是指向一个由4个元素组成的数组的指针,进行地址加1运算时,其地址值增加了16(放大因子为)。这种数组指针在C语言中很少使用,但在处理二维数组时,还是比较方便的。例如:

intarry[5][5],(*p)[5];8.5指向一个由n个元素所组成的数组指针(续)p=arry;开始时p指向二维数组第0行,当进行p+1运算时,根据地址运算规那么,此时放大因子为,所以此时正好指向二维数组的第1行。和二维数组元素地址计算的规那么一样,*p+1指向arry[0][1],*(p+i)+j那么指向数组元素arry[i][j]。

例8-12数组指针的应用。#include<stdio.h>intarry[5][5]={ {2,4,6,8,10}, {10,12,14,16,18}, {18,20,22,24,26}, {26,28,30,32,34}, {34,36,38,40,42},};8.5指向一个由n个元素所组成的数组指针(续)voidmain(){ inti,(*Point)[5]; for(i=0;i<4;i++) { Point=arry+i; printf("%d\t",*(Point[i]+2)); } printf("\n");}运行结果:6 22 38 08.6字符指针

我们已经知道,字符串常量是由“〞括起来的一组字符序列。例如:

“a

string〞就是一个字符串常量,该字符串中因为字符a后面还有一个空格字符,所以它是由8个字符序列组成。在程序中如果出现字符串常量,C编译程序就会给该字符串常量按排一连续存贮区域,这个区域是静态的,在整个程序运行的过程中始终占用。平时所讲的字符串常量的长度是指该字符串的字符个数,但在按排存贮区域时,C编译程序还自动给该字符串序列的末尾加上一个空字符'\0',用来标志字符串的结束,因此一个字符串常量所占的存贮区域的字节数总比它的字符个数多一个字节。让字符指针与存放字符串的字符数组关联,就可以用字符指针表示该字符串。8.6字符指针

(续)例8-13字符指针指向字符数组#include<stdio.h>voidmain(){ charstr[]="Goodmorning!"; char*chPoint; chPoint=str; /*chPoint指向str数组的首地址*/ printf("strstringis%s\n",str); printf("(1)chPointstringis%s\n",chPoint); chPoint=str+5; printf("(1)chPointstringis%s\n",chPoint);}8.6字符指针

(续)运行结果:strstringisGoodmorning!(1)chPointstringisGoodmorning!(1)chPointstringismorning!例8-13中,当chPoint指向str字符串首地址时,输出整个str字符串;当chPoint指向str字符串中某一个字符地址时,输出以该字符为首字符的后续字符串。因此,使用字符指针比使用字符数组名更为灵活。C语言中操作一个字符串常量的方法有如下两种:(1).把字符串常量存放在一个字符数组中。例如:chars[]="astring";8.6字符指针

(续)数组s共由9个元素组成,其中s[8]中的内容是'\0'。实际上,在字符数组声明的过程中,编译程序直接把字符串复制到数组中,即对数组s初始化。(2).用字符指针指向字符串,然后通过字符指针来访问字符串存贮区域。当字符串常量在表达式中出现时,根据数组的类型转换规那么,它被转换成字符指针。因此,假设我们声明了一个字符指针cp:

char*cp;于是可用:

cp="astring";使cp指向字符串常量中的第0号字符a。以后我们可以通过cp来访问这一存贮区域,如*cp或cp[0]就是字符a,而cp[i]或*(cp+i)就相当于字符串的第i号字符,但不能通过指针来修改字符串常量。8.6字符指针

(续)例8-14把键盘输入的字符串逆序排列,然后输出。#include<stdio.h>#include<string.h>voidmain(){ charstr[80],t; char*chPoint1,*chPoint2; printf("Enterastring:"); scanf("%s",str); /*输入要处理的字符串*/ for(chPoint1=str,chPoint2=chPoint1+strlen(str)-1; chPoint1<chPoint2;chPoint1++,chPoint2--) { t=*chPoint1; *chPoint1=*chPoint2; *chPoint2=t; } printf("Thereversedstringis:%s\n",str);}8.6字符指针

(续)运行结果:Enterastring:Hello!Thereversedstringis:!olleH字符串是特殊形式的字符数组,因此,如果要从调用函数传送字符串给被调函数处理,同样要采用字符串首地址作为实参的方式,当然对应的形参必须是char型指针或char型虚数组首指针。还有一个特别之处,字符串常量也可以作为实参。此时,外表上接受它的形参是char地址量,实际上是让此形参指向该字符串的首地址,因而被调函数同样可以对这个字符串常量所占据的空间操作。8.6字符指针

(续)例8-15字符串在函数间的传送。#include<stdio.h>intstrlen(chars[]);voidmain(){ char*str="cprogram"; intlen; len=strlen(str); /*字符串首指针为实参*/ printf("Thefirststring\'slength:%d\n",len); len=strlen("fortranlanguige"); /*移串常量为实参*/ printf("Thesecondstring\'slength:%d\n",len);}8.6字符指针

(续)intstrlen(chars[])/*自定义求字符串长度函数strlen()*/{ for(intn=0;*s!='\0';s++) n++; returnn;}运行结果:Thefirststring'slength:9Thesecondstring'slength:16例8-15中两次调用函数strlen(),第一次以字符串首指针为实参,第二次以串常量为实参。8.7指针数组

因为指针是变量,因此用指向同一数据类型的指针来构成一个数组,就形成了指针数组。数组中的每个元素都是指针变量,根据数组的声明,指针数组中每个元素都为指向同一数据类型的指针。指针数组的声明格式为:类型标识

*数组名[整型常量表达式];例如:

int*a[10];声明了一个指针数组a,数组中的每个元素都是指向整型量的指针,该数组由10个元素组成,即a[0],a[1],a[2],...,a[9],它们均为指针变量。a为该指针数组名,和数组一样,a是常量,不能对它进行增量运算。a为指针数组元素a[0]的地址,a+i即为a[i]的地址,*a就是a[0],*(a+i)就是a[i]。8.7指针数组

(续)指针数组处理字符串更方便、更灵活。使用二维数组对处理长度不等的正文效率是很低的,而指针数组由于其中每个元素都是指针变量,因此通过地址运算来操作正文是十分方便的。例8-16用指针数组处理二维数组数据。#include<stdio.h>voidmain(){ intiArray[3][4]; int*iPoint[3]; /*给二维数组iArray赋初值:*/ for(inti=0;i<3;i++) for(intj=0;j<4;j++) iArray[i][j]=(i+1)*(j+1); /*让指针数组iPoint分别指向3个一维数组:*/ iPoint[0]=iArray[0]; iPoint[1]=iArray[1]; iPoint[2]=iArray[2]; /*按行输出二维数组元素:*/8.7指针数组

(续) for(i=0;i<3;i++) { for(intj=0;j<4;j++) printf("iArray[%d][%d]=%d",i,j,*(iPoint[i]+j)); printf("\n"); }}运行结果:iArray[0][0]=1iArray[0][1]=2iArray[0][2]=3iArray[0][3]=4iArray[1][0]=2iArray[1][1]=4iArray[1][2]=6iArray[1][3]=8iArray[2][0]=3iArray[2][1]=6iArray[2][2]=9iArray[2][3]=12Pressanykeytocontinue_指针数组和一般数组一样,允许指针数组在声明时初始化,但由于指针数组的每个元素都是指针变量,它只能存放地址,所以对指向字符串的指针数组在说明赋初值时,是把存放字符串的首地址赋给指针数组的对应元素。8.7指针数组

(续)

例8-17把5个国名按字母顺序排列输出。#include<stdio.h>#include<string.h>voidsort(char*name[],intn);voidprint(char*name[],intn);staticchar*name[]={"CHINA","AMERICA","AUSTRALIA","FRANCE","GERMAN"};voidmain(){ intiCount=5; print(name,iCount); sort(name,iCount); print(name,iCount);}8.7指针数组

(续)voidsort(char*name[],intn){ char*pt; inti,j,k; for(i=0;i<n-1;i++) { k=i;for(j=i+1;j<n;j++)if(strcmp(name[k],name[j])>0)k=j;if(k!=i) { pt=name[i];name[i]=name[k];name[k]=pt;} }}8.7指针数组

(续)voidprint(char*name[],intn){ inti; for(i=0;i<n;i++) printf("%s\t",name[i]); printf("\n");}运行结果:CHINA AMERICA AUSTRALIA FRANCE GERMANAMERICAAUSTRALIA CHINA FRANCE GERMAN8.8指针函数所谓函数类型是指函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。格式:类型说明符*

函数名(参数);当然了,由于返回的是一个地址,所以类型说明符一般都是int。

例如:int*GetDate();int*aaa(int,int);函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。8.8指针函数(续)例8-18指针函数应用。#include<stdio.h>int*GetDate(intweek,intday);voidmain(){ intweek,day;do{printf("Enterweek(1-5)day(1-7)\n");scanf("%d,%d",&week,&day); printf("%d\n",*GetDate(week,day));}while((week>=1)&&(week<=5)&&(day>=1)&&(day<=7)); printf("\nEnd!\n");}8.8指针函数(续)int*GetDate(intweek,intday){ staticintWeekAndDay[5][7]={{1,2,3,4,5,6,7},{8,9,10,11,12,13,14},{15,16,17,18,19,20,21},{22,23,24,25,26,27,28},{29,30,31,-1,-1}};return&WeekAndDay[week-1][day-1];}运行结果:Enterweek(1-5)day(1-7)2,5120,08.9函数指针在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋给一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量〞。声明格式如下:类型说明符

(*函数名)(参数);其实这里不能称为函数名,应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明类型和它指向函数的声明类型保持一致。指针名和指针运算符外面的括号改变了默认的运算符优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。例如:void(*fptr)();把函数的地址赋值给函数指针,可以采用下面两种形式:fptr=&function;或fptr=Function;8.9函数指针(续)取地址运算符&不是必需的,因为单单一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。可以采用如下两种方式来通过指针调用函数:x=(*fptr)();或x=fptr();第二种格式看上去和函数调用无异。建议使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。例8-19函数指针的应用。#include<stdio.h>void(*funcp)();void(*funcp1)(intiValue);8.9函数指针(续)voidFileFunc();voidEditFunc(intiValue);voidmain(){ intiValue=0;funcp=FileFunc;(*funcp)();funcp1=EditFunc;(*funcp1)(iValue);}8.9函数指针(续)voidFileFunc(){printf("FileFunc\n");}voidEditFunc(intiValue){printf("EditFunc\n"); printf("%d\n",iValue);}运行结果:FileFuncEditFunc08.10多级指针如果一个指针变量存放的又是另一个指针变量的地址,那么称这个指针变量为指向指针的指针变量。例如:char**cp;指针的指针需要用到指针的地址。通过指针的指针,不仅可以访问它指向的指针,还可以访问它指向的指针所指向的数据。下面就是几个这样的例子:charc='A';char*p=&c;char**cp=&p;charc1=**cp;利用指针的指针可以允许被调用函数修改局部指针变量和处理指针数组。8.10多级指针(续)例8-20多级指针的应用。#include<stdio.h>voidFindCredit(int**Point);voidmain(){intvals[]={7,6,5,-4,3,2,1,0};int*fp=vals;FindCredit(&fp);printf("%d\n",*fp);}8.10多级指针(续)voidFindCredit(int**Point){while(**Point!=0) if(**Point<0) break; else (*Point)++;}运算结果:-48.10多级指针(续)现在让我们来对例8-20进行分析。首先用一个数组的地址初始化指针fp,然后把该指针的地址作为实参传递给函数FindCredit()。FindCredit()函数通过表达式**Point间接地得到数组中的数据。为遍历数组以找到一个负值,FindCredit()函数进行自增运算的对象是调用者的指向数组的指针,而不是它自己的指向调用者指针的指针。语句(*Point)++就是对形参指针指向的指针进行自增运算的。8.10多级指针(续)例8-21输出指针数组指向的各个字符串。#include<stdio.h>voidmain(){ char*chName_ptr[]={ "WINDOWS", "MS-DOS", "UNIX", "LINUX"}; char**ptr; for(inti=0;i<4;i++) { ptr=chName_ptr+i; printf("%s\n",*ptr); }}8.10多级指针(续)运行结果:WINDOWSMS-DOSUNIXLINUX注意:一级指针〔直接存放数据变量的地址的指针〕实现的是间接访问,又称“单级间接寻址〞,简称“单级间址〞。二级指针那么是间接的间接的访问,称为“二级间址〞。从理论上讲,间址方法可延伸到更多级。但三级及以上的指针极少用于程序,因为级数越多,越难理解,也容易出错。8.11void指针类型void的字面意思是“无类型〞,void*那么为“无类型指针〞,void

*可以指向任何类型的数据。8.11.1void的含义void真正的意义在于:〔1〕

对函数返回的限定;〔2〕

对函数参数的限定。众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,那么必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。

例如:float*p1;int*p2;p1=p2;8.11.1void的含义(续)其中p1=p2语句会编译出错,必须改为:p1=(float*)p2;而void

*那么不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换:void*p1;int*p2;p1=p2;但这并不意味着,void

*也可以无需强制类型转换地赋给其它类型的指针。因为“无类型〞可以包容“有类型〞,而“有类型〞那么不能包容“无类型〞。8.11.2void使用规那么下面给出void关键字的使用规那么:如果函数没有返回值,那么应声明为void类型在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void类型。例8-22void类型示范。#include<stdio.h>add(inta,intb){ returna+b;}voidmain(){ printf("2+3=%d\n",add(2,3));}8.11.2void使用规那么(续)运行结果:2+3=5这说明不加返回值说明的函数确实为int函数。另外,加上void类型声明后,也可以发挥代码的“自注释〞作用。代码的“自注释〞即代码能自己注释自己。如果函数无参数,那么应声明其参数为void在C语言中声明一个这样的函数:intfunction(void){ return1;}那么进行下面的调用是不合法的:function(2);8.11.2void使用规那么(续)因为在C中,函数参数为void的意思是这个函数不接受任何参数,所以,在C语言中,假设函数不接受任何参数,要指明参数为void。小心使用void指针类型按照ANSI(American

National

Standards

Institute)标准,不能对void指针进行算法操作,即以下操作都是不合法的:void*pvoid;pvoid++;/*ANSI:错误*/pvoid+=1;/*ANSI:错误*/ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。例如:int*pint;pint++;/*ANSI:正确*/pint++的结果是使其增大sizeof(int),但是大名鼎鼎的GNU(GNU’s

Not

Unix的缩写)那么不这么认定,它指定void

*的算法操作与char

*一致。8.11.2void使用规那么(续)

因此以下语句在GNU编译器中皆正确:pvoid++; /*GNU:正确*/pvoid+=1;/*GNU:正确*/pvoid++的执行结果是其增大了1。在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:void*pvoid;(char*)pvoid++;/*ANSI:正确;GNU:正确*/(char*)pvoid+=1;/*ANSI:错误;GNU:正确*/GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放〞,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合ANSI标准。如果函数的参数可以是任意类型指针,那么应声明其参数为void

*8.11.2void使用规那么(续)典型的如内存操作函数memcpy和memset的函数原型分别为:void*memcpy(void*dest,constvoid*src,size_tlen);void*memset(void*buffer,intc,size_tnum);这样,任何类型的指针都可以传入memcpy和memset中,这也真实地表达了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不管这片内存是什么类型。下面的代码执行正确:例如:memset接受任意类型指针intintarray[100];memset(intarray,0,100*sizeof(int));/*将intarray清0*/例如:memcpy接受任意类型指针intintarray1[100],intarray2[100];memcpy(intarray1,intarray2,100*sizeof(int));/*将intarray2拷贝给intarray1*/8.11.2void使用规那么(续)有趣的是,memcpy和memset函数返回的也是void

*类型。void不能代表一个真实的变量下面代码都企图让void代表一个真实的变量,因此都是错误的代码:voida;/*错误*/function(voida);/*错误*/void的出现只是为了一种抽象的需要。例8-23void指针类型应用实例。#include<stdio.h>#include<stdlib.h>8.11.2void使用规那么(续)voidmain(){ void*ptr=NULL; charchName[10]="Hello"; char*chptr; chptr=chName; printf("chptr=chName\n"); printf("chName=%s\tchptr=%s\n",chName,chptr); printf("chName'saddressis%d\tchptr'saddressis%d\tptr'saddressis%d\n", &chName,&chptr,&ptr); printf("\n"); ptr=chptr; printf("ptr=chptr\n"); printf("ptr=%s\tchptr=%s\tchName=%s\n",ptr,chptr,chName);8.11.2void使用规那么(续) printf("chName'saddressis%d\tchptr'saddressis%d\tptr'saddressis%d\n", &chName,&chptr,&ptr); printf("\n"); ptr=chName; printf("ptr=chName\n"); printf("ptr=%s\tchptr=%s\tchName=%s\n",ptr,chptr,chName); printf("chName'saddressis%d\tchptr'saddressis%d\tptr'saddressis%d\n", &chName,&chptr,&ptr); printf("\n");}8.11.2void使用规那么(续)运行结果:chptr=chNamechName=Hellochptr=HellochName'saddressis1310576chptr'saddressis1310572ptr'saddressis1310588ptr=chptrptr=Hellochptr=HellochName=HellochName'saddressis1310576chptr'saddressis1310572ptr'saddressis1310588ptr=chNameptr=Hellochptr=HellochName=HellochName'saddressis1310576chptr'saddressis1310572ptr'saddressis13105888.12带参数的main()函数在操作系统状态下执行程序时,必须键入该程序的可执行文件名。例如,为了复制文件需键入一行字符:Copyfile1.txtfile2.txt↙这行字符称为命令行,其格式为:命令名参数1参数2……<回车>如上述命令中,copy是命令名,一般是可执行程序的文件名,file1.txt、file2.txt即为命令行所带的参数,命令名和各个参数之间以空格分隔,整个命令以回车结束。当用C语言编写程序时,有时要求在启动该程序的命令行中带假设干个参数。那么,如何将命令中的参数传递给C程序呢?请看下面的例子:8.12带参数的main()函数(续)例8-24带命令行参数的验证例如。#include<stdio.h>intmain(intargc,char*argv[]){ inti; printf("argc=%d\n",argc); printf("Commandname:%s\n",argv[0]); for(i=1;i<argc;i++) printf("ArgumentNo.%d:%s\n",i,argv[i]); return0;}8.12带参数的main()函数(续)假定这个程序的可执行文件名为cprog.exe,在操作系统下键入的命令行为:cprogbookpenpaper/s↙运行结果:argc=5Commandname:cprogArgumentNo.1:bookArgumentNo.2:penArgumentNo.3:paperArgumentNo.4:/s8.12带参数的main()函数(续)由于C程序总是从main()函数开始执行,因此,C语言在main()函数中使用形式参数来接收命令中的实际参数。带参数的main()函数格式如下:intmain(intargc,char*argv[]){……}形式参数argc是int型变量,含义是命令行中命令名和所带参数的个数之和。字符指针数组argv的各个指针分别依次指向命令行中命令名和各个参数的字符串。8.12带参数的main()函数(续)例如:C程序exefile带有3个命令行参数,其命令行为:Exefileproc1.cproc2.cproc3.c执行该命令行时,main()函数中的两个形式参数被初始化。argc的值为4,argv中有四个元素,分别指向为:argv[0]="exefile";argv[1]="proc1.c";argv[2]="proc2.c";argv[3]="proc3.c";main()函数中这两个形式参数的初始化过程是由系统在执行程序时自动完成的。这两个参数的名字用户也可以取其他名称,但习惯上使用argc和argv。假设改用别的名字,其数据类型不能改变,即一个必须为int型,另一个必须为char型指针数组。8.13指针的内存动态分配存储数据的空间可以通过两种方式获得。一种是以显示说明的形式指定存储数据的变量或数组,C编译系统根据说明语句来预先安排规定大小的内存空间。这些空间一经分配,在变量或数组的生存期内固定不变,这种分配方式称为“静态存储分配〞。还有一种“动态存储分配〞方式,程序执行中假设需要空间存储数据,可向系统“申请〞适当大小的存储空间;当有空间闲置时,可随时“释放〞不用的空间,由系统“收回〞另作它用。下面介绍一些有关内存动态分配空间的函数。内存管理库函数:分配存储空间函数malloc();分配存储空间函数calloc();重新分配空间函数realloc();释放空间函数free();8.13指针的内存动态分配(续)(1)分配存储空间函数malloc()

malloc()函数的原型为:void*malloc(unsignedsize);该函数的作用是在内存空间中申请一块大小为size字节的空间,并将此存储空间的起始地址作为函数值带回。假设没有足够的内存空间分配,函数返回空〔NULL〕。例如:malloc(10)的结果是分配了一个长度为10字节的内存空间,假设系统设定的此内存空间的起始地址为1800,那么malloc(10)的函数返回值就为1800。实际应用时,往往要给出存放数据的数据类型,这可以通过强制类型转换实现。8.13指针的内存动态分配(续)例如:int*pi;float*pf;pi=(int*)malloc(sizeof(int)); /*pi指向一个int型的存储单元*/pf=(float*)malloc(sizeof(float)); /*pf指向一个float型的存储单元*/if(pi!=NULL)*pi=123;if(pf!=NULL)*pf=123.456;注意:(int*)中的*不可少,否那么就是int型而非int指针型了。假设数据类型所占字节数,也可直接指出。8.13指针的内存动态分配(续)例如:pi=(int*)

温馨提示

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

评论

0/150

提交评论