文件打开后才可以对文件进行操作。也就是说,文件必须经历打开-操作-关闭的过程。如前所述,C语言对文件的操作都是通过调用标准I/O库函数来实现的。文件操作实际是指对文件的读写。文件的读操作就是从文件中读出数据,即将文件中的数据输入计算机;文件的写操作是向文件中写入数据,即向文件输出数据。实际上对文件的处理过程就是对文件的输入输出过程。在前面已经介绍了C语言对标准设备文件的输入输出函数,本节讨论对磁盘文件的输入输出函数,这类文件及其相应的函数在实际应用和文件处理中占据重要的地位。C语言提供的缓冲式文件处理函数可分为:
字符输入输出函数 fgetc和fputc
字符串输入输出函数 fgets和fputs
格式化输入输出函数 fscanf和fprintf
数据块输入输出函数 fread和fwrite
文件定位函数 feek、rewind和ftell
其它函数 feof、ferror和clearerr。
12.3.1 文件的字符输入/输出函数
1.字符输入函数fgetc( )
fgetc函数的调用形式为:
ch = fgetc (fp);
其中fp为文件型指针变量,ch字符变量。
fgetc函数的功能是:从指定的文件中读取一个字符。即:从fp所指向的文件(该文件必须是以读或读写方式打开的)中读取一个字符返回,读取的字符赋给变量ch。若读取字符时文件已经结束或出错,fputc函数返回文件结束标记EOF,此时EOF的值为-1。
例如,要从磁盘文件中顺序读入字符并在屏幕上显示,可通过调用fgetc函数实现:
while ( (c=fgetc(fp)) != EOF )
putchar(c);
注意:文件结束标记EOF是不可输出字符,不能在屏幕上显示。因为EOF是在头文件stdio.h中定义的符号常量,其值为-1,而ASCII码中没用-1,可见,用它作文件结束标记是合适的。
例12-1:在屏幕上显示文本文件的内容。
#include <stdio.h>
main ( )
{ FILE *fp;
char filename[20], ch;
printf ("Enter filename:");
scanf("%s",filename); /* 输入文件名 */
if ( (fp = fopen (filename,"r"))==NULL) /* 打开文件 */
{ printf("file open error.\n"); /* 出错处理 */
exit (0);
}
while ( ( ch = fgetc(fp) )!=EOF) /* 从文件中读字符 */
putchar(ch); /* 显示从文件读入的字符 */
fclose (fp); /* 关闭文件 */
}
例12-2:使用标准输出文件显示文本文件的内容。
#include <stdio.h>
main ( )
{ FILE *fp;
char filename[20], ch;
printf ("Enter filename:");
scanf("%s",filename); /* 输入文件名 */
if ( (fp = fopen (filename,"r"))==NULL) /* 打开文件 */
{ printf("file open error.\n"); /* 出错处理 */
exit (0);
}
while ( (ch=fgetc(fp) ) != EOF ) /* 从文件中读取字符 */
fputc(ch,stdout); /* 向标准输出文件中输出(显示) */
fclose (fp); /* 关闭文件 */
}
2.字符输出函数fputc( )
fputc函数的调用形式为:
fputc (ch, fp);
其中:ch是要输出的字符(可为字符常数或字符变量),fp为文件型指针变量。
fputc函数的功能是:将一个字符输出到指定文件中。即将字符变量ch中的字符输出到fp所指向的文件。若输出操作成功,该函数返回输出的字符;否则,返回EOF。
例12-3:从键盘输入一字符串,并逐个将字符串的每个字符传送到磁盘文件file中,当输入的字符为"#"时停止输入。
#include <stdio.h> /* 凡程序中用到标准输入输出函数,必须包含此文件头 */
main ( )
{ FILE *fp; /* 指向磁盘文件file的指针 */
char ch; /* 暂存读入字符的字符变量 */
char filename[15]; /* 存放磁盘文件名的字符数组 */
scanf("%s", filename); /* 从键盘输入磁盘文件名 */
if ((fp=fopen(filename,"w"))==NULL) /* 以写方式打开文本文件并判定是否能正常打开 */
{ printf("Cannot open file.\n"); /* 不能正常打开磁盘文件的处理 */
exit(0); /* 调用函数exit终止程序运行 */
}
while ( (ch=getchar( )) != '#' ) /* 判断输入的是否为字符符串结束标志 */
fputc(ch, fp); /* 读入的字符写入磁盘文件 */
fclose(fp); /* 操作结束关闭磁盘文件 */
}
例12-4:请编程完成文本文件的复制。
#include <stdio.h>
main ( )
{ FILE *fp1, *fp2;
char file1[20], file2[20], ch;
printf ("Enter filename1:");
scanf("%s",file1);
printf ("Enter filename2:");
scanf("%s",file2);
if ( (fp1=fopen(file1,"r")) == NULL ) /* 以"只读"方式打开文件1 */
{ printf("file1 open error.\n");
exit (0);
}
if ( (fp2=fopen(file2, "w"))== NULL ) /* 以"写"方式打开文件2 */
{ printf("file2 open error.\n");
exit (0);
}
while ( ( ch = fgetc(fp1) ) != EOF ) /* 从文件fp1中读字符 */
fputc (ch, fp2); /* 写入文件fp2中 */
fclose (fp1); /* 关闭两个文件 */
fclose (fp2);
}
12.3.2 文件的字符串输入/输出函数
对文件的输入输出,除了前面介绍的以字符为单位进行处理之外,还允许以字符串为单位进行处理,这也被称为"行处理"。
C语言提供fgets和fputs函数实现文件的按字符串的读写。
1.字符串输入函数fgets( )
fgets函数的调用形式:
fgets (s, n, fp);
其中:参数s可以是一个字符数组名,或是指向字符串的指针,n为要读取的最多的字符个数;fp是指向该文件的文件型指针。
fgets函数的功能是:从fp所指向的文件中读取长度不超过n-1个字符的字符串,并将该字符串放到字符数组s中。如果操作正确,函数的返回值为字符数组s的首地址;如果文件结束或出错,则函数的返回值为NULL。
情况1:从文件中已经读入了n-1个连续的字符,还没有遇到文件结束标志或行结束标志'\n',
则:s中存入n-1个字符,串尾以串结束标记'\0'结束。
情况2:从文件中读入字符遇到了行结束标志'\n',
则:s中存入实际读入的字符,串尾为'\n'和'\0'。
情况3:在读文件的过程中遇到文件尾(文件结束标志EOF),
则:s中存入实际读入的字符,串尾为'\0'。文件结束标志EOF不会存入数组。
情况4:当文件已经结束仍然继续读文件,或读取文件内容发生错误,
则:函数的返回值为NULL,表示文件结束。例如:现有一个有两行字符的ASCII文件,文件打开后,文件的读写位置指针如图12-3所示。
若有:char s[5];FILE *fp;fp为指向该文件的指针。则多次执行语句"fgets (s, 5, fp);",每次的执行结果如下:
第1次执行语句fgets (s, 5, fp):
文件刚打开时,文件的读写位置指针指向了文件的第1个字符,执行语句:fgets (s, 5, fp)之后,从fp指向的文件中读取的字符串是"abcd\n",文件的读写位置指针向前移动了4个字符,字符串在s中的存储形式和文件读写位置指针如图12.4所示。
第2次执行语句fgets (s, 5, fp):
从文件的读写位置指针开始,顺序读入字符,遇到'\n'字符后,函数执行完毕。字符串在s中的存储形式和文件读写位置指针如图12.5所示。
第3次执行语句fgets (s, 5, fp):
从文件的读写位置指针开始,顺序读入字符,遇到文件结束标记EOF,函数执行完毕,此时,文件的读写位置指针指向了文件最后一个字符的后面。字符串在s中的存储形式和文件读写位置指针如图12-6所示。
第4次执行语句fgets (s, 5, fp):
由于文件的读写位置指针已经指向了文件结束标记EOF,所以函数的返回值为NULL,表示文件已经结束,文件的读写位置指针没有变化。文件读写位置指针如图12-7所示。
例12-5:显示文件内容并加上行号。
#include <stdio.h>
main ( )
{ FILE * fp;
char file[20], str[10];
int flag=1, i=0; /* flag标志变量,为1:开始新行。i为行号 */
printf ("Enter filename:");
scanf("%s",file);
if ( ( fp = fopen (file, "r")) == NULL ) /* 打开文件 */
{ printf("file open error.\n");
exit (0);
}
while ( fgets( str,10,fp )!=NULL ) /* 从文件中读出字符串 */
{ if (flag) printf ("%3d:%s", ++i, str); /* 显示行号 */
else printf ("%s", str);
if ( str [strlen(str)-1] == '\n' ) flag=1;
else flag=0;
}
fclose (fp);
}
本程序的特点是使用一个长度仅为10的小数组来处理文件,程序中充分利用了函数fgets的特点。
2.字符串输出函数 fputs( )
fputs函数的调用形式:
fputs (s, fp);
其中:s为指向字符串的指针或字符数组名,也可以是字符串常量;fp是指向将要被写入的文件的文件型指针。
fputs函数的功能是:将s指向的字符串或字符串常量写入fp指向的文件。输出的字符串写入文件时,字符'\0'被自动舍去。函数调用成功,则返回值为0;否则返回EOF。
例12-6:从键盘输入若干行字符存入磁盘文件file.txt中。
#include <stdio.h>
#include <string.h>
main ( )
{ FILE *fp;
char str[81];
if ((fp=fopen("file.txt", "w")) == NULL)
/* 以写方式打开磁盘文本文件file.txt并判断打开操作正常与否 */
{ printf("Cannot open file.\n"); /* 不能正常打开磁盘文件的处理 */
exit(0);
}
while ( strlen(gets(str)) > 0 )
/* 读入从键盘输入的一行字符,送入str字符数组 */
{ fputs(str, fp); /* 若该字符串非空则送入磁盘文件file.txt中去 */
fputs("\n", fp);
}
fclose (fp); /* 操作结束关闭磁盘文件 */
}
例12-7:复制文本文件。
#include <stdio.h>
main ( )
{ FILE *fp1, *fp2;
char file1[20], file2[20], s[10];
printf ("Enter filename1:");
scanf("%s",file1);
printf ("Enter filename2:");
scanf("%s",file2);
if ( ( fp1 = fopen (file1, "r")) == NULL ) /* 打开文本文件1 */
{ printf("file1 open error.\n");
exit (0);
}
if ( ( fp2 = fopen (file2, "w")) == NULL ) /* 打开文本文件2 */
{ printf("file2 open error.\n");
exit (0);
}
while ( fgets( s,10,fp1 ) != NULL ) /* 从文件fp1中读出字符串 */
fputs ( s, fp2 ); /* 将字符串写入文件fp2中 */
fclose (fp1);
fclose (fp2);
}
12.3.3 文件的格式化输入输出函数
前面的章节中介绍了scanf和printf两个格式化输入输出函数,它们适用于标准设备文件。C标准函数库还提供了fscanf和fprintf两个格式化输入输出函数,以满足磁盘文件格式化输入输出的需要。
1.格式化输入函数 fscanf( )
fscanf函数的调用形式:
fscanf (fp, 格式控制串, 输入列表);
其中:fp指向将要读取文件的文件型指针,格式控制串和输入列表的内容、含义及对应关系与第二章中介绍的scanf函数相同。
fscanf函数的功能是:从fp指向的文件中,按格式控制符读取相应数据赋给输入列表中的对应变量地址中。例如,
fscanf (fp, "%d,%f", &i, &t);
完成从指定的磁盘文件上读取ASCII字符,并按"%d"和"%f"型格式转换成二进制形式的数据送给变量i和t。
2.格式化输出函数 fprintf( )
fprintf函数的调用形式:
fprintf (fp, 格式控制串, 输出列表);
其中:fp指向将要写入文件的文件指针,格式控制串和输出列表的内容及对应关系与前面章节中介绍的printf函数相同。
fprintf函数的功能是:将输出列表中的各个变量或常量,依次按格式控制符说明的格式写入fp指向的文件。该函数调用的返回值是实际输出的字符数。
例12-8.C:从键盘输入一个字符串和一个十进制整数,将它们写入test文件中,然后再从test文件中读出并显示在屏幕上。
#include <stdio.h>
main( )
{ char s[80];
int a;
FILE *fp;
if ((fp=fopen("test", "w")) == NULL) /* 以写方式打开文本文件 */
{ printf ("Cannot open file.\n");
exit(1);
}
fscanf (stdin, "%s%d", s, &a); /* 从标准输入设备(键盘)上读取数据 */
fprintf(fp, "%s %d", s, a); /* 以格式输出方式写入文件 */
fclose (fp); /* 写文件结束关闭文件 */
if ((fp=fopen("test", "r")) == NULL) /* 以读方式打开文本文件 */
{ printf ("Cannot open file.\n");
exit(1);
}
fscanf (fp, "%s%d", s, &a); /* 以格式输入方式从文件读取数据 */
fprintf(stdout, "%s %d\n", s, a); /* 将数据显示到标准输出设备(屏幕)上 */
fclose(fp); /* 读文件结束关闭文件 */
}
12.3.4 文件的数据块输入/输出函数
这类函数是ANSI C标准对缓冲文件系统所做的扩充,以方便文件操作实现一次读写一组数据的功能。例如采用这种方式对数组和结构进行整体的输入输出是比较方便的。
1.文件数据块读函数fread( )
fread函数的调用形式:
fread ( buffer, size, count, fp);
其中:buffer是一个指针,是指向输入数据存放在内存区的起始地址;size是要输入的字节数;count是要输入大小为size个字节的数据块的个数;fp是文件指针。
fread函数的功能是:对fp所指向的文件读取count次,每次读取一个大小为size的数据块,将读取的各数据块存到buffer指向的内存区。该函数的返回值是实际读取的count的值。
2.文件数据块写函数fwrite( )
fwrite函数的调用形式:
fwrite ( buffer, size, count, fp);
fwrite函数的参数及其功能与fread函数类似,只是对文件的操作而言是互逆的,一个是读取,一个是写入。
例12-9:从键盘输入3个学生的数据,将它们存入文件student;然后再从文件中读出数据,显示在屏幕上。
#include <stdio.h>
#define SIZE 3
struct student /* 定义结构 */
{ long num;
char name[10];
int age;
char address[10];
} stu[SIZE], out;
void fsave ( )
{ FILE *fp;
int i;
if (( fp=fopen("student","wb")) == NULL ) /* 以二进制写方式打开文件 */
{ printf ("Cannot open file.\n"); /* 打开文件的出错处理 */
exit(1); /* 出错后返回,停止运行 */
}
for (i=0; i<SIZE; i++) /* 将学生的信息(结构)以数据块形式写入文件 */
if (fwrite(&stu[i], sizeof(struct student), 1, fp) != 1)
printf("File write error.\n"); /* 写过程中的出错处理 */
fclose (fp); /* 关闭文件 */
}
main ( )
{ FILE *fp;
int i;
for (i=0; i<SIZE; i++) /* 从键盘读入学生的信息(结构) */
{ printf("Input student %d:", i+1);
scanf ("%ld%s%d%s", &stu[i].num, stu[i].name, &stu[i].age, stu[i].address );
}
fsave( ); /* 调用函数保存学生信息 */
fp = fopen ("student", "rb"); /* 以二进制读方式打开数据文件 */
printf (" No. Name Age Address\n");
while ( fread(&out, sizeof(out), 1, fp) ) /* 以读数据块方式读入信息 */
printf ("%8ld %-10s %4d %-10s\n", out.num, out.name, out.age, out.address );
fclose(fp); /* 关闭文件 */
}