版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C语言程序设计教学做一体化
主编滕泓虬
中国水利水电出版社模块十文件事实上我们对于“文件”应该不会感到陌生,在前面的实例中完成一个程序的上机过程都要首先建立一个C语言的源文件(后缀名为.c),然后编译成相应的目标文件(后缀名为.obj),最后链接成可执行文件(后缀名为.exe)。这些程序都是以文件的形式在磁盘中保存的。10.1文件的概述学习目标
了解文件和文件名的概念;掌握文件的分类和操作流程。不管是Windows操作系统还是UNIX操作系统,均是以文件为单位管理数据的。也就是说,如果要找到存储在磁盘上的数据,需要先按照文件名找到数据所在的文件,然后才能从文件中读取或写入数据。同样,如果想在磁盘中保存某些数据,则需要先建立一个文件,然后才能向文件中输入数据。案例10-1文件和文件名的概念。10.1.1文件和文件名的概念案例分析1、文件和文件名2、文件的广泛含义案例分析案例10-2按照数据的组织形式对文件进行分类,说出整数123以ASCII形式和二进制形式存储到文件中的区别。10.1.2文件的分类1、整数123如果以ASCII文件形式存储,则存储形式如下:
001100010011001000110011在文件中按顺序存储123分别对应的ASCII码,比如1的ASCII码是49(十进制),对应的二进制ASCII码就是00110001。整数123在磁盘文件中占用3个字节。2、同样是整数123,如果以二进制文件形式存储,存储形式是:
0000000001111011在内存中存储的就是整数123对应的二进制数,整数在内存中占两个字节,所以整数123在内存中也是占有两个字节的存储空间。那么在文件中如果以二进制形式存储,则存储形式和在内存中存储形式一样。案例分析1、按照数据的组织形式对文件分类。(1)ASCII文件(2)二进制文件2、ASCII文件和二进制文件的优缺点比较。知识链接C语言没有提供对文件进行操作的语句,所有的文件都是利用C语言编译系统所提供的库函数实现的。多数C语言编译系统都提供两种文件处理方式:一种是“缓冲文件系统“,另一种是“非缓冲文件系统”。也就是说C语言提供了两类文件操作函数来存取文件。10.1.3缓冲文件系统和非缓冲文件系统案例10-4文件的操作流程。10.1.4文件的操作流程对文件的操作流程一般遵循以下四个步骤:1、定义一个文件类型的指针变量指向文件一般格式是:
FILE*指针变量标识符;例如:
FILE*fp;2、调用库函数fopen函数打开文件,同时将打开文件操作返回的文件指针值赋值给(1)中定义的文件类型的指针变量,是该指针变量指向文件。例如:
fp=fopen(“文件名”,“操作方式”);其中fopen的参数”操作方式”是指读/写等方式。3、通过文件指针对文件进行读/写操作例如:调用库函数fputs()或fgets()完成读写文件的操作。4、通过文件指针关闭文件,例如:调用库函数fclose fclose(fp);提示:对文件操作的库函数,函数原型均在头文件stdio.h中。所以使用文件操作的库函数都要包含头文件stdio.h。学习目标了解文件信息结构体类型;掌握文件指针的概念和定义格式;掌握定义文件指针的目的和作用;在C语言中用一个指针变量指向一个文件,该指针称为文件指针。10.2文件指针定义指向文件的指针。案例10-51、文件指针说明的一般格式如下:FILE*文件型指针变量名其中FILE必须大写,也就是应和stdio.h头文件中定义的一致。2、定义一个文件指针:
FILE*fp;fp是表示指向FILE结构的指针变量,它指向在内存中为某个文件开辟的用来保存文件信息的结构体变量的首地址,然后按照结构体变量提供的信息找到文件,实施对文件的操作。实际上fp是指向保存文件信息的结构体类型的指针,但习惯上也笼统地把fp称为指向一个文件的指针。案例分析1、文件指针是缓冲文件系统中的一个非常重要的概念。系统为每个正在使用的文件在内存中开辟一个区域来存放该文件的相关信息。比如文件名、文件属性、文件操作模式、文件缓冲区位置等。这些信息保存在一个结构体类型的变量中。该结构体类型由系统定义,其名为FILE。2、C语言系统定义的文件结构体类型FILE,可以在头文件stdio.h中查看它的定义。在编译系统文件夹中的include文件夹下找到该头文件并打开,可以看到下面的定义:知识链接typedefstruct {shortlevel; /*缓冲区满或空的程度fill/emptylevelofbuffer*/unsignedflags; /*文件状态标志Filestatusflags*/ charfd; /*文件描述符Filedescriptor*/unsignedcharhold;/*如果没有缓冲区就不读字Ungetccharifnobuffer*/shortbsize; /*缓冲区大小Buffersize*/unsignedchar*buffer;/*数据缓冲区位置Datatransferbuffer*/unsignedchar*curp; /*当前的指向Currentactivepointer*/unsignedistemp; /*临时文件指示Temporaryfileindicator*/shorttoken; /*用于有效性检查Usedforvaliditychecking*/}FILE;3、文件型指针就是指向类型为FILE的文件结构类型的指针。定义文件指针的目的就是通过文件指针对它指向的文件进行各种操作。在缓冲文件系统中,对已打开的文件进行I/O操作都是通过文件指针进行的。因此,文件操作的第一步就是定义文件指针。学习目标理解文件的使用方式;掌握文件的打开方法;掌握文件的关闭方法。在对磁盘文件进行读写操作之前,必须先打开文件,使用完毕要关闭文件。10.3文件的打开和关闭假设在C盘根目录下,有一个二进制文件,文件名为:student.dat,以只读的方式打开它。案例10-6案例程序#include<stdio.h>main(){ FILE*fp; /*定义文件指针*/ if((fp=fopen(“c:\\student.dat”,“rb”)==NULL) /*调用fopen函数打开文件*/ { printf(“Cannotopenthisfile!\n”); /*输出失败信息*/ exit(1); /*退出操作*/ } else printf(“Youhaveopenedthefile.\n”);/*提示打开成功*/}1、在程序中通过判断fopen函数的返回值来判定是否成功打开一个文件,并显示相应的提示信息。2、调用fopen函数会返回指针值,如果成功,则返回文件指针;如果失败,则返回指针为NULL。表达式(fp=fopen(“c:\\student.dat”,“rb”)==NULL就是判断打开student.dat文件时返回的指针是否为NULL,如果是,则表示不能打开C盘根目录下的student.dat文件,并给出出错提示信息“Cannotopenthisfile!”,然后用函数exit(1)结束运行。如果不是NULL,则表示成功打开文件,并输出成功打开信息。案例分析提示:exit()函数的功能就是关闭已打开的文件,结束程序运行,返回操作系统,并将“程序状态值”作为函数参数返回给操作系统。当“程序状态值”为0时,表示程序正常退出;非0值时,表示程序非正常退出。1、打开文件就是在内存中建立文件的各种有关信息,并使文件指针指向该文件。也就是在前面定义文件指针变量的基础上,再使该指针指向打开的文件,以便进行后续的操作。2、C语言中,使用fopen()函数打开文件,调用fopen()函数的方式如下: 文件指针名=fopen(“文件名”,“使用文件方式”);知识链接3、fopen()函数的功能函数返回一个指向指定文件的指针。例如:
FILE*fp; fp=fopen(“abc”,“r”);它表示要打开名为abc的磁盘文件,使用文件方式为“读入”(r代表read,即读入),fopen函数返回一个指向文件abc的指针并赋给文件指针变量fp,使fp和文件abc建立联系,或者说,fp指向文件abc。4、调用fopen()函数需要注意的问题:(1)“文件指针名”必须是被说明为FILE类型的指针变量。如:FILE*fp;(2)函数的实参“文件名”是要打开的文件名称,也就是准备要访问的文件的名字。该文件名可以包含路径。它可以是一个用双引号括起来的字符串常量,如fopen(“abc”,“r”);中的“abc”,也可以是一个指向字符串的指针。注意使用指针形式时,不使用双引号。(3)“使用文件方式”是指文件的类型(二进制文件还是文本文件)和操作要求(读文件、写文件或读写文件)。(4)让指针变量指向文件,即把函数返回值(FILE结构的首地址)赋给定义的文件指针变量。如fp=fopen(“abc”,“r”);又如:
FILE*fpt; fpt=fopen(“D:\\hi”,“rb”);以写入方式新建一个文件,如果文件已经存在,提示是否覆盖原文件。案例10-7#include<stdio.h>voidmain(){FILE*fp; /*定义文件指针*/charfilename[20],ch;printf("Pleaseinputthefilename:"); /*输入要建立的文件名*/scanf("%s",filename);fp=fopen(filename,"r");/*已只读方式打开文件*/if(fp!=NULL) /*判断是否成功打开文件,如果成功说明该文件已存在*/案例程序{ printf("Thefilealreadyexists,replaceitornot?(y/n)");/*提示已有同名文件*/ getchar(); scanf("%c",&ch); /*输入选择,决定是否替换原有文件*/ if(ch=='n'||ch=='N') /*如果输入N或n就退出程序*/ exit(1); }fclose(fp); /*关闭文件*/fp=fopen(filename,"w"); /*已写入模式新建文件*/if(fp!=NULL) /*成功新建*/ printf("Thefile%shasbeencreated.\n",filename);
else /*新建失败*/ { printf("Thefilecannotbecreated.\n"); exit(1); }fclose(fp); /*关闭文件*/}运行程序,假设原来在D盘中没有文件score.txt,结果是:Pleaseinputthefilename:d:\score.txt↙ /*输入文件名*/Thefiled:\score.txthasbeencreated. /*提示建立了文件*/再次运行程序,结果是:Pleaseinputthefilename:d:\score.txt↙ /*输入文件名*/ Thefilealreadyexists,replaceitornot?(y/n)n↙/*是否替换,输入n则不替换*/再次运行程序,结果是:
Thefilealreadyexists,replaceitornot?(y/n)y↙ /*选择替换原文件*/ Thefiled:\score.txthasbeencreated. /*新建了文件,并覆盖了原文件*/1、首先输入要新建的文件名,例如:d:\score.txt,表示在d驱动器磁盘根目录下建立文本文件score.txt。然后再以只读的方式打开文件,如果不能打开(fp==NULL),则新建该文件。见运行结果第一和第二行。2、再次运行程序,这时在d盘根目录下已建立文件score.txt,则读取文件时,就会提示用户是否覆盖已经存在的文件,如果用户不同意(输入N或n),则结束程序运行。见运行结果第三和第四行。3、再次运行程序,在提示是否覆盖时,输入y(实际上只要不输入N或n即可),表示用户同意覆盖已经存在的文件。则覆盖掉原来的文件,生成一个同名文件。4、语句fclose(fp);表示调用文件关闭函数,关闭文件指针fp所指向的文件。 案例分析知识链接提示:在程序开始运行时,系统自动打开三个标准文件,并分别定义文件指针。1、标准输入文件stdin:指向终端输入(一般为键盘)。2、标准输出文件stdout:指向终端输出(一般为显示器)。3、标准错误文件stderr:指向终端标准错误输出(一般为显示器)。判断文件读写文件是否已到文件结尾的方法。案例分析1、判断文本文件是否结束的方法。从一个磁盘文本文件中顺序读入字符并在屏幕上显示出来,代码如下:ch=fgetc(fp);while(ch!=EOF){ putchar(ch); ch=fgetc(fp);}案例10-82、判断二进制磁盘文件是否结束的方法从一个磁盘二进制文件中顺序读入字符并在屏幕上显示出来,代码如下:ch=fgetc(fp);while(!feof(fp)){ putchar(ch); ch=fgetc(fp);}字符读写函数有getc()、fgetc()、putc()、fputc()函数,字符读写函数是以字符(字节)为单位的读写函数,每次从文件读出或写入一个字符。10.4.1字符读写函数案例10-9把字符序列写入文件:从键盘输入一系列字符,并逐个输出到磁盘文件test.txt中。#include<stdio.h>main(){FILE*fp;charch;if((fp=fopen("test.txt","w+"))==NULL) /*以“w+”方式打开文件*/ { printf("Cannotopenfile\n"); exit(1); } 案例程序printf("Pleaseinput:\n"); ch=getchar(); /*获得第一个字符*/ while(ch!='\n') /*输入回车则结束*/ { fputc(ch,fp); /*将当前字符写入文件*/ ch=getchar(); /*获得下一个字符*/ } fclose(fp); /*关闭文件*/}运行程序,输入:Hello,everyone!↙运行结果:在Turboc目录下如没有test.txt文件,则新建test.txt文件,写入内容为输入的字符串;如果test.txt文件已存在,则覆盖原来的内容为新输入的字符串。1、本案例用“w+”方式建立文件test.txt在默认路径下。2、语句fputc(ch,fp);就是将字符变量ch的值写入fp所指向的文件中。也可以调用函数putc(),在stdio.h头文件中,putc()已被定义为fputc()函数的宏名,所以二者是同一个函数。案例分析提示:默认建立文件的目录是Turboc文件夹,也可以建立在其它磁盘目录下,例如调用fopen函数时,改为:fopen(“D:\\test.txt”,“w+”),则会在D盘根目录下建立test.txt文件。读取文件中的字符序列案例程序#include<stdio.h>voidmain(){ FILE*fp; charch,filename[20]; printf("\nPleaseinputfilename:"); gets(filename); /*输入文件名,保存在字符数组filename中*/ fp=fopen(filename,"r"); /*以只读方式"r"打开文件*/ if(fp!=NULL) /*如果成功打开文件*/案例10-10{ while((ch=getc(fp))!=EOF)/*读取字符直到文件结束*/ putchar(ch); /*输出字符到屏幕上*/ } else printf("\nCannotopenfile%s.\n",filename); fclose(fp); /*关闭文件*/}运行程序,结果是:
Pleaseinputfilename:test.txt↙
输出读取的结果:Hello,everyone!1、这里输入要打开的文件名是test.txt,该文件在上一案例中已建立,所以输出到屏幕上的内容就是:Hello,everyone!2、while循环中的表达式(ch=getc(fp))!=EOF,调用了getc()函数,该函数也可以改为fgetc()。因为在stdio.h头文件中,也定义了getc()为fgetc()的宏名,二者也是同一个函数。该函数调用后,会返回读取到的字符,如果遇到文件结束符,则返回一个文件结束标志EOF。案例分析1.、getc函数和fgetc函数(1)调用形式 字符变量=getc(文件指针);字符变量=fgetc(文件指针);(2)功能从文件指针所指向的文件中读取一个字符,该文件必须以读或读/写方式打开。(3)返回值返回读取到的字符,如果遇到文件结束符,则返回一个文件结束标志EOF。知识链接main(){ inta=11,b=22; /*定义两个整型变量a和b*/ int*p1,*p2; /*定义两个指针变量p1和p2*/ p1=&a;p2=&b; /*p1和p2初始化为变量a和b的地址*/ swap(p1,p2); /*调用swap,实现变量a和b的值的互换,用指针作为实参*/ printf("a=%d,b=%d\n",a,b); /*输出a和b的值*/ printf("*p1=%d,*p2=%d\n",*p1,*p2); /*输出p1和p2所指向变量的值*/}运行程序,结果是:
a=22,b=11 *p1=22,*p2=11 案例分析如果把swap()函数改成下面的代码,请考虑能否实现a和b互换。
voidswap(int*pa,int*pb) { int*temp; temp=pa; pa=pb; pb=temp; }1、编程从键盘输入两个数,用函数调用的方式(用指针作为函数参数),实现两个数由大到小排序。2、编程从键盘输入三个数,用函数嵌套调用的方式,先调用三个数交换的函数,再在三个数交换的函数中调用两个数交换的函数(调用过程中都用指针作为函数参数)。 随堂练习函数的参数传递,有两种方式,一种是传值方式,另一种是传址方式。对于变量来说,传值方式就是把变量的值作为参数传递,比如用变量名作为参数就是这种方式;传址方式就是把变量的地址作为参数传递,比如用指针指向某一变量,然后用指针作为参数,另外,用数组名作为参数也是传址方式。知识链接学习目标掌握指向数组的指针以及指针和数组名的关系。掌握指向数组元素的指针以及通过指针引用数组元素。指针和数组之间存在特殊的关系。一个变量有一个地址,一个数组包含若干个元素,每个数组元素都在内存中占有相应的内存单元,都有相应的地址。因此指针变量可以指向数组,也可以指向数组元素。所谓数组的指针就是数组的起始地址,数组元素的指针就是数组元素的地址。我们把指向数组的指针变量称为“指向数组的指针”,把指向数组元素的指针变量称为“指向数组元素的指针”。8.3指针与数组案例8-9定义一个数组,然后定义一个指针指向该数组。8.3.1指向数组的指针1、一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。数组的元素按顺序存储在内存单元中,它的第一个元素(如array[0])存储在首地址对应的内存单元中,后面的数组元素(下标大于0)存储在后续的内存单元中。例如定义一个整型数组:
intarray[5];案例分析1、一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。数组的元素按顺序存储在内存单元中,它的第一个元素(如array[0])存储在首地址对应的内存单元中,后面的数组元素(下标大于0)存储在后续的内存单元中。例如定义一个整型数组:
intarray[5];假定array数组的起始地址是1000,存放形式如图提问:请考虑下面的关系是否成立?(1)array==1000(2)&array[0]==1000(3)array==&a[0](4)&array[1]==10022、在清楚了数组在内存中的存储形式以后,下一步我们就可以定义指针来指向数组了。在前面已经定义了数组array[],再定义指针p指向数组array。
intarray[5]; int*p; /*定义p为指向整型变量的指针*/这里定义指针p为整型的原因就是因为数组array是int型,所以p也应为指向int型的指针变量。为了使指针p指向数组array,需要把数组的起始地址赋给指针变量p,以下两种方法都是等价的。(1)p=&array[0];(2)p=array;也可以在定义的同时赋初值给指针变量p:(1)int*p=&array[0]; /*求得第一个元素array[0]的地址赋给p*/(2)int*p=array; /*数组名a相当于数组的首地址,赋给p*/提示:(1)指针变量经过定义和赋初值以后,由于p的初值被赋为数组的起始地址,所以指针变量p、数组名array、&array[0]三者的值都是1000,如图8-15所示。指针变量p、数组名array、&array[0]均指向同一单元,它们是数组的首地址,也就是数组的第一个元素array[0]的地址。(2)注意数组名和数组元素的地址是常量,而指针变量是变量。1、指针变量指向数组首地址的方式如下:方式1:类型说明符*指针变量=数组名(或&数组名[0]); /*在定义指针的同时赋初值*/方式2:指针变量=数组名(或&数组名[0]); /*在定义了指针变量后*/2、指针变量指向数组某个元素的方式如下:方式1:类型说明符*指针变量=&数组名[下标]; /*在定义指针的同时赋初值*/方式2:指针变量=&数组名[下标]; /*在定义了指针变量后*/知识链接有两种方法可用于引用一个数组元素,即下标法和指针法。案例8-10定义数组并给数组赋初值,用下标法引用数组的元素并输出数值中各元素的值。8.3.2通过指针引用数组元素main(){intarray[5]={1,2,3,4,5}; /*定义循环变量i*/inti; /*定义数组array并初始化*/for(i=0;i<5;i++) /*用循环方式输出数组各元素的值*/printf(“array[%d]=%d”,i,array[i]);/*用下标法引用数组的元素*/}运行程序,结果是:
array[0]=1array[1]=2array[2]=3array[3]=4array[4]=5案例程序定义数组并给数组赋初值,用指针法引用数组的元素并输出数值中各元素的值。案例8-11main(){inti; /*定义循环变量i*/intarray[5]={1,2,3,4,5}; /*定义数组array并初始化*/int*p=array; /*定义指针p并指向数组的起始地址*/for(i=0;i<5;i++) /*用循环方式输出数组各元素的值*/printf("array[%d]=%d",i,*(p+i)); /**(p+i)就是用指针引用数组元素*/}运行程序,结果同上一个案例一样。 案例程序1、p指向了数组array的首地址后,p就指向了数组的第一个元素array[0],那么p+1就表示指向数组的下一个元素即第二个元素array[1,以此类推。2、既然p+i表示指向数组中的第i+1个元素,即p+i表示第i+1个元素的地址,则第i+1个元素可以表示为*(p+i)。如*p表示第一个元素array[0],*(p+1)表示第二个元素array[1]。3、程序中for循环循环五次,每次输出*(p+i)的值,分别是输出array[0]、array[1]、array[2]、array[3]、array[4]的值。4、当指针指向数组的首地址后,对下标为i的数组元素的引用有下面四种方法:(1)*(指针变量+i)(2)*(数组名+i)(3)指针变量[i](4)数组名[i] 案例分析提示:1、数组array中的下标为i(即第i+1个)的元素的地址有如下几个表示形式:p+i、array+i、&p[i]。这里假定p指向数组的首地址。2、数组array中的下标为i(即第i+1个)的元素有如下几个表示形式:p[i]、array[i]、*(p+i)、*(array+i)。引用数组元素可以用下标法,即通过表示某一个数组元素在数组中有序排列位置的下标去访问该数组元素,如array[5];也可以用指针法,即通过指向数组元素的指针去访问它。也就是说任何能由数组下标完成的操作也都可用指针来实现,而且在程序中使用指针不仅使代码更紧凑、灵活,而且还可使目标代码占用内存少、运行速度快。提问:占用内存少、运行速度快?知识链接编程实现通过指针引用、数组名及下标引用等方法引用数组元素案例8-12main(){intarr[5],i,*p; /*定义数组arr,循环变量i和指针p*/p=arr; /*p指向数组的首地址*/for(i=0;i<5;i++)scanf("%d",p+i); /*从键盘输入数据赋值给数组的各个元素*/for(i=0;i<5;i++)printf("arr[%d]\t=%d\t",i,arr[i]); /*下标法*/ 案例程序
printf("\n");for(;p<arr+5;p++)printf("*p\t=%d\t",*p); /*指针法*/printf("\n");for(i=0;i<5;i++)printf("*(arr+%d)\t=%d\t",i,*(arr+i)); /*数组名变量表示法*/
printf("\n");for(p=arr,i=0;i<5;i++)printf("*(p+%d)\t=%d\t",i,*(p+i)); /*指针变量表示法*/printf("\n");for(i=0;i<5;i++)printf("p[%d]\t=%d\t",i,p[i]); /*指针下标表示法*/}2、语句scanf(“%d”,p+i);也可以改成scanf(“%d”,arr+i);或scanf(“%d”,&arr[i]);3、各个输出语句分别采用不同的引用方法,arr[i]是使用下标法,即数组名[i]方法;*p是使每次循环指针变量做加1操作指向下一个数组元素的方法;*(arr+i)是使用*(数组名+i)的方法;*(p+i)是使用*(指针变量+i)的方法;p[i]是使用指针变量[i]的方法。案例分析1、利用指针变量引用数组各个元素并输出。2、从键盘输入5门课程的成绩,利用函数求平均成绩(用指针作为函数参数)。3、编程将0到9存储到一个一维数组中并输出,代码如下:
main() { int*p,i,a[10]; p=a; for(i=0;i<10;i++,p++) *p=i; for(i=0;i<10;i++,p++) printf(“a[%d]=%d\n”,i,*p); }上机调试该程序,并找出程序的错误。随堂练习提示:指针p是变量,在本例中它的值是不断改变的,在执行第二个for循环时,指针已经指向了哪里呢?案例8-13已知一个一维数组a[10],求其中的最大数和最小数并输出。8.3.3数组名与指针变量作为函数参数intmax,min; /*定义变量max和min保存最大和最小数*/voidmax_min(intarray[],int);main(){inti,a[10];printf("Pleaseinput10numbers:");for(i=0;i<10;i++)scanf("%d",a+i); /*输入十个数*/max_min(a,10); /*调用函数,数组名为实参*/printf("\nmax=%d,min=%d\n",max,min);} 案例程序voidmax_min(intarray[],intn) /*形参array为数组名*/{int*p;max=min=*array; /*假设第一个数为最大最小*/for(p=array+1;p<array+n;p++) /*循环与其它数比较*/if(*p>max)max=*p; /*改变最大最小变量的值*/elseif(*p<min)min=*p;}运行程序,从键盘输入五个整数:67891012345↙结果是:
Pleaseinput10numbers:67891012345 max=10,min=11、定义变量max和min用来保存最大数和最小数,而且为全局变量,所以在各个函数中均可访问引用。2、输入语句scanf(“%d”,a+i);灵活的运用了数组名。3、语句max_min(a,10); a作为数组名作为函数的参数,将a数组的首地址传递给形参array。4、函数max_min()中的语句max=min=*array;中的array是形参数组名,它接收实参数组a的首地址,*array相当于引用对应地址单元的内容,也就是数组的第一个元素,该语句等价于max=min=array[0]。5、在执行函数max_min()中的for循环时,p的初值为array+1,也就是p指向array[1](即a[1])。以后每次执行p++,使p指向下一个元素。案例分析1、用数组名作为函数参数,在函数调用时,是把实参数组的首地址传递给形参数组,使得两个数组共同占用同一段内存空间,这样形参数组中的元素值如果发生变化就会使实参数组的元素值也同时发生变化。数组名代表的是数组的首地址,也是第一个元素的地址。在实际应用中,既可以用数组名作为函数参数,也可以用指向数组的指针变量作为函数参数,在函数调用时,传递的都是数组的起始地址。知识链接2、归纳起来,如果有一个实参数组,想在函数中对此数组进行访问,比如查询、修改该数组元素的值等,一般就采用传址方式,也就是传递数组的首地址,形参接收数组的首地址后,也就和实参一样,共同占用相同的内存单元,对形参数组的改变,也就相应地改变了实参数组。这种传递数组的首地址的方式有以下四种。知识链接(1)形参和实参都用数组名。例如:main(){ inta[5]; …… f(a);}f(intb[]){ ……}(2)实参用数组名,形参用指针变量。例如:main(){ inta[5]; …… f(a);}f(int*pf){ ……}(3)实参、形参都用指针变量。例如:main(){ inta[5],*p; p=a; …… f(p);}f(int*pf){ ……}(4)实参为指针变量,形参为数组名。例如:main(){ inta[5],*p; p=a; …… f(p); }f(intb[]) { …… }定义一个有11个元素的数组,其中前10个元素为10个整数,求该数组前n个元素的和。要求:1、n的值由键盘输入,输入值如果大于10则提示重新输入。2、采用上面介绍的四种参数传递方式,在函数中求和,求得的结果保存在数组的最后一个元素中。3、在主函数中输出求得的和。随堂练习学习目标掌握指针指向字符串的方法;掌握使用指针处理字符串。指针可以指向数组,那么指针是否可以指向字符串呢?实际上,指针是非常灵活的,它可以指向任何数据类型。如果需要指针指向某数据类型,只需要该数据类型的变量或常量的起始地址赋值给指针就可以了。字符串在C语言中是用一维数组来表示的,也是数组的一种类型。只不过该数组的每个元素是字符罢了,所以指针当然可以指向字符串。既然指针可以指向字符串,那么就可以通过指针来实现对字符串的操作。8.4指针与字符串案例8-14用指针指向字符串常量并输出字符串。案例程序#include<stdio.h>voidmain(){ char*ptr="YouandMe"; /*指针指向字符串*/ printf("%s\n",ptr); /*输出字符串*/}运行程序,结果是输出字符串:YouandMe8.4.1指针指向字符串常量1、程序没有定义字符数组,而是使用字符串常量,同字符数组一样,字符串常量也是在内存中分配一块连续空间来存放字符串常量。语句char*ptr="YouandMe";在定义指针变量ptr的同时,把字符串常量的首地址赋给ptr,也就是第一个字符‘Y’的地址赋给ptr,使ptr指向该字符串。使用输出字符串格式符%s,对应输出ptr指向的字符串常量。这条语句还可以写成:
char*ptr;ptr="YouandMe";案例分析2、如printf("%s\n",ptr);语句所示,输出字符串可以使用以下格式:
printf(“%s”,指针变量);输出格式控制符%s表示输出一个字符串,给出指向字符串的指针变量名(这里是指向字符串的指针变量ptr),则系统先输出它所输出它所指向的第一个字符,然后自动使指针变量加1,使之指向下一个字符,输出下一个字符,依此类推,直到遇到字符串结束标志‘\0’,注意:字符串在内存中都会由系统自动添加一个结束标识符‘\0’。将指针指向字符串常量有两种方法:1、在定义指针变量的同时赋初值,格式为:
char*指针变量="字符串常量";2、在定义指针变量之后,再单独给指针变量赋初值的方法。格式为:
char*指针变量;指针变量="字符串常量";知识链接注意:第二种方法不可以采用下列方法,例如:
char*p; *p="YouandMe"; 想想为什么?案例8-15用户由键盘输入密码,判断密码长度是否等于6,如果不等,则提示用户重新输入。使用指向字符数组的指针变量。8.4.2指针指向字符数组#include<stdio.h>main(){chara[10],*p;intn=0;p=a;while(1){gets(a); /*输入一个字符串存入字符数组a中*/printf("Thestringis:%s\n",p);/*输出从键盘输入的字符串*/案例程序for(;*p!='\0';p++) /*通过for循环计算输入字符个数*/ n++;if(n!=6) printf("Pleaseinputagain:"); /*如果输入字符数不是6个,提示重新输入*/elsebreak; /*否则退出while循环,向下执行*/}}运行程序,从键盘输入字符串“China”,则输出后,再提示重新输入,再输入字符串“flower”。运行结果是:
China Thestringis:China Pleaseinputagain:flower Thestringis:flower1、定义数组a后,语句p=a使指针变量p保存数组a的首地址,即p指向a。2、首先调用函数gets(a)从键盘输入字符串保存在字符数组a中,然后再输出刚输入的字符串a3、循环语句for(;*p!='\0';p++)n++;通过for循环计算输入字符个数。每次统计一个字符后,指针p值自加1,指向下一个字符,直到遇到结束符‘\0’。4、语句if(n!=6)printf("Pleaseinputagain:");表示如果输入字符数不是6个,提示重新输入。案例分析字符串是按照字符数组存储的,所以对字符串中字符的引用也可以用下标法和指针法。知识链接案例8-16输入一段字符,统计其中所包含的单词数量。知识链接#include<stdio.h>#include<string.h>intcount(char*); /*统计个数函数count声明*/main(){charstr[100]; /*定义数组str来保存输入的字符串*/intn; /*整型变量n保存单词个数*/printf("Pleaseinputastring:\n");gets(str); /*输入字符串,保存在字符数组str中*/n=count(str); /*调用函数count,统计单词个数*/printf("Thewordsinstrare:%d\n",n); /*输出个数*/}案例程序intcount(char*p) /*形参指针p也指向输入的字符串*/{intnum=0,flag; /*num存储单词数,flag为标志变量*/flag=0; /*先把flag赋初始值为0*/while(*p!='\0') /*字符不是结束符就继续循环*/{if(*p=='')flag=0; /*遇到空格,flag置0*/elseif(flag==0) /*当前字符不是空格,而且flag为0,说明遇到一个新单词*/{num++; /*单词个数加1*/flag=1; /*标志flag置1,表示新单词已统计*/}p++; /*指针p加1,指向下一个字符*/}return(num); /*返回字符个数*/}运行程序,从键盘输入字符串“Iwouldliketobeaprogrammer!”输出结果是:
Pleaseinputastring: Iwouldliketobeaprogrammer! Thewordsinstrare:7提示:标志变量就是编程时用来对情况的判断的标志,例如本例flag变量就表示值为0表示前一个字符是空格,如果为1则表示前一个字符是非空格字符或是还没开始统计。这样通过对标志变量的值的判断,就可以很好的区分是否遇到了一个新的单词,从而进行正确的统计。定义两个字符数组,分别存储两个字符串,用指针指向字符串,将其中一个字符串复制到另外一个字符串。随堂练习使用字符串指针变量与字符数组的区别1、字符串指针变量本身是一个变量,用于存放字符串的首地址。也就是说字符串指针变量只能指向字符串,而不能存放字符串。字符数组则不同,它在内存中表示一段连续的存储空间,可以用来存放整个字符串。2、对字符串指针变量赋初值有两种方式:(1)在定义变量的同时赋初值,例如:
char*p="It’salongroadtogo.“(2)在定义后初始化,例如把上面语句改为:char*p;p="It’salongroadtogo."知识链接3、对字符数组的情况就不同了。对字符数组也同样可以在定义的同时进行初始化,例如:charstr[80]="Hardworkcanmakeupforalackofintelligence."但不能写成:charstr[80];str="Hardworkcanmakeupforalackofintelligence."如果采用先定义后初始化的方式,则只能采用对字符数组的各元素逐个赋值的方式。4、字符数组一经定义后,编译时就会为它分配一段连续的内存单元,所以它有确定的地址,字符数组名就代表该段内存单元的起始地址。而定义指针变量时,给指针变量分配内存单元,在其中存放一个地址值,但是该地址值是不确定的,也就是指针变量的指向是不确定的,在把字符串的首地址赋值给指针变量后,该指针才确定保存字符串的首地址。知识链接5、指针变量的值是可以改变的。例如:char*p="Yesterdayoncemore."p+=10;printf(“%s”,p);指针p开始指向字符‘Y’,然后指针p的值加10,则指针p指向“once”中的字符‘o’,最后输出结果应该是:oncemore.而数组名代表数组的首地址,它本身的值是不可以改变的。例如下面语句是错误的。charstr[]="NowandThen."str++;printf(“%s”,str);学习目标掌握指针型函数的用法。掌握用函数指针调用函数以及用函数指针作为函数参数。指针函数就是函数返回值为指针型的函数,而函数指针就是指向函数的指针。8.5指针函数与函数指针案例8-17利用函数返回的指针引用对应的数据:输入1~5之间的数字,输出对应的人名。8.5.1指针函数char*spstars(intn){staticcharstars[5][10]={"Jordan","James","Kobe","O'neil","Wade"};return(stars[n-1]); /*返回二维数组第n行的首地址*/}案例程序main(){inti;char*p;printf("Inputyourfavoriteplayer'snumber:");scanf("%d",&i); /*输入对应的号码*/ p=spstars(i); /*函数返回的地址保存在指针变量p中*/printf("No.%disyourfavoriteplayer:%s\n",i,p); /*输出序号和对应的人名*/}运行程序,从键盘输入序号,比如1,则输出结果是:
Inputyourfavoriteplayer’snumber:1 No.1isyourfavoriteplayer:Jordan1、本案例定义了一个指针函数(指针型函数)spstars,它的返回值是地址。2、语句staticcharstars[5][10]={“Jordan”,“James”,“Kobe”,“O‘neil”,“Wade”};定义了一个静态二维字符数组stars.3、返回语句return(stars[n-1]);返回二维数组第n行的首地址,在二维数组中stars中,stars[行下标]表示该行的首地址,也就是一个名字字符串的首地址。用n-1来表示下标的原因是数组下标是从0开始的,比如序号为1的对应行下标应该是0,序号为n的行下标应该是n-1。案例分析在函数spstars中,也可以定义一个局部指针变量,用该指针变量保存对应名字字符串的首地址,在返回时,返回该指针变量的值即可,指针型函数spstars代码修改如下:char*spstars(intn){char*p;staticcharstars[5][10]={"Jordan","James","Kobe","O'neil","Wade"};p=stars[n-1]; /*指针p保存对应行的首地址*/return(p); /*返回指针p的值就是第n行的首地址*/}提示编写一个指针型函数,求出一个整数数组中的最大数的地址并返回该地址,然后在main函数中输出数组中的最大数的值和对应的下标号。随堂练习1、在C语言中,允许一个函数的返回值是一个指针(即地址)。2、指针函数的一般形式是: 类型说明符*函数名(形参表)
{
函数体
}指针函数的定义格式和前面介绍的一般函数定义格式基本相同,唯一的区别是在函数名前加一个“*”用来表示函数的返回值是指针类型(即地址)。例如:知识链接
float*funcp()
{
函数体
}这里类型说明符“float”和“*”表示函数返回值是一个指针,该指针是指向浮点型(float)的指针。在C语言中,和数组类似,函数也是占有一段连续的内存空间,同样函数名就是该函数所占内存空间的首地址。可以把函数的首地址保存在一个指针变量中,使该指针变量指向该函数,然后就可以通过该指针找到并调用这个函数。指向函数的指针就是函数指针。8.5.2函数指针案例8-18用函数指针调用函数,求两个数中较大的数。案例8-18#include<stdio.h>intmax(intx,inty){return(x>y?x:y);}voidmain(){int(*p)(); /*定义指向函数的指针变量*/案例程序inta,b,c;p=max; /*将max函数的入口地址赋给函数指针*/scanf("%d,%d",&a,&b); /*输入两个数*/c=(*p)(a,b); /*通过指针调用函数max*/printf("a=%d,b=%d,max=%d\n",a,b,c);}运行程序,结果是: 输入:5,9↙
输出:a=5,b=9,max=91、主函数中int(*p)();定义指向函数的指针变量p,它所指向的函数的返回值是int型。2、赋值语句p=max;表示将函数的入口地址赋给p。3、调用函数用语句c=(*p)(a,b);它等价于c=max(a,b);案例分析案例分析1、主函数中int(*p)();定义指向函数的指针变量p,它所指向的函数的返回值是int型。2、赋值语句p=max;表示将函数的入口地址赋给p。3、调用函数用语句c=(*p)(a,b);它等价于c=max(a,b);定义一个一维数组,通过函数指针调用函数,求数组的最大元素的值。随堂练习1、用函数指针调用函数(*指针变量名)(实参表)
2、函数指针变量定义的一般形式是:类型说明符(*指针变量名)(参数);int(*p)();3、用函数指针调用函数的步骤如下:知识链接①先定义函数指针变量,如“int(*p)();”,定义p为函数指针。②给函数指针变量赋值为被调用函数的入口地址,形式为: 指针变量名=函数名; 如:p=max;③经过步骤(1)、(2)后,就可以通过函数指针调用函数,如:(*p)(a,b); 提示:1、int*p();表示指针型函数,即p是函数名,该函数返回值是指针(地址)。2、int(*p)();表示函数指针,即p是指向函数的指针,该函数返回值是一个整数(值)。编写一个函数calculation()做算术四则运算,每次调用实现不同的功能,输入两个数,第一次调用实现两个数的加法,第二次实现两个数的减法,第三次实现两个数的乘法,第四次实现两个数的除法。用函数指针作为参数,即调用函数calculation()时,用加减乘除四个函数的函数名作为函数实参,在函数calculation()中,
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 智慧解决方案:智能城市管理
- 消防应急避险
- 3.2.3离子反应 课件 高一上学期化学苏教版(2019)必修第一册
- 糖尿病个人教育与护理
- 传统毛笔课件教学课件
- 日常生活食品安全
- 生产安全事故案例培训教材
- 布谷鸟节奏游戏教案反思
- 弧度制说课稿
- 海水的运动说课稿
- 夏商周考古课件 第4章 殷墟文化(4-6节)
- JJG 667-2010液体容积式流量计
- GB/T 708-2019冷轧钢板和钢带的尺寸、外形、重量及允许偏差
- GB/T 6072.4-2012往复式内燃机性能第4部分:调速
- GB/T 1927.5-2021无疵小试样木材物理力学性质试验方法第5部分:密度测定
- GB/T 17395-2008无缝钢管尺寸、外形、重量及允许偏差
- GB/T 14996-2010高温合金冷轧板
- 【写作讲座】如何提升高中英语写作能力
- 公路工程概论全套课件
- 全文《中国式现代化》PPT
- 《红楼梦》深入研读学习任务群设计
评论
0/150
提交评论