C语言程序设计教程郧阳师范高等专科学校计算机科学系方 斌 制作第 13章 文 件
13.1 概述 13.5 文件的定位
13.2 文件类型指针 13.6 出错的检测
13.3 文件的打开与关闭 13.7 文件输入输出小结
13.4 文件的读写 本章要求与作业
13.1 概 述一、文件的概念
1、“文件 (file)”:是记录在介质上的数据的集合,以文件名作为访问文件的标识。
介质:一般是磁盘、磁带、光盘等。
2、内存文件:在内存中开辟一段空间,以文件的方式存放数据。如、
DOS虚拟磁盘驱动程序 RAMDRIVE.SYS建立的虚拟磁盘上存放的文件。
Windows9.X恢复盘也建立虚拟磁盘,用于临时存放系统命令文件。
3、操作系统把与主机关联的终端也当作文件处理,如:
键盘,输入文件显示器:输出文件打印机:输出文件
4,C语言把文件看作一个字节序列,即由一连串的字节组成,称为“流
(stream)”,以字节为单位访问,没有记录的界限(实际存在界限,例如,在 FAT16文件系统中,文件的最大长度是 2GB)。
5、按文件所依附的介质来分:有磁盘文件、磁带文件、内存文件、设备文件等。
6、按文件的内容区分:有源程序文件、目标文件、数据文件等。
7、按文件中的数据组织形式来分:数据文件可分为 ASCII码文件和二进制文件。 ASCII码文件,又称为“文本文件” (text),其每一个字节存放一个 ASCII码。
二进制文件,把内存中的数据按其在内存中的存储形式存放在磁盘上。
二、文件分类可以从不同的角度对文件进行分类:
( 1)根据文件的内容,可分为程序文件和数据文件,程序文件又可分为源文件、目标文件和可执行文件。
( 2)根据文件的组织形式,可分为顺序存取文件和随机存取文件。
(3)根据文件的存储形式,可分为 ASCII码文件和二进制文件。
ASCII码文件的每 1个字节存储 1个字符,因而便于对字符进行逐个处理。但一般占用存储空间较多,而且要花费转换时间(二进制与 ASCII
码之间的转换)。
二进制文件是把内存中的数据,原样输出到磁盘文件中。可以节省存储空间和转换时间,但 1个字节并不对应 1个字符,不能直接输出字符形式。
例、十进制整数 10000,
在内存中占两字节,其存放形式是,0010,0111,0001,0000。
在二进制文件中也按这中方式存放。
在 ASCII文件中,存放为 31H,30H,30H,30H,30H,占五个字节,
它们分别是 1,0,0,0,0,0字母的 ASCII码。
8、按照操作系统对磁盘文件的读写方式,文件可以分为“缓冲文件系统”和“非缓冲文件系统”。
缓冲文件系统:操作系统在内存中为每一个正在使用的文件开辟一个读写缓冲区。
非缓冲文件系统:操作系统不开辟读写缓冲区。
ANSI C的缓冲文件系统所谓缓冲文件系统是指,系统自动地在内存区为每个正在使用的文件开辟一个缓冲区。
从内存向磁盘输出数据时,必须首先输出到缓冲区中。
待缓冲区装满后,再一起输出到磁盘文件中。
从磁盘文件向内存读入数据时,则正好相反:首先将一批数据读入到缓冲区中,再从缓冲区中将数据逐个送到程序数据区。
要调用一个文件,需要有以下的信息:
13.2 文件类型指针文件当前的读写位置与该文件对应的内存缓冲区的地址缓冲区中未被处理的字符串文件操作方式等缓冲文件系统会为每一个文件系统开辟一个“文件信息区”,在 stdio.h
中,它被定义为 FILE类型数据,用于存放文件的有关信息(如文件名、
文件位置等)
注意:结构类型名,FILE”必须大写。
typedef struct
{
int _fd; /* 文件号 */
int _cleft; /* 缓冲区中剩下的字节数 */
int _mode; /* 文件操作模式 */
char * _nextc; /* 下一个字节的位置 */
char * _buff; /*文件缓冲区位置 */
}FILE;
其中:每个成员就是用来存放有关文件的各种信息的数据项。
定义文件指针变量的一般形式为:
FILE *文件结构指针变量名例如,FILE *fp;
注意:只有通过文件指针,才能调用相应的文件。
13.3 文件的打开与关闭文件操作的过程:对磁盘文件的操作必须“先打开,后读写,最后关闭”。
“打开”文件的含义:以某种方式从磁盘上查找指定的文件或创建一个新文件,相关函数 fopen()。
原型,FILE * fopen(const char *filename,const char *mode)
filename,文件名 (可以包含逻辑驱动器、路径、文件名、扩展名 )
mode,打开方式
FILE *:返回值为 FILE类型指针如:
FILE *fp;
fp = fopen("file1","r");
如果成功打开,返回一个指向被打开文件的文件信息区的起始地址;
如果打开失败,返回一个 NULL指针。
文件打开方式 含义
"r"(只读) 为输入打开一个文本文件
"w"(只写) 为输出打开一个文本文件
"a"(追加) 为追加打开一个文本文件
"rb"(只读) 为输入打开一个二进制文件
"wb"(只写) 为输出打开一个二进制文件
"ab"(追加) 为追加打开一个二进制文件
"r+"(读写) 为读/写打开一个文本文件
"w+"(读写) 为读/写创建一个文本文件
"a+"(读写) 为读/写打开一个文本文件
"rb+"(读写) 为读/写打开一个二进制文件
"wb+"(读写) 为读/写创建一个二进制文件
"ab+"(读写) 为读/写打开一个二进制文件文件打开方式说明:
1、打开文件的方式,必须与文件的属性兼容。例如,不能以 "w"和
"a"方式打开具有“只读”属性的文件。但可以以任何方式打开“隐藏”属性文件。
2,DOS/Windows中,无“只写”文件属性,一个文件可写,同时意味着可读。
3、用带 "r"的方式( "r","rb","r+","rb+")打开文件时,若文件不存在,则返回 NULL指针。一般用以下方式检查是否成功打开:
FILE *fp;
if ((fp=fopen("file1","r")) = =NULL )
{
printf("cannot open this file\n");
exit(0);
}
exit(0)是程序返回操作系统。
4、在 Turbo C2.0中,用带 "a"或带 "w"的方式打开文件时,若文件不存在,则创建该文件。但 fopen()函数仍可能因为磁盘写保护或目录满而失败。
5、在 Turbo C2.0中,用带 "a"或带 "w"的方式打开文件时,若文件已存在,"a"方式在原文件后追加数据,"w"方式把原文件删除、并重新创建一个相同名字的文件。
文件的关闭,fclose()函数
“关闭”文件的作用:
(1)使文件指针 fp与文件脱离。
(2)刷新文件输入/输出缓冲区。
用法,int fclose(FILE *文件指针 );
功能:关闭“文件指针”所指向的文件。如果正常关闭了文件,则函数返回值为0;否则,返回值为非0。
例如,fclose(fp); /*关闭 fp所指向的文件 */
● 关于 exit()函数
1)用法,void exit([程序状态值 ]);
2)功能:关闭已打开的所有文件,结束程序运行,返回操作系统,并将“程序状态值”返回给操作系统。当“程序状态值”为0时,表示程序正常退出;非0值时,表示程序出错退出。
( 2),r( b) +”与,a( b) +”的区别:使用前者打开文件时,读写位置指针指向文件头;使用后者时,读写指针指向文件尾。
( 3)使用文本文件向计算机系统输入数据时,系统自动将回车换行符转换成一个换行符;在输出时,将换行符转换成回车和换行两个字符。
使用二进制文件时,内存中的数据形式与数据文件中的形式完全一样,
就不再进行转换。
( 4)有些C编译系统,可能并不完全提供上述对文件的操作方式,或采用的表示符号不同,请注意所使用系统的规定。
( 5)在程序开始运行时,系统自动打开三个标准文件,并分别定义了文件指针:
1)标准输入文件 ——stdin:指向终端输入(一般为键盘)。如果程序中指定要从 stdin所指的文件输入数据,就是从终端键盘上输入数据。
2)标准输出文件 ——stdout:指向终端输出(一般为显示器)。
3)标准错误文件 ——stderr:指向终端标准错误输出(一般为显示器)。
13.4 文件的读写一,fputc()和 fgetc()函数
int fputc(int c,FILE *fp)----把字符 c写入文件 fp,成功时返回字符 c的
ASCII码,失败时返回 EOF(在 stdio.h中,符号常量 EOF的值等于 -1)。
int fgetc(FILE *fp)----从文件 fp中读一个字符,返回读得的字符。
对于文本文件,遇文件尾时返回 EOF。
对于二进制文件,用 feof(fp) 判别是否遇文件尾。 feof(fp)=1说明遇文件尾。
例、从文本文件 test.txt中顺序读入文件内容,并在屏幕上显示出来。
#include "stdio.h"
void main()
{
FILE *fp;
char ch;
fp = fopen("test","r");
if (fp == NULL) /*或 if(fp)*/
{
printf("can not open test\n");
exit(0);
}
ch = fgetc(fp);
while(ch != EOF) /*或 while(ch)*/
{
putchar(ch);
ch = fgetc(fp);
}
fclose(fp);
}
例、从二进制文件 test中读入文件内容,
#include "stdio.h"
void main()
{
FILE *fp;
char ch;
fp = fopen("test","rb");
if (fp == NULL)
{
printf("can not open test\n");
exit(0);
}
while(!feof(fp)) /*文件指针到文件尾 */
{
ch = fgetc(fp);
...,
}
fclose(fp);
}
[例 13.1] 从键盘输入一些字符,逐个把它们送入磁盘文件,知道从键盘输入 #为止。
#include "stdio.h"
void main()
{
FILE *fp;
char ch;
char filename[10]; /* 文件名 */
printf("Input filename\n");
scanf("%s\n",filename); /* 从键盘输入文件名 */
if ((fp = fopen(filename,"w"))==NULL) /* 打开文件 */
{
printf("can not open file %s\n",filename);
exit(0);
}
ch = getchar();
while(ch != '#') /* 从键盘读入字符,直到 #为止 */
{
fputc(ch,fp);
putchar(ch);
ch = getchar();
}
fclose(fp); /* 关闭文件 */
}
[例 13.2]、将一个磁盘文件的内容复制到另一个磁盘文件。
#include "stdio.h"
void main()
{
FILE *in,*out;
char ch;
char infile[10],outfile[10];
printf("Enter the infile name\n");
scanf("%s",infile);
printf("Enter the outfile name\n");
scanf("%s",outfile);
if ((in = fopen(infile,"r"))==NULL)
{ printf("can not open infile %s\n",infile);
exit(0);
}
if ((out = fopen(outfile,"w"))==NULL)
{ printf("can not open outfile %s\n",outfile);
exit(0);
}
while(!feof(in)) fputc(fgetc(in),out);
fclose(in);
fclose(out);
}
二,fread()/ fwrite()函数
size_t fread(void *buffer,size_t size,size_t count,FILE *fp)
从文件 fp中读入 count次,每次读 size字节,读入的信息存在 buffer指针指向的缓冲区。函数返回值等于实际读入的次数(可能少于 count)。注注,typedef unsigned int size_t
size_t fwrite(void *buffer,size_t size,size_t count,FILE *fp)
将 buffer地址开始的信息,写入 count次,每次写 size字节至文件 fp中。
函数返回值等于实际写入的次数(可能少于 count)。
[例]、结构体类型数据。
struct student_type
{
char name[10];
int num;
int age;
char addr[30];
}stu[40];
写入文件:
for(i=0; i<40; i++) /* 每次写一个学生 */
fwrite(&stu[i],sizeof(struct student_type),1,fp);
或:写一次
fwrite(stu,sizeof(struct student_type),40,fp);
从磁盘文件读出:
for(i=0; i<40; i++)
fread(&stu[i],sizeof(struct student_type),1,fp);
或:
fread(&stu[i],sizeof(struct student_type),40,fp);
[例 13.3] 从键盘上输入一批学生数据,然后存储到磁盘上。
#include "stdio.h"
#define SIZE 4
struct student_type
{
char name[10];
int num;
int age;
char addr[15];
}stud[SIZE];
void save(); /* 原型 */
void load(); /* 原型 */
void main()
{ int i;
for(i=0; i<SIZE; i++) /* 从键盘读入数据 */
scanf("%s%d%d%s",stud[i].name,&stud[i].num,
&stud[i].age,stud[i].addr);
save(); /* 存盘 */
load(); /* 从盘读出 */
for(i=0; i<SIZE; i++) /* 屏幕上显示 */
printf("%-10s%4d%4d%-15s\n",stud[i].name,
stud[i].num,stud[i].age,stud[i].addr);
}
void save()
{ FILE *fp;
int i;
if ((fp=fopen("stu_list","wb"))==NULL)
{
printf("can not open file\n";
exit(0);
}
for(i=0; i<SIZE; i++)
if (fwrite(&stud[i],sizeof(struct student_type),1,fp) != 1)
printf("file read error\n");
}
void load()
{ FILE *fp;
int i;
if ((fp=fopen("stu_list","rb"))==NULL)
{
printf("can not open file\n“);
return;
}
for(i=0; i<SIZE; i++)
if (fread(&stud[i],sizeof(struct student_type),1,fp) != 1)
{
if (feof(fp)) return;
printf("file read error\n");
}
}
三,fprintf()/ fsacnf()函数
fprintf(文件指针,格式控制,变量列表 );
fscanf (文件指针,格式控制,变量地址列表 );
除增加“文件指针”外,与 printf()/ scanf()用法相同。
四、其它读写磁盘函数
int putw(int n,FILE *fp) /* 整数 n写入文件 fp */
int getw(FILE *fp) /* 从文件 fp读一个整数 */
char * fgets(char *str,int n,FILE *fp);
从文件 fp读 n-1个字节到 str,str最后一个字节加 '\0'。
int fputs(const char *str,FILE *fp) /*把 str写入 fp*/
读/写函数的选用原则从功能角度来说,fread()和 fwrite()函数可以完成文件的任何数据读/写操作。 但为方便起见,依下列原则选用:
1.读 /写 1个字符(或字节)数据时:选用 fgetc()和 fputc()函数。
2.读 /写 1个字符串时:选用 fgets()和 fputs()函数。
3.读 /写 1个 (或多个 )不含格式的数据时:选用 fread()和 fwrite()函数。
4.读 /写 1个 (或多个 )含格式的数据时:选用 fscanf()和 fprintf()函数。
13.5 文件的定位文件指针 FILE *fp中,包含一个读写位置指针 char *_nextc,它指向下一次文件读写的位置。
typedef struct
{
int _fd; /* 文件号 */
int _cleft; /* 缓冲区中剩下的字节数 */
int _mode; /* 文件操作模式 */
char * _nextc; /* 下一个字节的位置 */
char * _buff; /*文件缓冲区位置 */
}FILE;
每当进行一次读写后,该指针自动指向下一次读写的位置。
当文件刚打开或创建时,该指针指向文件的开始位置。
可以用函数 ftell()获得当前的位置指针,也可以用 rewind()/ fseek()函数改变位置指针,使其指向需要读写的位置。
一,rewind()函数原型,void rewind(FILE *fp)
作用:使文件 fp的位置指针指向文件开始。
[例 12.4] 把一个文件的内容显示在屏幕上,并同时复制到另一个文件。
#include "stdio.h"
void main()
{
FILE *fp1,*fp2;
fp1 = fopen("file1.c","r"); /* 源文件 */
fp2 = fopen("file2.c","w"); /* 复制到 file2.c */
while(!feof(fp1)) putchar(fgetc(fp1)); /* 显示到屏幕上 */
rewind(fp1); /* fp回到开始位置 */
while(!feof(fp1)) fputc(fgetc(fp1),fp2);
fclose(fp1);
fclose(fp2);
}
二,fseek()函数原型,int fssek (FILE *fp,long offset,int origin);
功能:把文件 fp的位置指针从起始点 origin开始,移动 offset字节。
成功返回 0,失败返回非 0。
起始点 origin 符号常量 值文件开始位置 SEEK_SET 0
当前位置 SEEK_CUR 1
文件尾 SEEK_END 2
[例 13.5] 在磁盘文件 stud_dat上,存有 10个学生 (0~ 9)
的数据,读出 1,3,5,7,9号学生数据,并在屏幕上显示出来。
#include "stdio.h"
struct student_type
{
char name[10];
int num;
int age;
char sex;
}stud[10];
void main()
{
int i;
FILE * fp;
if ((fp=fopen("stud_dat","rb")) == NULL)
{
printf("can not open file\n");
exit(0);
}
for(i=0; i<10; i += 2)
{
fseek(fp,i*sizeof(struct student_type),SEEK_SET);
fread(&stu[i],sizeof(struct student_type),1,fp);
printf("%s %d %d %c\n",
stud[i].name,stud[i].num,stud[i].age,stud[i].sex);
}
fclose(fp);
}
三、返回文件当前位置的函数 ftell()
由于文件的位置指针可以任意移动,也经常移动,
往往容易迷失当前位置,ftell()就可以解决这个问题。
1.用法,long ftell(文件指针 );
2.功能:返回文件位置指针的当前位置(用相对于文件头的位移量表示)。
如果返回值为 -1L,则表明调用出错。例如,
offset=ftell(fp);
if(offset= =-1L)printf(“ftell() error\n”);
一,ferror()函数在调用输入输出库函数时,如果出错,除了函数返回值有所反映外,
也可利用 ferror()函数来检测。
1.用法,int ferror(文件指针 );
2.功能:如果函数返回值为 0,表示未出错;如果返回一个非 0值,
表示出错。
( 1)对同一文件,每次调用输入输出函数均产生一个新的 ferror()
函数值。因此在调用了输入输出函数后,应立即检测,否则出错信息会丢失。
( 2)在执行 fopen()函数时,系统将 ferror()的值自动置为 0。
二,clearerr()函数
1.用法,void clearerr(文件指针 );
2.功能:将文件错误标志(即 ferror()函数的值)和文件结束标志
(即 feof()函数的值)置为 0。
对同一文件,只要出错就一直保留,直至遇到 clearerr()函数或
rewind()函数,或其它任何一个输入输出库函数。
13.6 出错的检测本章要求与作业
1、文件中数据的组织形式:文本文件、二进制文件。
2,打开文件的含义,为什么要关闭文件?
3、缓冲文件操作函数的使用。
作业,13.3 13.4 13.12