目录
一、为什么要使用文件?
#include <stdio.h>
int main()
{
int a = 0;
printf("%d\n", a);
scanf("%d", &a);
printf("%d\n", a);
return 0;
}
这里程序运行一次输入输出后再次运行计算机不会记住输入的值,这是因为&a是内存上的一块区域,但在电脑上创建一个文本之类的文件电脑就能记录它,因为这些文件被存放在硬盘上,因此使用文件就是为了数据可以持久化保存。
二、什么是文件?
这里所说的文件就是磁盘(也叫做硬盘)上的文件。
文件从功能上分类可以将文件分为程序文件和数据文件。
1.程序文件
包括源程序文件(后缀为.c),目标文件(windows环境下后缀为.obj)和可执行文件(后缀为.exe)。
注:我们以前处理数据的输入输出都以终端为对象,输出时的终端也就是屏幕,输入时数据从键盘输入到屏幕,再从屏幕中读取。
2.数据文件
文件内容不一定是程序,可能是程序运行时读写的数据。
例如:
这里主要是说如何通过程序文件操作数据文件。
3.文件名
文件名是一个文件的唯一标识。
组成:文件路径+文件名主干+文件后缀。
三、二进制文件和文本文件(从内容角度分类)
数据在内存中以二进制形式存储,不加转换输入到外存的文件中,就是二进制文件。
字符用ASCⅡ码存储,存储前需要穿环,以ASCⅡ码的形式存储的文件就是文本文件(数值型数据可以用ASCⅡ码存储也可以二进制存储)。
注:ASCⅡ码值在内存中也是以二进制的形式存放。
例如:10000如果要用ASCⅡ码存储,则占用5个字节(每个字符一个字节),二进制占4个byte。
#include <stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");//以二进制写的形式打开文件
fwrite(&a, 4, 1, pf);//二进制形式写到文件中
fclose(pf);//关闭文件
pf = NULL;
return 0;
}
运行代码后会在本文件夹中创建一个名为test.txt的文本文件,将其打开到VS上再以二进制的打开会出现十六进制形式的a值。
注:文本编译器无法读懂二进制,但VS编译器上可以提供二进制的打开方式。
四、文件的打开与关闭
1.流和标准流
1.1流
为了方便程序数据和外部设备之间的输入输出,诞生了流的概念,通过流来操作。
注:操作流的步骤:
1.打开流;
2.都/写;
3.关闭流。
1.2标准流
从键盘上输入,向屏幕上输出没有打开流,这是为什么?
C语言程序启动时,默认打开了3个流:
1.stdin:标准输入流,scanf函数就从这里读取数据;
2.stdout:标准输出流,printf函数将信息输出到这;
3.stderr:标准错误流,大多数环境输出到显示器界面。
三个标准流的类型:FILE*,文件指针。
注:C语言中,通过FILE*的文件指针来维护流的各种操作。
2.文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区来存放文件信息(名字、状态、位置等),这些信息保存在一个结构体中,该结构体类型由系统声明,取名为FILE。
FILE相当于一个结构体重命名,其内容包括:
typedef struct iobuf
{
char* ptr;
int cnt;
char* base;
int flag;
int file;
int charbuf;
int bufsiz;
char* tmpfname;
}FILE;
注:1.不同编译器FILE的内容不尽相同,但是也都大同小异;
2.文件存放在硬盘中,文件信息去存放在内存中。
操作文件:
注:打开一个文件时,系统会根据文件情况自动创建一个FILE变量,并填充信息,使用时不必关心细节,通常用一个指针来维稳FILE的变量。
3.打开和关闭文件
C语言规定,fopen用来打开文件,fclose用来关闭文件。
3.1 fopen
组成:FILE *fopen(const char *filename,const char *mode);(打开成功返回有效地址,失败返回NULL)
#include <stdio.h>
int main()
{
//如果文件在桌面上,可以加上文件途径,防止转义字符可以再加一个“\”。
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//这里就可以开始读/写文件
fclose(pf);//关闭文件
pf = NULL;
return 0;
}
注:1.路径里带磁盘名是绝对路径,不带磁盘名是相对路径;
2. test.txt的上一个路径可以表示为.\\..\\test.txt,这就是相对路径;
3. “.”表示当前路径,“..”表示上一个路径。
3.2 fclose
组成:int fclose(FILE *p);
文件读的时候只能读,写的时候只能写。
五、文件的顺序读写
1.顺序读写函数
1.1 fputc
组成:int fputc(int character,FILE *stream);
例如:
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");//将写改为读时不会清空文件内容。
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
fclose(pf);
pf = NULL;
return 0;
}
此时test.txt的内容就是abc。
1.2 fgetc
组成:int fgetc(FILE *stream)
读取成功时,返回读取到的字符的ASCⅡ码值,失败返回EOF,且可以一次读取多个字符。
注:EOF本质上为-1。
例如:
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c", ch);
}
fclose(pf);
pf = NULL;
return 0;
}
此时打印结果就是文本test.txt中的内容。
注:1.连续向同一个文本中用fgets写两个字符串,两个字符串连续,且可以加上\n换行;
2.fgetc(stdin);//从键盘(标准输入流)上获取;
3.fputc(ch,stdout);//将字符输出到屏幕(标准输出流)上。
1.3 fputs
格式:int fputs(const char *str,FILE *stream);
功能:向stream输入一串字符串。
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("I am a Student\n", pf);//数据输入到文件中
fputs("Are you OK?", pf);
fclose(pf);
pf = NULL;
return 0;
}
1.4 fgets
组成:char *fgets(char *str,int num,FILE *stream);
功能:从stream中读取一个字符串,读到的字符串会被拷贝到str中,num时最多拷贝进去的字符个数。
例如:
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[20] = { 0 };
fgets(arr, 20, stdin);//从键盘上输入
fputs(arr, stdout); //输出到屏幕上
fclose(pf);
pf = NULL;
return 0;
}
注:如果num超过文本中一行字符串的个数,那么他只会读到\n的位置后加\0结尾,例如test.txt文本中有abcde\nfghijkl两行字符,则fgets只会读7个字符(包括\n和\0);
1.5 fprintf
组成: int fprintf(FILE *stream,const char *format);
fgetc、fputc、fgets、fputs都是为了输入输出字符类型的数据准备的,那么fscanf、fprintf则是为了整型、浮点型数据准备的。
注:可变参数列表是指参数的个数可变。
#include <stdio.h>
int main()
{
char name[20] = "zhangsan";
int age = 20;
float score = 95.5f;
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf, "%s %d %f",name, age, score);
fclose(pf);
pf = NULL;
return 0;
}
注:1.fscanf和scanf、fprintf和printf都是包含关系;
2.打印出的score可能会因为浮点数无法精确地存储到内存中。
1.6 fscanf
组成:int fscanf(FILE *stream,const char *format...);//fscanf和fprintf差不多
#include <stdio.h>
int main()
{
char name[20] = "zhangsan";
int age = 20;
float score = 95.5f;
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fscanf(pf, "%s %d %f",name, &age, &score);
printf("%s %d %f",name,age,score);
fclose(pf);
pf = NULL;
return 0;
}
注:1.scanf和printf:针对所有输入/输出流,格式化输入/输出函数;
2.fscanf和fprintf:针对所有输入/输出流,格式化输入/输出函数。
2.sprintf和sscanf(这两者不涉及文件操作)
2.1 sprintf
组成:int sprintf(char *str,const char *format...);
功能:把后面格式化的数据输入到str中,可以理解为将格式化的数据转换成字符串。
例如:
#include <stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
char arr[100] = { 0 };
struct S s = { "wangwu",32,66.6f };
sprintf(arr, "%s %d %f", s.name, s.age, s.score);
printf("%s", arr);
return 0;
}
2.2 sscanf
组成:int sscanf ( const char * s, const char * format, ...);
功能:从字符串中提取格式化数据,可以理解为将字符串转换为格式化数据。
例如:
#include <stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
char arr[100] = { 0 };
struct S s = { "wangwu",32,66.6f };
sprintf(arr, "%s %d %f", s.name, s.age, s.score);
struct S tmp = { 0 };
sscanf(arr, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
printf("%s %d %f", tmp.name,tmp.age,tmp.score);
return 0;
}
这时候就能够从arr中提取格式化数据放到结构体tmp中。
注:sscanf和sprintf虽然参数差不多,但是它们的逻辑是相反的。
3. fwrite(以二进制形式输出)
组成:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
功能:写数据块到文件中,吧ptr指向空间里count个大小为size字节的数据写到stream中。
4. fread(读取二进制数据到文件中)
组成:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
功能:从文件中读数据,将stream维护的文件中count个大小为size的数据读取到ptr中。
注:返回值是实际读到的字符个数,对于fread而言,如果count为5,但文件中只有3个元素,那么有几个读几个,返回值也是3.
六、文件的随机读写
1.fseek
组成:int fseek ( FILE * stream, long int offset(偏移量,可加可减), int origin(起始位置,可以用来定位指针) );
功能:根据文件指针的位置和偏移量来定义文件指针(文件内容的光标)。
注:origin有三种选项
1.SEEK_SET:文件的起始位置;
2.SEEK_CUR:文件指针的当前位置;
3.SEEK_END:文件末尾。
例如:文件中有abcdef这个字符串
fgetc(pf);//a
fgetc(pf);//b
fseek(pf, 4, SEEK_SET);//e
//fseek(pf, 2, SEEK_CUR);//e
//fseek(pf, -2, SEEK_END);//e
三种形式的fseek结果都一样。
2.ftell
组成:long int ftell ( FILE * stream );
功能:返回文件指针(当前光标位置)相对于起始位置的偏移量。
3.rewind
组成:void rewind ( FILE * stream );
功能:能够让文件指针的位置回到起始位置。
七、文件读写结束的判定
1.被错误使用的函数:feof
EOF-end of file文件结束标志
很多人以为feof用来判定文件是否结束,实则不然,feof真正的作用是当文件读取结束时,判断读取结束的原因是否是遇到文件结尾结束,如果是,返回一个非0值,否则返回0。
文件读取结束可能的原因:
1.遇到文件末尾;
2.遇到错误。
ferror函数会检测标记,(如果钱买你读取错误,会设置一个状态值用来标记)如果检测到这个标记则返回一个非0的值。
判断什么原因结束:
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
char arr[20] = { 0 };
if (pf == NULL)
{
perror("fopen");
return 1;
}
fgets(arr,20,pf);
if (ferror(pf))
{
puts("I/0 error when reading");
}
else if (feof(pf))
{
puts("End of file reached successfully");
}
fclose(pf);
pf = NULL;
return 0;
}
注:1.读成功但是写失败时要把读的文件关闭。
2.若给数组初始化时赋值为1.,2.,3.,则表示为1.0,2.0,3.0;
3.日常写的代码被称为文本信息。
拷贝文件:
1.首先需要以读的形式打开test1.txt;
2.接着以写的形式打开test2.txt;
3.最后从test1里读一个字符再向test2里写一个。
#include <stdio.h>
int main()
{
char arr[20] = { 0 };
FILE* pf1 = fopen("test1.txt", "r");
FILE* pf2 = fopen("test2.txt", "w");
if ((pf1 == NULL)|| (pf2 == NULL))
{
perror("fopen");
return 1;
}
fgets(arr, 20, pf1);
fputs(arr, pf2);
fclose(pf1);
fclose(pf2);
pf1 = NULL;
pf2 = NULL;
return 0;
}
注:这需要准备好test1中的内容,否则还需要先向test1中些内容。
八、文件缓冲区
ANSIC标准采⽤“缓冲⽂件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为 程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓 冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输 ⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓 冲区的⼤⼩根据C编译系统决定的。
也就是说系统自动在内存中位程序中的每一个正在使用的文件开辟一个文件缓冲区。