目录
一、什么是文件
磁盘上的文件是文件
在程序设计中,一般谈的文件由两种:程序文件 和 数据文件
程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据
比如程序运行需要从中读取数据的文件,或者输出内容的文件
本章讨论的是数据文件
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用
这里处理的就是磁盘上的文件
二、文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用
文件包含3部分:文件路径 + 文件名主干 + 文件后缀
例如:c: \ code \ test.txt
为了方便起见,文件标识被称为文件名
三、文件类型
根据数据的组织形式,数据文件被称为 文本文件(人看的) 或者 二进制文件(电脑看的)
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是 二进制文件
如果要在外存上以ASCII码的型存储,则需要在存储前转换
以ASCII字符的形式存储的文件就是文本文件
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII码值形式存储,也可以使用二进制形式存储
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节)
而二进制形式输出,在磁盘上只占4个字节
测试代码
#include<stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt","wb");
fopen打开的文件叫test.txt,wb代表write bin以二进制的形式写进去
fwrite(&a,4,1,pf);
a的地址,4个字节,1个这样的数据,放到pf这个文件里面去
fclose(pf);
pf = NULL;
return 0;
}
四、文件缓冲区
ANSIC标准采用“缓冲文件系统”处理的数据文件的
所谓缓冲文件系统是指 系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”
从内存向磁盘输出数据首先会送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上
如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区中(充满缓冲区)
然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)
缓冲区的大小跟C编译系统决定的
五、文件指针
缓冲文件系统中,关键的概念是 “文件类型指针”,简称“文件指针”
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,状态及位置)
这些信息是保存在一个结构体变量中,该结构体类型是有系统声明,取名FILE
例如,VS2008编译环境提供的stdio.h头文件中有以下文件类型申明:
struct _iobuf
{
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关
心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
文件指针变量
FILE* pf;
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
六、文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束后应该关闭文件
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件
也相当于建立了指针和文件的关系
ANSIC规定使用fopen函数来打开文件,fclose来关闭文件
FILE* fopen(const char* filename,const char* mode); mode为打开方式
int fclose(FILE* stream); 不会置成空指针
“r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
“a”(追加) 向文本文件尾添加数据 出错
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
“ab”(追加) 向一个二进制文件尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,建议一个新的文件 建立一个新的文件
“a+”(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件 出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“ab+”(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件
#include<stdio.h>
int main()
{
.. 表示上一级路径
. 表示当前路径
fopen("../../test.txt","r");
fopen("test.txt","r"); 相对路径
fopen("C:\\2020_code\\84\\test_5_6\\test.txt","r"); 绝对路径
FILE* pf = fopen("test.txt","r"); FILE* pf = fopen("test.txt","w");
若打开文件不存在,会传回来空指针NULL w写入 会建立一个新的文件覆盖掉原来的文件,导致内容消失
加一个判断语句 除非是追加
if(pf == NULL)
{
printf("%s\n",strerror(errno));
return 0;
}
关闭文件
fclose(pf); fclose接收的是FILE*类型的指针
pf = NULL;
return 0;
}
七、文件的顺序读写
字符输入函数 fgetc 所有输入流
字符输出函数 fputc 所有输出流
文本行输入函数 fgets 所有输入流
文本行输出函数 fputs 所有输出流
格式化输入函数 fscanf 所有输入流
格式化输出函数 fprintf 所有输出流
二进制输入 fread 文件
二进制输出 fwrite 文件
1.字符fputc,fgetc
int fputc(int c,FILE* stream) 写入一个字符到流中或标准输出,人话:从程序中写数据输入文件
int fgetc(FILE* steam) 从流中一个一个读取字符或者标准输入,人话:从文件中读取数据到程序
无符号char强制转换为int的形式返回读取的字符,如果到达文件末尾或者发生读错误,则返回EOF
#include<stdio.h>
int main()
{
w是写入
FILE* pfWrite = fopen("text.txt","w");
if(pfWrite == NULL)
{
printf("%s\n",strerror(errno));
return 0;
}
从程序中写数据输入文件
fputc('F',pfWrite);
fputc('6',pfWrite);
fputc('G',pfWrite);
关闭文件
fclose(pfWrite);
pfWrite = NULL;
return 0;
}
此时text.txt里放的F6G
int main()
{
r是读
FILE* pfRead = fopen("text.txt","r");
if(pfRead == NULL)
{
printf("%s\n",strerror(errno));
return 0;
}
从文件中读取数据到程序
printf("%c:,fgetc(pfRead);
printf("%c:,fgetc(pfRead);
printf("%c:,fgetc(pfRead);
打印结果是F6G
关闭文件
fclose(pfRead);
pfRead = NULL;
return 0;
}
2.文本行fgets,fputs,puts
int puts(const char* string) 写一个字符串到标准输出上(屏幕)人话:从文件中读取数据到程序
打印完一行数据后会自动换行
int fgets(char* string,int n,FILE* stream) 从流读取的信息放到string里面,人话:从文件中读取数据到程序
n代表最后读取多少个字符,读取一行内所有内容
int fputs(const char* string,FILE* stream) 写一个字符串到流中,人话:从程序中写入数据到文件
不会自己换行
假设test.txt放的是 abc
hello
int main()
{
char buf[1024] = {0};
FILE* pf = fopen("test.txt","r");
if(pf == NULL)
{
return 0;
}
从文件中读取数据到程序
fgets(buf,1024,pf);
printf("%s\n",buf);
puts(buf); puts打印完一行数据后会自动换行
打印为abc,abc后面本身有一个换行,fgets会把换行\n也读进去
fgets(buf,1024,pf);
printf("%s",buf);
打印为hello,因为hello后面没有换行,所以不会换行
puts(buf); puts打印完一行数据后会自动换行
fclose(pf);
pf = NULL;
return 0;
}
int main()
{
char buf[1024] = {0};
FILE* pf = fopen("text.txt","w");
if(pf == NULL)
{
return 0;
}
从程序中写数据到文件中
fputs("hello",pf); fputs("hello\n",pf);
fputs("world",pf); fputs("world\n",pf);
在文件text.txt写上了helloworld 要换行需要自己添加\n
不会换行
fclose(pf);
pf = NULL;
return 0;
}
3.格式化(复杂的数据类型)fscanf,fprintf
int fprintf(FILE* stream,const char* format[,argument]...)
打印格式化的数据到流中,人话:从程序中输入数据到文件
int fscanf(FILE* stream,const wchar_t* format[,argument]...);
写入格式化的数据到流中,人话:从文件中读取数据到程序中
后面一串是地址
放进去
struct S
{
int n;
float score;
char arr[10];
};
int main()
{
struct S s = {100,3.14f,"bit"};
FILE* pf = fopen("text.txt","w");
if(pf == NULL)
{
return 0;
}
程序中的数据格式化的形式写入文件
fprintf(pf,"%d %f %s",s.n,s.score,s.arr);
显示到text.txt这个文件中
fclose(pf);
pf = NULL;
return 0;
}
读出来的
struct S
{
int n;
float score;
char arr[10];
};
int main()
{
struct S s = {0};
FILE* pf = fopen("text.txt","r");
if(pf == NULL)
{
return 0;
}
将文本中的数据格式化的形式输入程序
fscanf(pf,"%d %f %s",&(s.n),&(s.score),s.arr);
printf("%d %f %s\n",s.n,s.score,s.arr);
fclose(pf);
pf = NULL;
return 0;
}
5.二进制fread,fwrite
size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream);
以二进制的形式写入文件,buffer指针,size元素大小,count几个元素
size_t fread(const void* buffer,size_t size,size_t count ,FILE* stream);
以二进制的形式读取文件,buffer指针,size元素大小,count几个元素
如果读取的个数大于原本的个数,返回的值是原本的元素个数
如果读取的个数小于原本的个数,返回的值是读取的元素个数
如果不成功或读到文件末尾返回0
struct S
{
char name[20];
int age;
double score;
};
int main()
{
struct S s = {"张三",20,55.6};
写入
FILE* pf = fopen("test.txt","wb");
if(pf == NULL)
{
return 0;
}
二进制的形式写入文件
fwrite(&s,sizeof(struct S),1,pf);
fclose(pf);
pf = NULL;
return 0;
}
int main()
{
struct S tmp = {0};
FILE* pf = fopen("test.txt","rb");
if(pf == NULL)
{
return 0;
}
以二进制的形式读取文件
fread(&tmp,sizeof(struct S),1,pf);
printf("%s %d %lf\n",tmp.name,tmp.age,tmp.score);
fclose(pf);
pf = NULL;
return 0;
}
6.sscanf,sprintf
int sscanf(const char* buffer,const char* format[,argument]...);
从一个字符串中读取格式化的数据
int sprintf(char* buff,const char* format[,argument]...)
写一个格式化的数据到字符串去
struct S
{
int n;
float score;
char arr[10];
};
int main()
{
struct S s = {100,3.14f,"abcdef"};
struct S tmp = {0};
char buf[1024] = {0};
把格式化的数据转成字符串存储到buf
sprintf(buf,"%d %f %s",s.n,s.score,s.arr);
printf("%s\n",buf);字符串
从buf中读取格式化的数据到tmp中
sscanf(buf,"%d %f %s",&(tmp.n),&(tmp.score),tmp.arr);
printf("%d %f %s\n",tmp.n,tmp.score,tmp.arr); 格式化
return 0;
}
7.对比一组函数
scnaf,fscanf,sscanf
printf,fprintf,sprintf
scanf,printf 是针对标准输入流/标准输出流的格式化输入/输出语句
fscanf,fprintf 是针对所有输入流/输出流的格式化输入/输出语句
sscanf 是从字符串中读取格式化的数据
sprintf 是把格式化的数据输出成(存储到字符串)
八、文件的随机读写
1.fseek
根据文件指针的位置和偏移量来定位文件指针
itn fseek(FILE* stream,long int offset,int origin);
offset是偏移量
origin是文件指针的当前位置:三个选项
文件指针的当前位置:SEEK_CUR
文件指针的末尾位置:SEEK_END
文件指针的起始位置:SEEK_SET
假设test.txt文件中存储的是abcdef
int main()
{
FILE* pf = fopen("test.txt","r");
if(pf == NULL)
{
return 0;
}
定位文件指针
fseek(pf,4,SEEK_CUR); fseek(pf,-2,SEEK_END);
读取文件
int ch = fgetc(pf);
printf("%c\n",ch);
打印结果为e
fclose(pf);
pf = NULL;
return 0;
}
2.ftell
返回文件指针相对于起始位置的偏移量
long int ftell(FILE* stream);
假设test.txt里面有"abcdef"
int main()
{
FILE* pf = fopen("test.txt","r");
if(pf == NULL)
{
return 0;
}
fseek(pf,-2,SEEK_END);
int pos = ftell(pf);
printf("%d\n",pos);
打印结果为4
fclose(pf);
pf = NULL;
return 0;
}
3.rewind
让文件指针回到文件的起始位置
void rewind(FILE* stream);
假设test.txt里面有"abcdef"
int main()
{
FILE* pf = fopen("test.txt","r");
if(pf == NULL)
{
return 0;
}
int ch = fgetc(pf);
printf("%c\n",ch);
rewind(pf);
ch = fgetc(pf);
printf("%c\n",ch);
两次打印结果都为a
fclose(pf);
pf = NULL;
return 0;
}
九、文件的结束判定
1.feof(末尾)
int feof(FILE* stream);
若当前指针位置是文件末尾,返回一个非0值,
若当前指针位置不是文件末尾,返回0且无错误返回
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件是否结束
而是应用于当文件读取结束的时候,判断是读取失败结束 还是遇到文件尾结束
-
文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgetc)
fgetc判断是否为EOF
fgetc判断返回值是否为NULL -
二进制文件的读取结束判断,判断返回值是或否小于实际要读的个数
fread判断返回值是否小于实际要读的个数
2.ferror(无错反0,错反非0,流上的错误)
int ferror(FILE* stream); 测试流上的错误
如果流中未发生错误,则返回 0。否则,它将返回一个非零值。
3.文本文件的例子
#include<stdio.h>
#include<stdlib.h>
int main()
{
int c; 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt","r");
if(!fp)
{
perror("FILE opening failed"); 报错误信息,比strerror方便
return Exit_FAILURE;
}
fgetc当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while((c = fgetc(pf)) ! = EOF)
无符号char强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。
{
putchar(c);
}
判断是什么原因结束的
if(ferror(fp))
put("I/O error when reading");
else if(feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
4.二进制文件的例子
#include<stdio.h>
enum{SIZE = 5};
int main()
{
double a[SIZE] = {1.0,2.0,3.0,4.0,5.0};
double b = 0.0;
size_t ret_code = 0;
FILE* fp = fopen("test.bin","wb"); 必须用二进制模式
fwrite(a,sizeof(*a),SIZE,fp); 写double 的数组
fclose(fp);
fp = fopen("test.bin","rb);
读double 的数组
while((ret_cod = fread(&b,sizeof(double),1,fp))>=1)
fread判断返回值是否小于实际要读的个数
如果读取的个数大于原本的个数,返回的值是读取的元素个数
如果读取的个数小于原本的个数,返回的值是读取的元素个数
如果不成功或读到文件末尾返回 0。
{
printf("%lf\n".b);
}
if(feof(fp))
printf("Error reading test,bin: unexpected end of ile\n");
else if(ferror(fp))
perror("Error reading test.bin");
fclose(fp);
fp = NULL;
return 0;
}