C程序设计第四版第10章 对文件的输出和输入.docx
《C程序设计第四版第10章 对文件的输出和输入.docx》由会员分享,可在线阅读,更多相关《C程序设计第四版第10章 对文件的输出和输入.docx(20页珍藏版)》请在冰豆网上搜索。
C程序设计第四版第10章对文件的输出和输入
第十章对文件的输入和输出
10.1C文件的有关基本知识
10.1.1什么是文件
10.1.2文件名
10.1.3文件的分类
10.1.4文件名文件的缓冲区
10.1.5文件类型指针
10.2打开与关闭文件
10.2.1用fopen函数打开数据文件
10.2.2用fclose函数关闭数据文件
10.3顺序读写数据文件
10.3.1怎样向文件读写字符
10.3.2怎样向文件读写一个字符串
10.3.3用格式化的方式读写文件
10.3.4用二进制方式向文件读写一组数据
10.4随机读写数据文件
10.4.1文件位置标记及其定位
10.4.2随机读写
10.5文件读写的出错检测
10.1C文件的有关基本知识
10.1.1什么是文件
文件有不同的类型,在程序设计中,主要用到两种文件:
程序文件。
包括源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为.exe)等。
这种文件的内容是源程序代码。
数据文件。
文件的内容不是程序,而是供程序运行时读写的数据,如在程序运行过程中输出到磁盘(或其他外部设备)的数据,或在程序运行过程中供读入的数据。
如一批学生的成绩数据,或货物交易的数据等。
本章主要讨论的是数据文件。
在以前各章中所处理的数据的输入和输出,从终端的键盘输入数据,运行结果输出到终端显示器上。
常常需要将一些数据输出到磁盘上保存起来,以后使用,这就要用到磁盘文件。
“文件”指存储在外部介质上数据的集合。
一批数据是以文件的形式存放在外部介质(如磁盘)上的。
操作系统是以文件为单位对数据进行管理,想找存放在外部介质上的数据,先按文件名找到所指定的文件,然后再从该文件读数据。
要向外部介质上存储数据也必须先建立一个文件(以文件名作为标志),才能向它输出数据。
输入输出是数据传送的过程,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流(stream),即数据流。
流表示了信息从源到目的端的流动。
输入操作时,数据从文件流向计算机内存,输出操作时,数据从计算机流向文件(如打印机、磁盘文件)。
无论是用Word打开或保存文件,还是C程序中的输入输出都是通过操作系统进行的。
“流”是一个传输通道,数据可以从运行环境流入程序中,或从程序流至运行环境。
从C程序的观点来看,无论程序一次读写一个字符,或一行文字,或一个指定的数据区,作为输入输出的各种文件或设备都是统一以逻辑数据流的方式出现的。
C语言把文件看作是一个字符(或字节)的序列。
一个输入输出流就是一个字符流或字节(内容为二进制数据)流。
C的数据文件由一连串的字符(或字节)组成,而不考虑行的界限,两行数据间不会自动加分隔符,对文件的存取是以字符(字节)为单位的。
输入输出数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制,这就增加了处理的灵活性。
这种文件称为流式文件。
件。
10.1.2文件名
文件要有一个唯一的文件标识,以便用户识别和引用。
文件标识包括三部分:
文件路径;
(2)文件名主干;
(3)文件后缀。
文件路径表示文件在外部存储设备中的位置。
如:
D:
\CC\temp\file1.dat
表示file1.dat文件存放在D盘中的CC目录下的temp子目录下面。
10.1.3文件的分类
根据数据的组织形式,数据文件可分为ASCII文件和二进制文件。
数据在内存中是以二进制形式存储的,如果不加转换地输出到外存,就是二进制文件。
如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。
ASCII文件又称文本文件,每一个字节放一个字符的ASCII代码。
字符一律以ASCII形式存储。
数值型数据既可以用ASCII形式存储,也可以用二进制形式存储。
如有整数10000,如果用ASCII码形式输出到磁盘,则在磁盘中占5个字节(每一个字符占一个字节),而用二进制形式输出,则在磁盘上只占4个字节(用VC++C时)。
10.1.4文件缓冲区
ANSIC标准采用“缓冲文件系统”处理数据文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。
从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。
如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量)。
10.1.5文件类型指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。
这些信息是保存在一个结构体变量中的。
该结构体类型是由系统声明的,取名为FILE。
例如有一种C编译环境提供的stdio.h头文件中有以下的文件类型声明:
typedefstruct
{shortlevel;//缓冲区“满”或“空”的程度
unsignedflags;//文件状态标志
charfd;//文件描述符
unsignedcharhold;//如无缓冲区无内容不读取字符
shortbsize;//缓冲区的大小
unsignedchar*buffer;//数据缓冲区的位置
unsignedchar*curp;//指针当前的指向
unsignedistemp;//临时文件指示器
shorttoken;//用于有效性检查
}FILE;
声明FILE结构体类型的信息包含在头文件“stdio.h”中。
一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。
FILE结构体类型:
FILEf1;定义了一个结构体变量,用它存放一个文件的有关信息。
这些信息是在打开文件时由系统根据文件的情况自动放入的,用户不必过问。
FILE*fp;定义一个指向FILE类型数据的指针变量。
可以使fp指向某一个文件的文件信息区(是一个结构体变量),通过该文件信息区中的信息就能够访问该文件。
也就是说,通过文件指针变量能够找到与它关联的文件。
如果有n个文件,应设n个指针变量,分别指向n个FILE类型变量,以实现对n个文件的访问。
10.2打开与关闭文件
对文件读写之前应该“打开”该文件,在使用结束之后应“关闭”该文件。
所谓“打开”是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据)。
在编写程序时,在打开文件的同时,一般都指定一个指针变量指向该文件,也就是建立起指针变量与文件之间的联系,这样就可以通过该指针变量对文件进行读写。
所谓“关闭”是指撤销文件信息区和文件缓冲区。
10.2.1用fopen函数打开数据文件
fopen函数的调用方式为
fopen(文件名,使用文件方式);
例如:
fopen(“al”,“r”);
表示要打开名为“a1”的文件,使用文件方式为“读入”(r代表read,即读入)。
fopen函数的返回值是指向a1文件的指针(即al文件信息区的起始地址)。
通常将fopen函数的返回值赋给一个指向文件的指针变量。
如:
FILE*fp;//定义一个指向文件的指针变量fp
fp=fopen(“a1”,”r”);//
fp和文件a1相联系,fp指向了a1文件。
在打开一个文件时,通知编译系统以下3个信息:
①需要访问的文件的名字;②使用文件的方式(“读”还是“写”等);③让哪一个指针变量指向被打开的文件。
使用文件方式见表10.1。
表10.1使用文件的方式
文件使用方式
含义
“r”(只读)
打开一个已存在的文本文件,只能从中读取数据
“w”(只写)
打开或新建一个文本文件,只能写入数据;若文件已存在则覆盖原全部内容
“a”(追加)
打开已存在的文本文件,并在文件末尾写入数据
“rb”(只读)
打开一个已存在的二进制文件,只能从中读取数据
“wb”(只写)
打开或新建一个二进制文件,只能写入数据;若文件已存在则覆盖原全部内容
“ab”(追加)
打开已存在的二进制文件,并在文件末尾写入数据
“r+”(读写)
打开一个已存在的文本文本文件,允许读写数据,写入时只依次覆盖指针访问到的位置,不会覆盖全部内容
“w+”(读写)
打开或新建一个文本文件,允许读写,若文件已存在,写入时覆盖文件全部内容
“a+”(读写)
打开一个已存在的文本文件,可以读出数据也可以在文件末尾写入数据
“rb+”(读写)
打开或新建一个二进制文件,允许读写,若文件已存在,写入时覆盖文件全部内容
“rw+”(读写)
打开或新建一个二进制文件,允许读写,若文件已存在,写入时覆盖文件全部内容
“ra+”(读写)
打开一个已存在的二进制文件,可以读出数据也可以在文件末尾写入数据
说明:
(1)用“r”方式打开的文件只能用于向计算机输入而不能用作向该文件输出数据,而且该文件应该已经存在,并存有数据,这样程序才能从文件中读数据。
不能用“r”方式打开一个并不存在的文件,否则出错。
(2)用“w”方式打开的文件只能用于向该文件写数据(即输出文件),而不能用来向计算机输入。
如果原来不存在该文件,则在打开文件前新建立一个以指定的名字命名的文件。
如果原来已存在一个以该文件名命名的文件,则在打开文件前先将该文件删去,然后重新建立一个新文件。
(3)如果希望向文件末尾添加新的数据(不希望删除原有数据),则应该用“a”方式打开。
但此时应保证该文件已存在;否则将得到出错信息。
打开文件时,文件读写标记移到文件末尾。
(4)用r+、w+、a+方式打开的文件既可以用来输入数据,也可以用来输出数据。
用r+方式时该文件应该已经存在。
用w+方式则新建立一个文件,先向此文件写数据,然后可以读此文件中的数据。
用a+方式打开的文件,原来的文件不被删去,文件读写位置标记移到文件末尾,可以添加,也可以读。
(5)如果打开失败,fopen函数将会带回一个出错信息。
fopen函数将带回一个空指针值NULL。
常用下面的方法打开一个文件:
if((fp=fopen(“file1”,’r″))==NULL)
{printf(“cannotopenthisfile\n”);
exit(0);
}
(6)C标准建议用表10.1列出的文件使用方式打开文本文件或二进制文件,但目前使用的有些C编译系统可能不完全提供所有这些功能。
(7)计算机输从ASCII文件读入字符时,遇到回车换行符,系统把它转换为一个换行符,在输出时把换行符转换成为回车和换行两个字符。
在用二进制文件时,不进行这种转换,在内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。
(8)程序中可以使用3个标准的流文件:
标准输入流、标准输出流、标准出错输出流。
系统已对这3个文件指定了与终端的对应关系。
标准输入流是从终端的输入,标准输出流是向终端的输出,标准出错输出流是当程序出错时将出错信息发送到终端。
程序开始运行时系统自动打开这3个标准流文件。
因此,程序编写者不需要在程序中用fopen函数打开它们。
所以以前我们用到的从终端输入或输出到终端都不需要打开终端文件。
10.2.2用fclose函数关闭数据文件
关闭文件用fclose函数。
fclose函数调用的一般形式为
fclose(文件指针);
例如:
fclose(fp);
如果不关闭文件将会丢失数据。
10.3顺序读写数据文件
在顺序写时,先写入的数据存放在文件中前面,后写入的数据存放在文件中后面。
在顺序读时,先读文件中前面的数据,后读文件中后面的数据。
对顺序读写来说,对文件读写数据的顺序和数据在文件中的物理顺序是一致的。
顺序读写需要用库函数实现。
10.3.1怎样向文件读写字符
对文本文件读入或输入一个字符的函数见表10.2。
表10.2读写一个字符的函数
函数名
调用形式
功能
返回值
fgetc
fgetc(fp)
从fp指向的文件读入一个字符
读成功,带向所读的字符,失败则返回文件结束来标志EOF(即-1)
fputc
fputc(ch,fp)
把字符ch写到文件指针变量fp所指向的文件中
输出成功,返回值就是输出的字符,输出失败,则返回EOF(即-2)
例10.1从键盘输入一些字符,逐个把它们送到磁盘上去,直到用户输入一个“#”为止。
解题思路:
用fgetc函数从键盘逐个输入字符,然后用fputc函数写到磁盘文件即可。
#include
#include
intmain()
{FILE*fp;
charch,filename[10];
printf("请输入所用的文件名:
");
scanf("%s",filename);
if((fp=fopen(filename,“w”))==NULL)//打开输出文件并使fp指向此文件
{
printf("无法打开此文件\n");//如果打开时出错,就输出“打不开”的信息
exit(0);//终止程序
}
ch=getchar();//用来接收最后输入的回车站
printf(“请输入一个字符串(以#结束):
");
ch=getchar();//接受从键盘输入的第一个字符
while(ch!
=‘#’)//当输入“#”是结束循环
{
fputc(ch,fp);//向键盘文件输入一个字符
putchar(ch);//将输出的字符显示在屏幕上
ch=getchar();//再接收从键盘输入的一个字符
}
fclose(fp);//关闭文件
putchar(10);//向屏幕输出一个换行符
return0;
}
10.3.2怎样向文件读写一个字符串
读写一个字符串的函数。
函数名
调用形式
功能
返回值
fgets
fgets(str,n,fp)
从fp指向的文件读入长度为(n-1)的字符串,存放到字符数组str中
读成功,返回地址str,失败则返回NULL)
fputs
fputs(str,fp)
str所指向的字符串写到文件指针变量fp所指向的文件中
写成功,返回0;否则返回非0值
说明:
(1)fgets函数的函数原型为:
char*fgets(char*str,intn,FILE*fp);
其作用是从文件读入一个字符串。
调用时可以写成:
fgets(str,n,fp);
其中n是要求得到的字符个数,但实际上只读n-1个字符,然后在最后加一个‘\0’字符,这样得到的字符串共有n个字符,把它们放到字符数组str中。
如果在读完n-1个字符之前遇到换行符“\n”或文件结束符EOF,读入即结束,但将所遇到的换行符“\n”也作为一个字符读入。
执行fgets成功,返回str数组首地址,如果一开始就遇到文件尾或读数据错,返回NULL。
(2fputs函数的函数原型为:
intfputs(char*str,FILE*fp);
其作用是将str所指向的字符串输出到fp所指向的文件中。
调用时可以写成:
例如:
fputs(″China”,fp);
fputs函数中第一个参数可以是字符串常量、字符数组名或字符型指针。
字符串末尾的‘\0’不输出。
输出成功,函数值为0;失败,函数值为EOF。
例10.3从键盘读入若干个字符串,对它们按字母大小的顺序排序,然后把排好序的字符串送到磁盘文件中保存。
解题思路:
为解决问题,可分为三个步骤:
(1)从键盘读入n个字符串,存放在一个二维字符数组中,每一个一维数组存放一个字符串;
(2)对字符数组中的n个字符串按字母顺序排序,排好序的字符串仍存放在字符数组中;
(3)将字符数组中的字符串顺序输出。
编写程序:
#include
#include
#include
intmain()
{FILE*fp;
charstr[3][10],temp[10];//str是用来存放字符串的二维数组,temp是临时数组
inti,j,k,n=3;
printf(“Enterstrings:
\n”);//提示输入字符串
for(i=0;igets(str[i]);//输入字符串
for(i=0;i{k=i;
for(j=i+1;jif(strcmp(str[k],str[j])>0)k=j;
if(k!
=i)
{strcpy(temp,str[i]);
strcpy(str[i],str[k]);
strcpy(str[k],temp);}
}
if((fp=fopen(“D:
\\CC\\string.dat”,“w”))==NULL)//打开磁盘文件
{printf("can'topenfile!
\n");exit(0);}
printf("\nThenewsequence:
\n");
for(i=0;i{fputs(str[i],fp);
fputs(“\n”,fp);//向磁盘文件写一个字符串,然后输出一个换行符
printf(“%s\n”,str[i]);//在屏幕上显示
}
return0;
}
10.3.3用格式化的方式读写文件
一般调用方式为:
fprintf(文件指针,格式字符串,输出表列);
fscanf(文件指针,格式字符串,输入表列);
例如:
fprintf(fp,”%d,%6.2f”,i,f);
fscanf(fp,”%d,%f”,&i,&f);
10.3.4用二进制方式向文件读写一组数据
一般调用形式为:
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中:
buffer:
是一个地址。
对fread来说,它是用来存放从文件读入的数据的存储区的地址。
对fwrite来说,是要把此地址开始的存储区中的数据向文件输出。
size:
要读写的字节数。
count:
要读写多少个数据项。
fp:
FILE类型指针。
例10.4从键盘输入10个学生的有关数据,然后把它们转存到磁盘文件上去。
解题思路:
定义有10个元素的结构体数组,用来存放10个学生的数据。
从main函数输入10个学生的数据。
用save函数实现向磁盘输出学生数据。
用fwrite函数一次输出一个学生的数据。
编写程序:
#include
#defineSIZE10
structStudent_type
{charname[10];
intnum;
intage;
charaddr[15];
}stud[SIZE];//定义全局结构体数组stud,包含10个学生的数据
voidsave()//定义函数save,向文件输出SIZE个学生的数据
{FILE*fp;
inti;
if((fp=fopen("stu.dat","wb"))==NULL)//打开输出文件stu.dat
{printf("cannotopenfile\n");
return;
}
for(i=0;iif(fwrite(&stud[i],
sizeof(structStudent_type),
1,fp)!
=1)
printf("filewriteerror\n");
fclose(fp);
}
intmain()
{inti;
printf(“enterdataofstudents:
\n");
for(i=0;iscanf("%s%d%d%s",stud[i].name,&stud[i].num,&stud[i].age,stud[i].addr);
save();
return0;
}
10.4随机读写数据文件
对文件进行顺序读写比较容易理解,也容易操作,但有时效率不高。
随机访问不是按数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数据进行访问,显然这种方法比顺序访问效率高得多。
10.4.1文件位置标记及其定位
1.文件位置标记
为了对读写进行控制,系统为每个文件设置了一个文件读写位置标记(简称文件标记),用来指示“接下来要读写的下一个字符的位置”。
一般情况下,在对字符文件进行顺序读写时,文件标记指向文件开头,进行读的操作时,就读第一个字符,然后文件标记向后移一个位置,在下一次读操作时,就将位置标记指向的第二个字符读入。
依此类推,直到遇文件尾,结束。
如果是顺序写文件,则每写完一个数据后,文件标记顺序向后移一个位置,然后在下一次执行写操作时把数据写入指针所指的位置。
直到把全部数据写完,此时文件位置标记在最后一个数据之后。
可以根据读写的需要,人为地移动了文件标记的位置。
文件标记可以向前移、向后移,移到文件头或文件尾,然后对该位置进行读写——随机读写。
随机读写可以在任何位置写入数据,在任何位置读取数据。
2.文件位置标记的定位
可以强制使文件位置标记指向指定的位置。
可以用以下函数实现:
(1)用rewind函数使文件标记指向文件开头
rewind函数的作用是使文件标记重新返回文件的开头,此函数没有返回值。
例10.5有一个磁盘文件,内有一些信息。
要求第一次将它的内容显示在屏幕上,第二次把它复制到另一文件上。
解题思路:
因为在第一次读入完文件内容后,文件标记已指到文件的末尾,如果再接着读数据,就遇到文件结束标志,feof函数的值等于1(真),无法再读数据。
必须在程序中用rewind函数使位置指针返回文件的开头。
编写程序:
#include
intmain()
{FILE*fp1,*fp2;
fp1=fopen(“file1.dat”,“r”);//打开输入文件
fp2=fopen(“file2.dat”,“w”);//打开输出文件
while(!
feof(fp1))putchar(getc(fp1));//逐个读入字符并输出到屏幕
putchar(10);//输出一个换行
rewind(fp1);//使文件位置标记返回文件头
while(!
feof(fp1))putc(getc(fp1),fp2);//从文件头重新逐个读字符,输出到file2文件
fclose(fp1);fclose(fp2);
return0;
}
:
(2)用fseek函数改变文件标记
fseek函数的调用形式为:
fseek(文件类型指针,位移量,起始点)
“起始点”0代表“文件开始位置”,1为“当前位置”,2为“文件末尾位置”。
C标准指