目录
1. 文件基础
1.1 文件分类
从用户角度,文件可分为:
1)普通文件:是指驻留在磁盘或其他外部介质上的有序数据集,程序文件、数据文件。
2)设备文件:是指与主机相连的各种外部设备,如显示器、打印机、键盘等。在操作系统中,把外部设备也看做是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和写。
—— 通常把显示器定义为标准输出文件,一般情况下在屏幕上显示有关信息就是向标准输出文件输出。如前面经常使用的printf()、putchar()函数就属于这类输出。
—— 通常把键盘指定为标准的输入文件,从键盘上输入就意味着从标准输入文件上输入数据。scanf,getchar函数就属于这类输入。
从文件编码方式,文件可分为:
1) ASCII文件:也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的 ASCII 码。ASCII 码文件可在屏幕上按字符显示,例如源程序文件就是ASCII 文件,用DOS命令TYPE可显示文件的内容。由于是按字符显示,因此能读懂文件内容。
2)二进制文件:按二进制的编码方式来存放文件。
例如 5678的存储形式为:00010110 00101110
只占2字节。二进制文件虽然也可在屏幕上显示,但其内容无法读懂。C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。
输入/输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。因此也把这种文件称作“流式文件”。
在C语言中,把文件看作一组字符或二进制数据的集合,也称为数据流。数据流的结束标志为-1,在C语言中,规定文件的结束标志为 EOF。EOF为一符号常量,定义在头文件“stdio.h”中,形式如下:
#define EOF(-1)
1.2 文件指针
指向文件的指针变量,通过文件指针可以对所指的文件进行各种操作。定义:
FILE *fp;
fp是指向FILE 结构的指针变量,通过fp可以找到存放某个文件信息的结构变量,按结构变量中的信息找到该文件,实施对文件的操作。
2. 文件的打开和关闭
FILE *fopen (const char *path,const char *mode);// 打开文件
FILE *close (FILE *stream);//关闭文件
FILE *fopen 形参:
1) const char *path:文件名称,用字符串表示。
全名:目录+文件名:char szFileName [256]="E\\temp\\123.c"(注意是 \\ )
仅文件名:系统确定,一般为当前目录名。
2) const char *mode:文件打开方式,同样用字符串表示。
r打开文件必须存在、w打开 不存在会创建,如果打开的文件已经存在 将删除重建。
a给文件追加新信息,文件必须已经存在。
3) 函数返回值:FILE 类型指针。如果运行成功,fopen()返回文件的地址,否则返回NULL。用于判断文件打开是否成功
把一个文本文件读入内存时,要将 ASCII码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读/写要花费较多的转换时间。对二进制文件的读/写不存在这种转换。
标准输入文件(键盘)、标准输出文件(显示器)、标准出错输出(出错信息)是由系统打开的,可直接使用。
FILE *close 形参:
- FILE*stream:打开文件的地址。
- 函数返回值:int类型,如果为0,则表示文件关闭成功,否则表示失败。
3. 文件的读/写操作
3.1 字符的读写
读字符:getc()&fgetc()函数:完全相同互换。
ch = fgetc(fp);
此时文件必须是读或读写方式打开的。
在文件内部的位置指针用来指向文件的当前读/写字节。当打开文件时,该指针总是指向文件的第一个字节。使用fgetc()函数后,该位置指针将向后移动一个字节。因此可连续多次使用fgetc()函数来读取多个字符。
写字符:putc()&fputc()函数:
fputc('a',fp);
被写入的文件可以用 写、读写、追加方式打开;写、读写时,文件已存在将清除原来内容,从文件首开始写。文件不存在将创建。
每当写入一个字符,文件内部指针将向后移动一个字节。
返回值:成功写入,返回写入的字符,否则返回EOF。
#include <stdio.h>
int main(){
FILE *fp;
char ch;
// 打开文件写入字符
fp = fopen("test.txt","w+");
if(fp != NULL){
printf("请输入向写入文件的字符,以回车结束\n");
ch = getchar();
while(ch != '\n'){
fputc(ch,fp);
ch = getchar();
}
fclose(fp);
}else printf("文件打开失败");
// 打开文件读取字符
fp = fopen("test.txt","r");
do{
ch = fgetc(fp);
putchar(ch);
}while(ch!=EOF);
return 0;
}
3.2 字符串的读写
读取字符串:fgets()函数:从fp所指的文件中读取n-1个字符给字符数组str中。
fgets(str,n,fp);
- str:字符数组名;
- n:正整数,读取出字符串不超过n-1个字符(最后加上'\0');
- fp:文件指针。
- 如在n-1之前遇到了换行或EOF,则读出结束。
- 返回值:为字符数组的首地址
写入字符串:fputs()函数:向fp所指的文件中写入一个字符串"abcd";
fputs("abcd",fp);
字符串可以是字符串常量、字符数组名、指针变量。
#include <stdio.h>
#include <conio.h> //getch()
#include <stdlib.h> //exit()
int main () {
FILE *fp;
char ch,strin[20] ,strout[20];
if((fp=fopen ( "123.txt" , "wt+"))==NULL)//打开文件123.txt
{
printf ( "can not open" );
getch() ;
exit(1);
}
printf ( "input a string : \n");
gets(strin);//输入字符串
fputs(strin,fp);//将该字符串写入123.txt文件中
fclose(fp);//关闭文件
if((fp=fopen("123.txt", "r"))==NULL)//再次打开文件123.txt
{
printf ( "can not open");
getch() ;
exit(1);
}
fgets (strout,21,fp);//从文件这个读取字符串
puts(strout); //输出字符串
fclose(fp);//关闭文件
getch() ;
}
3.3 格式化读写函数
fscanf(fp,"%d%s",&i,&s);
fprintf(fp,"%d%s",j,ch);
scanf()和printf()读写对象是键盘和显示器;fscanf()和fprintf()读写对象是磁盘文件。
#include<stdio.h>
#include <conio.h> //getch()
#include <stdlib.h> //exit()
struct stu {
char name[10] ;
int age;
}boya[2], boyb[2], *pp, *qq;
int main()
{
FILE *fp;
int i;
pp=boya;
qq=boyb;
if((fp=fopen ("123.txt" , "wb+"))==NULL){
printf( "Cannot open!" );
getch();
exit(1);
}
printf ( "\ninput data\n");
for(i=0; i<2; i++,pp++){
scanf("%s %d", pp->name ,&pp->age);
}
pp=boya;
for(i=0; i<2; i++,pp++){
fprintf(fp,"%s %d\n" , pp->name, pp->age);
}
rewind(fp);//将文件内指针指向开头
for(i=0; i<=2; i++,qq++){
fscanf(fp, "%s %d\n" , qq->name, &qq->age);
}
printf ( "\n\nname\tnumber\n" );
qq=boyb;
for(i=0; i<2; i++,qq++){
printf("%s\t%5d\n", qq->name, qq->age);
}
fclose(fp);
getch();
}
3.4 数据块的读写函数
fread()和fwrite()读写整块数据和一组数据:一个数组元素、一个结构变量的值。
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
- buffer:指针,fread()中表示存放输入数据的首地址;fwrite()中表示存放输出数据的首地址。
- size:表示数据块的字节数。
- count:表示要读/写的数据块块数。
- fp:表示文件指针。
函数fread()和fwrite()能够直接将内存中的一块信息以其二进制形式进行整体读/写,所以常用于实现二进制文件的读/写工作。在使用fwrite()函数将不同类型的数据写到同一文件中时,需要注意读取信息的顺序。
#include<stdio.h>
#include <conio.h> //getch()
#include <stdlib.h> //exit()
#define N 2
struct stu{
char name[10]; //姓名
int num; //学号
int age; //年龄
float score; //成绩
}boya[N], boyb[N], *pa, *pb;
int main(){
FILE *fp;
int i;
pa = boya;
pb = boyb;
if( (fp=fopen("F:\\demo.txt", "wb+")) == NULL ){
printf("Cannot open file, press any key to exit!\n");
getch();
exit(1);
}
//从键盘输入2组数据
printf("Input data:\n");
for(i=0; i<N; i++,pa++){
scanf("%s %d %d %f",pa->name, &pa->num,&pa->age, &pa->score);
}
//将数组 boya 的数据写入文件
fwrite(boya, sizeof(struct stu), N, fp);
//将文件指针重置到文件开头
rewind(fp);
//从文件读取数据并保存到数据 boyb
fread(boyb, sizeof(struct stu), N, fp);
//输出数组 boyb 中的数据
for(i=0; i<N; i++,pb++){
printf("%s %d %d %f\n", pb->name, pb->num, pb->age, pb->score);
}
fclose(fp);
return 0;
}
3.5 其他读写函数
int getw (FILE *stream);
getw():从文件中读取一个字(2字节)的信息。
- FILE*stream:文件地址。
- 函数返回值:如果成功读取,则返回当前读入的信息,否则返回EOF。
int putw (int w,FILE *stream);
putw():向文件中写一个字信息,其返回值是当前写入的信息,是一个整数。
- FILE*stream:文件地址。
- int w:整型数据。
- 函数返回值:如果成功,与输入参数w的值相等,否则返回EOF 。
#include "stdio.h"
#include <conio.h> //getch()
#include <stdlib.h> //exit()
int main(){
FILE *fp;
int word;
fp = fopen ( "123.txt","wb" );//打开二进制文件,只允许写数据
if (fp == NULL)//判断返回值是否为空
{ //若是则输出错误信息,并退出程序
printf ("Error ! !\n");
exit (1);
}
word = 100;
if (putw (word+80,fp) == word+80)//将整数写入文件中
{
printf ( "Successful write \n");
}
fclose(fp);//关闭文件
fp =fopen("123.txt","rb");//重新打开二进制文件,只允许读数据
if(fp ==NULL)//判断返回值是否为空
{//若是则输出错误信息,并退出程序
printf ( "Error opening file 123.txt\n" );
exit(1);
}
word = getw(fp) ;//从文件中读取一个整数,赋给变量word
printf ( "read: word = %d\n", word) ;
fclose(fp);//关闭文件
getch();
}
4. 文件的随机读写
- 在计算机系统中,可以将文件理解为一个完整的数据流,可以将“数据流”分为文件头、文件尾和文件主体三个部分。
- 在C语言中,可以通过FILE类型指针来描述文件流的位置,所以又称FILE类型指针为文件指针。
- 在默认情况下,文件的读取是按顺序进行的。在完成一段信息的读/写之后,文件指针移动到其后的位置上准备读取下一次读/写。
- 在特殊情况下,需要对文件进行随机读/写,即读取当前位置的信息后,并不读取紧接其后的信息,而是根据需要读取特定位置处的信息。
- 在C语言中,提供了专门的文件指针定位函数来实现对文件的随机读/写处理。
4.1 fseek()函数
int fseek (FILE *stream,long offset,int whence);
形参说明如下:
- FILE *stream:文件地址。
- long offset:文件指针偏移量。表示移动的字节数,要求位移量是long 型数据,以保证文件长度大于64KB 时不会出错。当使用常量表示位移量时,要求加后缀“L”。
- int whence:偏移起始位置。表示从何处开始计算位移量,规定的起始点有3种:文件首,当前位置和文件尾。这三种起始点的表示方法如表所示。计算单位为字节,负值表示反方向偏移。
- 函数返回值:非零值表示成功,0表示失败。
#include<stdlib.h>
#include<stdio.h>
#include <conio.h> //getch()
#define MAX 50
int main () {
FILE *fp;//文件指针
int num,i,array[MAX];
long offset; //定义位移量
for(i=0 ;i<MAX ;i++)
array[i] = i+10;//字符数组赋值
if((fp=fopen ( "123.txt", "wb" ) )==NULL)//打开二进制文件
{
printf ( "Error! ! \n" ) ;
exit(1);
}
if (fwrite(array,sizeof(int),MAX,fp)!=MAX)//将数组中的元素写入文件中
{
printf( "Error! !\n");
exit(1);
}
fclose(fp);//关闭文件
if((fp=fopen ("123.txt","rb") )==NULL)//打开二进制文件
{ printf ("Error! !\n");
exit(1);
}
while (1)
{
printf ( "Please input offset (input -1 to quit) :\n");
scanf("%ld",&offset); //输入位移量
if(offset<0) break; //若输入-1或任何负数就退出循环
if(fseek(fp, (offset*sizeof(int)), SEEK_SET)! = 0)//文件定位
{
printf ("Error using fseek ().\n");
exit (1);
}
fread (&num, sizeof(int),1, fp); //从文件中读取当前位置上的数
//输出结果
printf ("The offset is %ld , its value is %d .\n ",offset ,num);
}
fclose(fp);//关闭文件
getch ();
}
- DOS可以管理的文件大小上限是2048 MB,即2GB。
- 函数fseek()对文本文件和二进制文件的处理方式有所不同,对于二进制文件,可以获得准确的定位。对于文本文件要注意如下的问题,首先文件偏移量必须为0或通过ftell()函数获得的文件指针的当前位置,并且相对位置的起始点必须为SEEK_SET。
- 另外,函数 fseek()将指针移动到文件的开始和结束位置时,产生一个文件状态标志,必须使用clearerr()函数清除文件状态标志后,才可以继续读/写此文件。
4.2rewind()函数
rewind()函数:将当前文件指针重新移动到文件的开始位置
void rewind(FILE *stream);
- FILE*stream:文件地址。
- 函数返回值:无
rewind()函数可以将文件指针移动到文件头,并清除状态标志。其作用相当于如下的程序:
fseek(fp,0L,SEEK_SET);
clearerr(fp);
4.3 ftell()函数
ftell()函数:用于获取文件的当前读/写位置,返回值是当前读/写位置偏离文件头部的字节数。
long ftell(FILE *fp)
ban = ftell(fp);
上述代码的功能是获取fp指定的文件的当前读/写位置,并将其值传给变量 ban。
通过使用ftell()函数可以方便地获取一个文件的长度,例如下面的代码:
ftell(fp, 0L,SEEK_END); //将当前位置移到文件的末尾
len = ftell(fp) // 获取当前位置相对文件开头的位移,为字节数。
#include<stdlib.h>
#include<stdio.h>
#include <conio.h> //getch()
int main()
{
FILE *fp;
char buf[4], str[80];
printf("please input : \n" );
scanf("%s",str);//输入字符串
if((fp=fopen("Ex1215", "wb+"))==NULL)//打开二进制文件
{
printf( "Error !\n");
exit(1);
}
if(fputs(str,fp)==EOF)//将字符串写入文件中
{
printf ("Error!\n" ) ;
exit(1);
}
rewind(fp);//将文件中的位置指针移到文件的开头
printf("current position = %ld\n" , ftell(fp));//输出当前位置
fgets(buf,4,fp);//从文件中读取3个字符
//输出读取后文件中位置指针的位置
printf ("After reading in %s, Current position = %ld\n", buf, ftell(fp));
fgets(buf,4, fp);//再次从文件中读取3个字符
//再次输出读取后文件中位置指针的位置
printf ("Then, After reading in %s,Current position=%ld\n" , buf, ftell(fp));
rewind (fp);//将文件中的位置指针移到文件的开头
printf ("The position is back at %ld", ftell(fp));
fclose(fp);//关闭文件
getch();
}
ftell()函数通常跟 fseek()一起使用,作为它的 offset那个参数的值。
5. 文件管理函数
5.1 删除文件
remove()函数实现文件的删除:
remove(fp);
5.2 重命名文件
rname()函数实现文件重新命名:需要遵循C语言文件命名规则。
rname(oldname,newname);
如果函数调用成功,则返回0,如果发生错误则返回-1。重命名处理的常见错误有如下3点。
1) 指定的“旧文件名”不存在;
2) 设置的“新文件名”已经存在;
3) 视图将文件重命名并将其移动到其他目录中。
5.3 复制文件
C标准库没有专用的文件复制函数,可以自定义:
#include <stdio.h>
int copy_file(char *oldname , char *newname)
{
FILE *fpnew, *fpold;//定义文件指针
int ch;
if((fpnew= fopen (newname, "wb" ) )== NULL)//打开或建立文件
return -1;
if ((fpold = fopen (oldname , "rb" ) )== NULL)//打开已有文件
return -1;
while (1){
ch = fgetc(fpold); //从文件 oldname 中读取一个字符
if ( !feof(fpold)) // 未到文件尾
fputc(ch,fpnew);//将该字符写入data.txt文件中
else
break;//若newname 文件结束则退出循环
}
fclose (fpnew);//关闭文件
fclose (fpold);
return 0;
}
int main(){
char sou[80] ,des[80];
printf ( "Input source file : \n" ) ;
gets(sou);//输入原文件名
printf ( "Input destination file1 : \n");
gets(des);//输入目标文件名
if(copy_file(sou, des)==0)//调用copy_file函数复制文件
puts ( "copy successful . \n" ) ;
else
printf ( "Error ! ! ! ");
// 复制出第二个文件,并删除它
printf ( "Input destination file2 : \n");
gets(des);//输入目标文件名
if(copy_file(sou, des)==0)//调用copy_file函数复制文件
puts ( "copy successful . \n" ) ;
else
printf ( "Error ! ! ! ");
if (remove(des)==0)//删除文件
printf ("file %s has been deleted. \n" , des);
else
printf ("Remove Error! ! ! %s.\n" ,des);
//复制出第三个文件,重命名它
printf ( "Input destination file3 : \n");
gets(des);//输入目标文件名
if(copy_file(sou, des)==0)//调用copy_file函数复制文件
puts ( "copy successful . \n" ) ;
else
printf ( "Error ! ! ! ");
char newname [80];
printf ("New name : " );
gets (newname) ;//输入新文件名
if (rename (des,newname)==0)//给文件重命名
printf ("Renamed %s to %s.\n", des, newname);
else
printf ( "Error! ! %s. \n" , des);
}
也可以使用API函数CopyFile()和CopyFileEx()来实现文件复制。
6. 文件状态检测函数
在C语言中,提供了一组函数来检查文件的读写状态,可以跟踪文件的读/写状态,并检测读/写中是否出现未知的错误。
6.1 feof()函数
函数feof():检验文件指针是否到达文件末尾
#define feof(f)((f)->flags&_F_EOF)
在文件处理过程中,一般应用此函数检测文件指针是否到达文件末尾。
如果其返回值为非0,说明文件指针到达文件尾;否则返回值为0。
例如,在模拟实现MS-DOS系统中的COPY命令的程序代码中,使用feof()函数检测文件指针是否到达文件末尾。代码如下:
void main (){
FILE *fpFrom,*fpTo;
......
while ( ! feof(fpFrom))
fputc(fgetc(fpFrom) , fpTo);
......
}
6.2 ferror()函数
ferror()函数:检验文件的错误状态
#define ferror(f) ((f)->flags&_F_ERR)
如果函数的返回值为非0,则说明对当前文件的操作出错,否则说明当前的文件操作正常。
ferror()函数仅反映上一次文件操作的状态,因此必须在执行一次文件操作后,执行下一文件操作前调用ferror(),才可以正确反应此次操作的错误状态。
6.3 clearerr()函数
当文件操作出错后,文件状态标志为非0,此后所有的文件操作均无效。如果希望继续对文件进行操作,必须使用clearerr()函数清除此错误标志后才可以继续操作。
void clearerr(FILE *stream) ;
如:文件指针到文件末尾时会产生文件结束标志,须执行此函数后,才可以继续对文件进行操作。因此在执行 fseek (fp,0L,SEEK_SET)和fseek (fp,0,SEEK_END)语句后,注意调用此函数。
#include <stdio.h>
void main (){
FILE *fp;
fp= fopen ( "123.txt","w");//以只写方式打开文件Ex1219
//用读取只写打开的文件来产生错误
(void) getc (fp);//从文件中读区一个字符
if (ferror(fp) )//检测读取字符时是否有错误
{
//若有错误则显示错误信息
printf ( "Error reading from 123.txt\n");
clearerr(fp);//清除错误标志
}
fclose(fp);
}
在Win32 APl中,也有一系列函数来进行文件操作:
Fileapi.h 标头 - Win32 apps | Microsoft Learn
CreateFileA 函数 (fileapi.h) - Win32 apps | Microsoft Learn
参考:
张玲玲. C语言编程新手自学手册[M].北京: 机械工业出版社,2011.