13.4 文件的读写
文件打开之后,就可以对它进行读写了。常用的读写函数如下所述。
13.4.1 fputc 函数和 fgetc 函数(putc 函数和 getc 函数)
1. fputc 函数
把一个字符写到磁盘文件上去。其一般调用形式为
fputc (ch,fp);
其中ch 是要输出的字符,它可以是一个字符常量,也可以是一个字符变量.
fp 是文件指针变量。fputc (ch,fp) 函数的作用是将字符(ch的值)输出到所指向的文件中去。fputc 函数也带回一个值:如果输出成功,则返回值就是输出的字符;如果输出失败,则返回一个EOF(即—1)。EOF 是在 stdio.h 文件中定义的符号常量,值为—1。
在第4章介绍过 putchar 函数,其实 putchar 是从 fputc 函数派生出来的。putchar(c) 是在 stdio.h 文件中用预处理命令 #define 定义的宏:
#define putchar(c) fputc(c,stdout)
前面已叙述,stdout 是系统定义的文件指针变量,它与终端输出相联.
fputc(c,stdout)的作用是将 c 的值输出到终端.用宏putchar(c)比写fputc(c,stdout)
简单一些。从用户的角度,可以把 putchar(c) 看作函数而不必严格地称它为宏。
2.fgetc 函数
从指定的文件读入一个字符,该文件必须是以读或读写方式打开的。fgetc 函数的调用形式为: ch=fgetc(fp);
fp 为文件型指针变量,ch 为字符变量。fgetc 函数带回一个字符,赋给 ch。
如果在执行 fgetc 函数读字符时遇到文件结束符,函数返回一个文件结束标志EOF(即—1)。如果想从一个磁盘文件顺序读入字符并在屏幕上显示出来,可以用:
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
注意:EOF 不是可输出字符,因此不能在屏幕上显示。由于字符的 ASCII 码不可能出现—1,因此 EOF 定义为—1是合适的。当读入的字符值等于—1(即EOF)时,表示读入的已不是正常的字符而是文件结束符。但以上只适用于读文本文件的情况。现在 ANSI C 已允许用缓冲文件系统处理二进制文件,而读入某一个字节中的二进制数据的值有可能是—1,而这又恰好是EOF的值.
这就出现了需要读入有用数据而却被处理为“文件结束”的情况。为了解决这个问题,ANSI C 提供一个 feof 函数来判断文件是否真的结束。feof(fp)用来测试 fp 所指向的文件当前状态是否“文件结束”。如果是文件结束,函数feof(fp)的值为1(真);否则为0(假)。
如果想顺序读入一个二进制文件中的数据,可以用:
while(! feof(fp))
{
c=fgetc(fp);
……
}
当未遇文件结束,feof(fp)的值为0,! feof(fp) 的值为1,读入一个字节的数据赋给整型变量c,并接着对其进行所需的处理。直到遇文件结束,feof(fp)值为1,! feof(fp) 值为0,不再执行 while 循环.
这种方法也适用于文本文件。
3. fputc 和 fgetc函数使用举例
在掌握了以上几种函数以后,可以编制一些简单的使用文件的程序。
例13 .1 从键盘输入一些字符,逐个把它们送到磁盘上去,直到输入一个“#”为止。程序如下: (本例有错误)
#include "stdio.h"
#include "stdlib.h"
void main()
{
FILE * fp;
char ch,filename[10];
scanf("%s",filename);
if((fp=fopen(filename,"W"))==NULL)
{
printf("cannot open file\n");
exit(0);
}
ch=getchar();
ch=getchar();
while(ch!='#')
{
fputc(ch,fp);
putchar(ch);
ch=getchar();
}
putchar(10);
fclose(fp);
}
运行结果是:
输入:file1.c
cannot open file
(不能打开文件)
文件名由键盘输入,赋给字符数组 filename。 fopen 函数中的第一个参数“文件名” 可以直接写成字符串常量形式(如file1.c),也可以用字符数组名,在字符数组中存放文件名(如本例所用的方法)。本例运行时,从键盘输入磁盘文件名 “file1.c” ,然后输入要写入该磁盘文件的字符“ computer and c”, '#' 是表示输入结束,程序将 “ computer and c” 写到以命名的磁盘文件中,同时在屏幕上显示这些字符 ,以便核对。exit 是标准 C 的库函数,作用是使程序终止,用此函数应当加入 stdlib 头文件。
例13.2 将一个磁盘文件中的信息复制到另一个磁盘文件中。
(能运行,不能复制 )
#include<stdlib.h>
#include<stdio.h>
void main()
{
FILE*in,*out;
char ch,infile[10],outfile[10];
printf("Enter the infile name:\n");
scanf("%s",infile);
printf("Enter the infile name:\n");
scanf("%s",outfile);
if((in=fopen(infile,"r"))==NULL)
{
printf("can not open infile\n");
exit(0);
}
if((out=fopen(outfile,"w"))==NULL)
{
printf("can not open outfile\n");
exit(0);
}
while(! feof(in)) fputc(fgetc(in),out);
fclose(in);
fclose(out);
}
运行情况如下:
Enter the infile name:
file1.txt (输入原有磁盘文件名)
Enter the infile name:
file2.txt (输入新复制的磁盘文件名)
程序运行结果是将 file1.txt 文件中的内容复制到 file2.txt 中去。
以上程序是按文本文件方式处理的。也可以用此程序来复制一个二进制文件,只需将两个 fopen 函数中的 r 和 w 分别改为 rb 和 wb 即可。
也可以在输入命令行时把两个文件名一起输入。这时要用到 main 函数的参数。程序可改为:
#include<stdlib.h>
#include<stdio.h>
void main(int agc,char *argv[])
{
FILE * in,* out;
char ch;
if(argc !=3)
{
printf("You forgot to enter a filename\n");
exit(0);
}
if((in=fopen(argv[1],"r"))==NULL)
{
printf("cannot open infile\n");
exit(0);
}
if((out=fopen(argv[2],"w"))==NULL)
{
printf("cannot open infile\n");
exit(0);
}
while(! feof(in)) fputc(fgetc(in),out);
fclose(in);
fclose(out);
}
假若本程序的原文件名为 1.c 经编译连接后得到的可执行文件名为 1.exe ,则在DOS命令工作方式下,可以输入以下的命令行:C>1 file1.c file2.c 即在输入可执行文件名后,再输入两个参数 file1.c 和 file2.c ,分别输入到 argv[1]和 argv[2]中,argv[0]的内容为a,argc 的值等于3(因为此命令行共有3个参数) 。如果输入的参数少于3个,则程序会输出:“You forgot to enter a filename”
(你忘了输入一个文件名)。程序执行结果是将 file1.c 中的信息复制到 file2.c 中。可以用以下命令验证:
C>type file1.c
computer and c
(这是 file1.c 文件中的信息)
C>type file2.c
computer and c
(这是 file2.c 文件中的信息。可见 file1.c已复制到 file2.c 中了)。
最后说明一点,为了书写方便,系统把 fputc 和 fgetc 定义为宏名putc 和getc:
#define putc(ch,fp) fputc(ch,fp)
#define getc(fp) fgetc(fp)
这是在 stdio.h 中定义的。因此,用 putc 和 fputc 及用 getc 和 fgetc 是一样的。一般可以把它们作为相同的函数来对待。
13.4.2 fead 函数和 fwrite 函数
用 getc 和 putc 函数可以用来读写文件中的一个字符。但是常常要求一次读入一组数据(例如,一个实数或一个结构体变量的值),ANSI C 标准提出设置两个函数(fead 函数和 fwrite 函数),用来读写一个数据块。它们的一般调用形式为:
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中:buffer:是一个指针.对 fread 来说,它是读入数据的存放地址. 对fwrite 来说,
是要输出数据的地址(以上指的是起始地址)。
size : 要读写的字节数。
count: 要进行读写多少个 size 字节的数据项。
fp: 文件型指针。
如果文件以二进制形式打开,用 fead 和 fwrite 函数就可以读写任何类型的信息,例如: fead(f,4,2,fp); 其中 f 是一个实型数组名。一个实型变量占4个字节。这个函数从所指向的文件读入2个4个字节的数据,存储到数组 f 中。
如果有一个如下的结构体类型:
struct student_type
{
char name[10];
int num;
int age;
char addr[30];
}stud[40];
结构体数组 stud 有40个元素,每一个元素用来存放一个学生的数据(包括姓名、学号、年龄、地址)。假设学生的数据已存放在磁盘文件中,可以用下面的 for 语句和 fread 函数读入40个学生的数据:
for(i=0;i<40;i++)
fread(&stud[i],sizeof(struct student_type),1,fp);
同样,以下 for 语句和 fwrite 函数可以将内存中的学生数据输出到磁盘文件中去:
for(i=0;i<40;i++)
fwrite(&stud[i],sizeof(struct student_type),1,fp);
如果 fead 和 fwrite 调用成功,则函数返回值为 count 的值,既输入或输出数据项的完整个数。
下面写出一个完整的程序。
例13.3 从键盘输入4个学生的有关数据,然后把它们转存到磁盘文件上去。
#include "stdio.h"
#define SIZE 4
struct student_type
{char name[10];
int age;
int num;
char addr[15];
}stud[SIZE];
void save()
{
FILE * fp;
int i;
if((fp=fopen("stu_list","wb"))==NULL)
{
printf("cannot open file\n");
return;
}
for(i=0;i<SIZE;i++)
if(fwrite(&stud[i],sizeof(struct student_type),1,fp)!=1)
printf("file write error\n");
fclose(fp);
}
void main()
{
int i;
for(i=0;i<SIZE;i++)
scanf("%s%d%d%s",stud[i].name,&stud[i].age,&stud[i].num,stud[i].addr);
save();
}
在main 函数中,从终端键盘输入4个学生的数据,然后调用 save 函数,将这些数据输出到以 " stu_list "命名的磁盘文件中。fwrite 函数的作用是将一个
长度为29字节数据块送到 stu_list 文件中(一个 student_type 类型结构体变量的长度为它的成员长度之和,即10+2+2+15=29)。
运行情况如下:
输入4个学生的姓名、学号、年龄和地址:
Zhang 1001 18 room_101
Fun 1002 18 room_102
Tan 1003 18 room_103
Lin 1004 21 room_104
程序运行时,屏幕上并无输出任何信息,只是将从键盘输入的数据送到此盘文件上。为了验证在磁盘文件 “ stu_list ”中是否已存在此数据,可以用以下程序从 stu_list 文件中读入数据,然后在屏幕上输出。
#include "stdio.h"
#define SIZE 4
struct student_type
{
int age;
int num;
char addr[15];
char name[10];
}stud[SIZE];
void main()
{
int i;
FILE * fp;
fp=fopen("stu_list","rb");
for(i=0;i<SIZE;i++)
{
fread(&stud[i],sizeof(struct student_type),1,fp);
printf("%-10s %4d %4d %-15s\n",stud[i].name,&stud[i].age,&stud[i].num,stud[i].addr);
}
fclose(fp);
}
请注意:输入输出数据的状况。从键盘输入4个学生的数据是 ASCII 码,也就是文本文件。在送到计算机内存时,回车和换行符转换成一个换行符。再从内存以 “wb”方式(二进制写)输出到 stu_list 文件,此时不发生字符转换,按内存中存储形式原样输出到磁盘文件上。在上面验证程序中,又用“fread ”函数从 stu_list 文件向内存读入数据,注意此时用的是“rb” 方式,即二进制方式,数据按原样输入,也不发生字符转换。也就是这时候内存中的数据恢复到第一个程序向 “ stu_list ” 输出以前的情况。最后在验证程序中,用printf 函数输出到屏幕,printf 是格式输出函数,输出 ASCII 码,在屏幕上显示字符。换行符又转换为回车加换行符。
如果企图从 “ stu_list ”文件中以 “r ”方式读入数据就会出错。
fread 和 fwrire 函数一般用于二进制文件的输入输出。因为它们是按数据块的长度来处理输入输出的,在字符发生转换的情况下很可能出现与原设想的情况不同。 例如,写成:fread(&stud[i],sizeof(struct student_type),1,stdin); 企图从终端键盘输入数据,这在语法上并不存在错误,编译能通过。如果用以下形式输入数据: Zhang 1001 18 room_101
……
由于 fread 函数要求一次输入29个字节(而不问这些字节的内容),因此输入数据中的空格也作为输入数据而不作为数据间的分隔符了。连空格也存储到 stud[i] 中了,显然是不对的。
这个题目要求的是从键盘输入数据,如果已有的数据已经以二进制形式存储在一个磁盘文件 stu_dat 中,要求从其中读入数据并输出到 stu_list 文件中,可以编写一个 load 函数,从磁盘文件中读二进制数据。
#include "stdio.h"
#define SIZE 4
struct student_type
{
int age;
int num;
char addr[15];
char name[10];
}stud[SIZE];
void load()
{
FILE * fp;
int i;
if((fp=fopen("stu_dat","rb"))==NULL)
{
printf("cannot open file\n");
return;
}
for(i=0;i<SIZE;i++)
if(fread(&stud[i],sizeof(struct student_type),1,fp)!=1)
{ if(feof(fp))
{ fclose(fp);
return;
}
printf("file write error\n");
}
fclose(fp);
}
main()
{
load();
save();
}
13.4.3 fprintf (从文件中输出) 函数和 fscanf (从文件中读入) 函数
fprintf 函数、fscanf 函数 与 printf 函数、scanf 函数作用相仿,都是格式读写函数。只有一点不同: fprintf 函数、fscanf 函数的读写对象不是终端而是磁盘文件。它们的一般调用方式为:
fprintf (文件指针,格式字符串,输出表列);
fscanf (文件指针,格式字符串,输入表列);
例如:
fprintf(fp,"%d,%6.2f",a,b);
它的作用是将整型变量 a 和实型变量 b 的值按 %d 和 %6.2f 的格式输出到 fp 指向的文件上。如果 i=3,t=4.5 则输出到磁盘文件上的是以下的字符串:
3, 4.50
同样,用以下函数可以从磁盘文件上读入 ASCII 字符:
fscanf(fp,"%d,%f",&a,&b);
磁盘文件上如果有这样的字符:3, 4.5 即将磁盘文件中的数据 3送给变量 a,4.5 送给变量 b。
用 fprintf 和 fscanf 函数对磁盘文件读写,使用方便,容易理解,但由于在输入时要将 ASCII 码转换为二进制形式,在输出时又要将二进制形式转换成字符,花费时间比较多。因此,在内存与磁盘频繁交换数据的情况下,最好不用 fprintf 和 fscanf 函数,而用 fread(从文件中读) 和 fwrite(往文件中写) 函数。
13.4.4 其它读写函数
1. putw 和 getw 函数
大多数 C 编译系统都提供另外两个数:putw 和 getw 函数 ,用来对磁盘文件读写一个字(整数)。例如: putw(10,fp); 它的作用是将整数 10 输出到 fp指向的文件。而 i=getw(fp); 的作用是从磁盘文件读一个整数到内存,赋给整型变量 i。
如果所用的 C 编译系统的库函数中不包括 putw 和 getw 函数,可以自己定义这两个函数。putw 函数如下:
putw(int i,FILE * fp)
{
char * s; 图1: i
s=&i; 00000000 00001010
putc(s[0],fp); s[0] s[1]
putw(s[1],fp);
return(i);
}
当调用 putw 函数时,如果用 putw(10,fp); 语句, 形参 i 得到实参传来的值 10, 在 putw 函数中将 i 的地址赋予指针变量 s ,而 s 是指向字符变量的指针变量,因此 s 指向 i 的第 1 个字节,s+1 指向 i 的第 2 个字节。由于 * (s+0)就是 s[0],* (s+1)就是 s[1],因此,s[0]、s[1]分别对应的第 1 个字节和第 2 个字节。顺序输出s[0]、s[1]就相当于输出了 i 的两个字节中的内容,见图1.
getw 函数如下:
getw(FILE * fp)
{
char * s;
int i;
s=char * &i; /*使 s 指向 i 的起始地址 */
s[0]=getc(fp);
s[1]=getc(fp);
return(i);
}
putw 和 getw 函数并不是 ANSI C 标准定义的函数。许多 C 编译系统都提供这两个,但有的不以 putw 和 getw 命名此两函数,而用其它函数名,用时要注意。
2. 读写其它类型数据
如果用 ANSI C 提供的 fread 和 fwrite函数,读写任何类型数据都是十分方便的。如果所用的系统不提供这两个函数,用户只好自己定义所需函数。例如,可以定义一个向磁盘文件写一个实数(用二进制方式)函数 putfloat:
putfloat(float num,FILE * fp)
{
char * s;
int count;
s=(char *) #
for(count=0;count<4,count++)
putc(s[count],fp);
}
同样可以编写出读写任何类型数据的函数。
3. fgets 函数和 fputs 函数
fgets 函数的作用是从指定文件读入一个字符串。例如:fgets (str,a,fp); a 为要求得到的字符,但只从 fp指向的文件输入 a-1 个字符,然后在最后加一个‘ \0 ’字符,因此得到的字符串共有 a 个字符,把它们放到字符数组 str 中.如果
在读完 a-1个字符之前遇到换行符或 EOF ,读入即结束。 fgets 函数返回值为 str 的首地址。
fputs 函数的作用是从指定文件输出一个字符串。例如:fputs("Wolong",fp);
把字符串 “ Wolong ” 输出到 fp 指向的文件。fputs 函数中第一个参数可以是字符串常量、字符数组名或字符指针。 字符串末尾的‘ \0 ’ 不输出。若输出成功,函数值为 0;失败时,为 EOF 。
这两个函数类似以前介绍过的 gets 和 puts 函数, 只是 fgets 和 fputs 函数以指定的文件为读写对象。
fgets(从文件中获取字符串)
fputs(往文件中写字符串)
如果所用的 C 编译系统的库函数中不包括 putw 和 getw 函数,可以自己定义这两个函数。putw 函数如下:
putw(int i,FILE * fp)
{
char * s; 图1: i
s=&i; 00000000 00001010
putc(s[0],fp); s[0] s[1]
putw(s[1],fp);
return(i);
}
当调用 putw 函数时,如果用 putw(10,fp); 语句, 形参 i 得到实参传来的值 10, 在 putw 函数中将 i 的地址赋予指针变量 s ,而 s 是指向字符变量的指针变量,因此 s 指向 i 的第 1 个字节,s+1 指向 i 的第 2 个字节。由于 * (s+0)就是 s[0],* (s+1)就是 s[1],因此,s[0]、s[1]分别对应的第 1 个字节和第 2 个字节。顺序输出s[0]、s[1]就相当于输出了 i 的两个字节中的内容,见图1.
getw 函数如下:
getw(FILE * fp)
{
char * s;
int i;
s=char * &i; /*使 s 指向 i 的起始地址 */
s[0]=getc(fp);
s[1]=getc(fp);
return(i);
}
如果用 ANSI C 提供的 fread 和 fwrite函数,读写任何类型数据都是十分方便的。如果所用的系统不提供这两个函数,用户只好自己定义所需函数。例如,可以定义一个向磁盘文件写一个实数(用二进制方式)函数 putfloat:
putfloat(float num,FILE * fp)
{
char * s;
int count;
s=(char *) #
for(count=0;count<4,count++)
putc(s[count],fp);
}
同样可以编写出读写任何类型数据的函数。
例13.1从键盘输入一些字符,逐个把它们送到磁盘上,直到输入一个"#"为止.程序如下:
#include<stdlib.h>
#include<stdio.h>
void main()
{
FILE*fp;
char ch,filename[10];
scanf("%s",filename);
if((fp=fopen(filename,"w"))==NULL)
{
printf("can not open file\n");
exit(0);/*终止程序*/
}
ch=getchar();/*接收输入的一个字符*/
ch=getchar();/*这一句可以省略.*/
while(ch!='#')
{
fputc(ch,fp);putchar(ch);
ch=getchar();
}
putchar(10);/*向屏幕输出一个换行符*/
fclose(fp);
}
运行后的结果:输入:is a c# 输出 a c
13.5 文件的定位
文件中有一个位置指针,指向当前读写的位置。如果顺序读写一个文件,每次读写一个字符,则读写完一个字符后,该位置指针自动移动指向下一个字符位置。如果想改变这样的规律,强制使位置指针指向其它指定的位置,可以用后面介绍的有关函数。
13.5.1 rewind 函数
rewind 函数的作用是使位置指针重新返回文件的开头,此函数没有返回值。
例13.4 有一个磁盘文件,第一次将它的内容显示在屏幕上,第二次把它复制到另一文件上。
#include "stdio.h"
void main()
{
FILE * fp1,* fp2;
fp1=fopen("file1.txt","r");
fp2=fopen("file2.txt","w");
while(! feof(fp1))
putchar(getc(fp1));
rewind(fp1);
while(! feof(fp1))
putc(getc(fp1),fp2);
fclose(fp1);
fclose(fp2);
}
(先在某一个位置上新建立一个文本文件,命名为file1.txt然后运行本程序(在vc编译系统运行过))如:“ file1.txt ”里面的内容是:
Nu11 pointer assignment
(无效的) ( 指示器)(分配、任务、作业)
运行后的结果是:
在第一次将内容显示在屏幕上,file1.txt 的位置指针已指到文件末尾,feof 的值为零(真)。执行 rewind 函数 ,使文件的位置指针重新定位于文件开头,并使 feof 函数的值恢复为0(假)。
fopen (打开文件) fclose(文件关闭) rewind(重新) putchar(写一个字符的函数)feof=end of file (文件末尾) putc (输出字符)