许多程序在实现过程中,依赖于把数据保存到变量中,而变量是通过内存单元存储数据的,数据的处理完全由程序控制。当一个程序运行完成或终止运行,所有变量的值不再保存。当输入输出数据量较大时,就会受到限制,带来不便。
文件是解决上述问题的有效办法,它通过把数据存储在磁盘文件中,得以长久保存。当有大量数据输入时,可通过编辑工具事先建立输入数据的文件,程序运行时将不再从键盘输入,而从指定的文件上读入,从而实现数据一次输入多次使用。同样,当有大量数据输出时,可以将其输出到指定文件,不受屏幕大小限制,并且任何时候都可以查看结果文件。一个程序的运算结果还可以作为其他程序的输入,进一步加工。
(1)文件的概念
文件系统功能是操作系统的重要功能和组成部分。文件是指驻留在外部介质中的一个有序数据集,可以是源文件,目标程序文件,可执行程序,也可以是待输入的原始数据,或是一组输出的结果。
使用应用程序时,通常保存功能实现把数据从内存写入到文件,这就是所谓的存盘。打开功能实现把磁盘文件的内容读入到内存 。
(2)文本文件和二进制文件
根据数据存储的形式,文件的数据流分为字符流和二进制流,前者称为文本文件或字符文件,后者称为二进制文件。
(3)缓冲文件系统
缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用,当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读入接收的变量。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。
(4)文件结构与文件类型指针
FILE文件类型说明:
typedef struct{
short level; //缓冲区使用量
unsigned flags; //文件状态标志
char fd; //文件描述符
short bsize; //缓冲区大小
unsigned char *buffer; //文件缓冲区的首地址
unsigned char *curp; //指向文件缓冲区的工作指针
unsigned char hold; //其他信息
unsigned istemp
short token;
} FILE;
上述定义中,文件结构本身用关键字 struct 进行定义,用typedef关键字把 struct 结构类型重新命名为 FILE 。struct 内部定义的成员包含了文件缓冲区的信息。
自定义类型( typedef )不是用来定义一些新的数据类型,而是将C语言中的已有类型包括已定义的自定义类型重新命名,用新的名称代替已有数据类型,常用于简化对复杂数据类型定义的描述。如FILE就描述了整个 struct 的定义部分。
自定义类型的一般形式为:
typedef <已有类型名> <新类型名>;
一般要求重新定义的类型名用大写。
文件缓冲区是内存中用于数据存储的数据块,在文件处理过程中,程序需要访问该缓冲区实现数据的存取。因此,如何定义其中的具体数据,是文件操作类程序需要解决的首要问题。然而,文件缓冲区由系统自动分配,并不像数组那样可以通过数组名加下标来定位。为此,C语言引进FILE文件结构,其成员指针指向文件的缓冲区,通过移动指针实现对文件的操作。除此之外,在文件操作中还需用到文件的名字,状态,位置等信息。
C语言中的文件操作都是通过调用标准函数来完成的。由于结构指针的参数传递效率更高,因此C语言文件操作统一以文件指针方式实现。定义文件类型指针的格式为:
FILE * fp ;
其中,FILE是文件类型定义符,fp 是文件类型的指针变量。
文件指针是特殊指针,指向的是文件类型结构,它是多项信息的综合体。每一个文件都有自己的FILE结构和文件缓冲区,FILE结构中有一个curp成员,通过 fp->curp, 可以指示文件缓冲区中数据存取的位置。
注意:文件指针不像以前普通指针那样能进行 fp ++或 *fp 等操作, fp++将意味着指向下一个FILE结构(如果存在)。
文件操作具有顺序性的特点,前一个数据取出后,下一次将顺序取后一个数据,fp->curp 会发生改变,但改变是隐含在文件读写操作中的,而不需要在编程时写上 fp->curp ++ ,这样类似的操作将由操作系统在文件读写时自动完成。
(5)文件控制块
已知文件缓冲区与磁盘文件之间的处理是由操作系统自动完成的,那么操作系统具体又是如何处理的呢?答案是通过操作文件控制块FCB( File Control Block)实现的。文件控制块包括文件属性,文件名,驱动器号,扩展名,文件长度以及文件记录状态等信息。
操作系统为了控制管理文件,采用文件表来管理文件,它给每一个文件顺序编号,并对应一个不同的FCB。程序要访问文件时,用一个FILE指针指向文件缓冲区,此时操作系统会把文件缓冲区与FCB相关联。因此不管有多少文件,操作系统都能有效地控制管理。如果程序想要对某个文件进行操作,只要告诉缓冲文件系统“要访问哪个文件”,操作系统会根据文件表立即在磁盘中找到该文件,并把相应的FCB编号与文件缓冲区关联。
(6)文件处理步骤
1.定义文件指针
2.打开文件:文件指针指向磁盘文件缓冲区
打开文件功能用于建立系统与要操作的某个文件之间的关联,指定这个文件名并请求系统分配相应的文件缓冲区内存单元。打开文件由标准函数 fopen()实现,其一般调用形式为:
fopen("文件名","文件打开方式");
说明:1.该函数有返回值。如果执行成功,函数将返回包含文件缓冲区等信息的FILE结构地址,赋给文件指针 fp。否则,返回一个NULL的FILE指针。 2.括号内的两个参数都是字符串。"文件名"指出要对哪个具体文件进行操作,一般要指定文件的路径,如果不写出路径,则默认与应用程序的当前路径相同。文件路径若包含绝对完整路径,则定位子目录用的斜杆‘ \ ’需要用双斜杠' \ '.
文件打开方式
使用方式 | 含义 |
---|---|
" r " | 打开文本文件进行只读 |
" w " | 建立新文本文件进行只写 |
" a " | 打开文本文件进行追加 |
" r+" | 打卡文本文件进行读 / 写 |
" w+" | 建立新文本文件进行读 / 写 |
" a+" | 打开文本文件进行读 / 写 / 追加 |
二进制文件打开方式比文本文件多了个 ' b '
下面两种方式都以只读的方式打开 abc.txt 文件:
fp = fopen ( " abc.txt "," r ") ;
或 char *p = " abc.txt " ;
fp = fopen( p ," r ");
执行标准函数 fopen(),计算机将完成下述步骤工作 :
1. 在磁盘中找到指定文件 .
2. 在内存中分配保存一个FILE类型结构单元(16B).
3. 在内存中分配文件缓冲区单元(521B).
4. 返回FILE结构地址(回送给 fp ).
文件打开的实质是把磁盘文件与文件缓冲区对应起来,这样后面的文件读写操作只需使用文件指针即可。如果 fopen()返回NULL,表明文件无法正常打开,其原因可能是文件不存在,路径不对,或者文件已经被别的程序打开,也可能是文件存储有问题。为了保证文件操作的可靠性,调用 fopen()函数时最好做一个判断,以确保文件正常打开后再进行读写。
其形式:
if (( fp = fopen (" abc.txt ", " r ")) == NULL ) {
printf(" File open error !\n ") ;
exit ( 0 ) ;
}
其中 exit ( 0 ) 是系统标准函数,作用是关闭所有打开的文件,并终止程序的运行。参数 0 表示程序正常结束,非 0 参数通常表示不正常的程序结束。
一旦文件经 fopen()正常打开,对该文件的操作方式就被确定,并且直至文件关闭都不变,即若一个文件按 r 的方式打开,则只能对该文件进行读操作,而不能进行写入操作。
一般进行文件读写操作时,常用到如下规则:
if 读文件
指定的文件必须存在,否则出错;
if 写文件(指定的文件可以存在或不存在)
if 以 " w " 方式写
if 该文件已经存在
原文件将被删去然后重新建立;
else
按指定的名字新建一个文件 ;
if 以 " a " 方式写
if 该文件已存在
写入的数据将被添加到指定文件原有数据的后面,不会删去原来的内容;
else
与 " w " 相同 ;
if 文件同时读和写
使用 " r++ " , " w++ " 或 " a++ " 打开文件 ;
C语言允许同时打开多个文件,不同文件采用不同文件指针指示,但不允许同一文件在关闭前被再次打开。
3.文件处理:文件读写操作
字符方式文件读写函数: fgetc() 和 fputc() ;
对于文本文件,存取的数据都是ASCII码字符文本,使用这两个函数读写文件时,逐个字符地进行文本读写。
fgetc()函数实现从 fp 所指示的磁盘文件读入一个字符到 ch 。
函数调用格式:
ch = fgetc( fp );
该函数与 getchar()函数功能类似,getchar()从键盘上读入一个字符。
fputc()函数把一个字符 ch 写到 fp 所指示的磁盘文件上。
函数调用格式:
fputc( ch ,fp );
函数返回值若写文件成功为 ch ,若写文件失败则为 EOF 。
该函数同 putchar()函数类似,putchar( ch )把ch显示在屏幕上。EOF值为-1.
字符串方式文件读写函数 :fgets()和 fputs()
这两个函数以字符串的方式来对文本文件进行读写。读写文件时一次读取或写入的是字符串。
fputs()函数用来向指定的文本文件写入一个字符串。
函数调用格式:
fputs( s , fp ) ;
其中, s 是要写入的字符串,可以是字符数组名,字符型指针变量或者字符串常量, fp 是文件指针。该函数把 s 写入文件时,字符串 s 的结束符 ' \0 ' 不写入文件。
若函数执行成功,函数返回所写的最后一个字符 ;否则,函数返回 EOF。
fgets()用来从文本文件中读取字符串。
函数调用格式:
fgets( s ,n,fp ) ;
s 与 fputs()一样,n是指定读入的字符个数, fp 是文件指针。函数被调用时,最多读取
n-1 个字符,并将读入的字符串存入指针 s 所指向内存地址开始的 n-1 个连续的内存单元中。当函数读取的字符达到指定的个数,或接收到换行符,或接收到文件结束标志 EOF 时,将在读取的字符后面自动添加一个 ' \0 ' 字符;若有换行符,则将换行符保留(换行符在 ' \0' 字符之前);若有EOF,则不保留EOF。该函数若执行成功,返回读取的字符串;如果失败,返回空指针,这时,s的内容不确定。
格式化方式文件读写函数 fscanf() 和 fprintf()
fscanf() 用于从文件中按照给定的控制格式读取数据,而 fprintf()用于按照给定的控制格式向文件写入数据。
函数调用格式:
fscanf( 文件指针,格式字符串,输入表) ;
fprintf( 文件指针,格式字符串,输出表) ;
例如:
FILE *fp;
int n;
flaot x;
fp = fopen("a.txt","r");
fscanf(fp,"%d %f",&n,&x);
//从文件a.txt分别读入整型数到变量n,浮点数到变量x
按组进行读写
fwrite()
size_t fwrite( const void*buffer , size_t size , size_t count , FILE *fp)
// 将一个数据块写入fp指向的文件中
// buffer是指向所要输出参数的地址
// size是每次所要写入的字节数
// count是写入次数
// fp目标文件指针
// 读写成功则返回写入字节数
fread()
size_t fread( const void*buffer , size_t size , size_t count , FILE *fp)
// 从fp指向的文件中读取一个数据块
// buffer是指向所要读出的地址
// size是每次所要读出的字节数
// count是读出次数
// fp目标文件指针
// 读写成功则返回读出字节数
位置指针
rewind()
void rewind( FILE *fp )
// 将位置指针重新定位到文件开头
// 没有返回值
fseek()
int fseek( FILE *fp ,long offset ,int start)
// fseek()可以按照需要任意移动位置
// offset是以start参数为起始位置的偏移量
// start 取值如下:
// SEEK_SET SEEK_CUR SEEK_END
// 相应整数值为:0,1,2
// 分别表示:文件开头位置,当前位置,文件结束位置
// 成功则返回0,否则返回非0
ftell()
long ftell( FILE *fp )
// 获取位置指针当前位置相对于文件首的偏移字节数
// 函数调用成功则返回当前文件的读写位置,否则返回-1
文件出错检测
ferror()
int ferror( FILE *fp )
// 检测对文件指针fp所指向的文件读写操作出现的错误
// 没有出错则返回0,否则返回非0
// 由于每次进行读写操作后,再调用ferror()函数都会产生一个新的值
// 因此在调用读写操作函数后要及时地调用ferror()函数对其经行检测,否则信息会丢失
clearerr()
void clearerr( FILE *fp )
// 复位错误标志,无返回值
perror()
void perror( const char *string)
// 将上一个函数发生错误的原因输出到标准设备
// 参数string所指字符串先被打印,再加上错误原因字符串
strerror()
char *strerror( int errnum)
// 将错误的数值信息转化为易于理解的字符串信息
原文链接:https://blog.csdn.net/qq_45279570/article/details/108050221
4.关闭文件
当文件操作完成后应关闭它,防止不正常的操作。如果把数据写入文件,首先是写到文件缓冲区,只要当写满512B,才会由系统真正写入磁盘扇区。如果写的数据不到512B,发生程序异常终止,那么这些缓冲区中的数据就会丢失。当文件操作结束时,即使未写满512B,通过文件关闭,能强制把缓冲区中的数据写入磁盘扇区,确保写文件的正常完成。
关闭文件通过调用标准函数 fclose ()实现,其一般格式为:
fclose (文件指针) ;
该函数将返回一个整数,若 0 表示正常关闭文件,反之异常。所以关闭文件也应使用条件判断:
if ( fclose ( fp )) {
printf (" Can not close the file !\n");
exit ( 0 ) ;
关闭文件除了强制把缓冲区的数据写入磁盘外,还将释放文件缓冲区单元和FILE结构,使文件指针与具体文件脱钩。
在编写程序时应养成文件使用结束后及时关闭文件的习惯,一则确保数据完整写入文件,二则及时释放不用的文件缓冲区单元。
题目:从键盘输入一些字符,逐个把它们送到磁盘上去,直到输入一个#为止。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE*fp=NULL;
char filename[25];
char ch;
printf("输入你要保存到的文件的名称:\n");
gets(filename);
if((fp=fopen(filename,"w"))==NULL)
{
printf("error: cannot open file!\n");
exit(0);
}
printf("现在你可以输入你要保存的一些字符,以#结束:\n");
getchar();
while((ch=getchar())!='#'){
fputc(ch,fp);
}
fclose(fp);
system("pause");
return 0;
}
以上实例运行输出结果为:
输入你要保存到的文件的名称: test.txt 现在你可以输入你要保存的一些字符,以#结束: www.runoob.com #
实例: 有五个学生,每个学生有3门课的成绩,从键盘输入以上数据(包括学生号,姓名,三门课成绩),计算出平均成绩,况原有的数据和计算出的平均分数存放在磁盘文件"stud"中
#include<stdio.h>
#include<stdlib.h>
typedef struct{
int ID;
int math;
int English;
int C;
int avargrade;
char name[20];
}Stu;
int main()
{
FILE*fp;
Stu stu[5];
int i,avargrade=0;
printf("请输入5个同学的信息:学生号,姓名,3门成绩:\n");
for(i=0;i<5;i++)
{
scanf("%d %s %d %d %d",&(stu[i].ID),stu[i].name,&(stu[i].math),&(stu[i].English),&(stu[i].C));
stu[i].avargrade=(stu[i].math+stu[i].English+stu[i].C)/3;
}
if((fp=fopen("stud","w"))==NULL)
{
printf("error :cannot open file!\n");
exit(0);
}
for(i=0;i<5;i++)
fprintf(fp,"%d %s %d %d %d %d\n",stu[i].ID,stu[i].name,stu[i].math,stu[i].English,
stu[i].C,stu[i].avargrade);
fclose(fp);
// system("pause");
return 0;
}
以上实例运行输出结果后:
请输入5个同学的信息:学生号,姓名,3门成绩: 1 a 60 70 80 2 b 60 80 90 3 c 59 39 89 4 e 56 88 98 5 d 43 88 78
打开 stud文件,内容如下
1 a 60 70 80 70 2 b 60 80 90 76 3 c 59 39 89 62 4 e 56 88 98 80 5 d 43 88 78 69
题目:有两个磁盘文件A和B,各存放一行字母,要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件C中。
程序分析:你需要先创建 A.txt 与 B.txt。
A.txt文件内容:
123
B.txt文件内容:
456
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE*fa,*fb,*fc;
int i,j,k;
char str[100],str1[100];
char tem;
if((fa=fopen("A.txt","r"))==NULL) // A.txt 文件需要存在
{
printf("error: cannot open A file!\n");
exit(0);
}
fgets(str,99,fa);
fclose(fa);
if((fb=fopen("B.txt","r"))==NULL) // B.txt 文件需要存在
{
printf("error: cannot open B file!\n");
exit(0);
}
fgets(str1,100,fb);
fclose(fb);
strcat(str,str1);
for(i=strlen(str)-1;i>1;i--)
for(j=0;j<i;j++)
if(str[j]>str[j+1])
{
tem=str[j];
str[j]=str[j+1];
str[j+1]=tem;
}
if((fc=fopen("C.txt","w"))==NULL) // 合并为 C.txt
{
printf("error: cannot open C file!\n");
exit(0);
}
fputs(str,fc);
fclose(fc);
system("pause");
return 0;
}
以上实例运行输出结果后,打开 C.txt 内容如下:
123456
题目:从键盘输入一个字符串,将小写字母全部转换成大写字母,然后输出到一个磁盘文件"test"中保存。 输入的字符串以!结束。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE*fp=NULL;
char str[50];
int i,len;
printf("输入一个字符串:\n");
gets(str);
len=strlen(str);
for(i=0;i<len;i++)
{
if(str[i]<='z'&&str[i]>='a')
str[i]-=32;
}
if((fp=fopen("test","w"))==NULL)
{
printf("error: cannot open file!\n");
exit(0);
}
fprintf(fp,"%s",str);
fclose(fp);
system("pause");
return 0;
}