1.为什么需要使用文件
如果我们想写一个可以记录数据的程序的话,我们就需要文件来存放数据,文件可以使得数据存放在电脑的硬盘上,当你每次需要这些数据时候,就可以从文件中读取
2.什么是文件
文件可以分为两类,数据文件和程序文件
2.1程序文件
包括源文件,目标文件,和可执行文件,他们的后缀分别为.c,.obj,.exe
2.2数据文件
文件内容为数据的文件,以往我们通过键盘进行数据的输入,现在通过数据文件我们可以通过读取文件的数据进行数据的输入
2.3文件名
文件名的组成:路径+文件名主干+文件名后缀
例如 c:\code\test.txt
3.文件的打开和关闭
3.1 文件指针
连接我们的程序与文件之间的桥梁就是文件指针,通过文件指针我们可以在我们的程序中打开一个文件或者关闭一个文件。
文件指针的原理:每一个文件在内存中都开辟了一块文件信息区,这块空间保存在一个由系统声明的结构体变量FILE中,这里面存放了关于文件的相关信息,因此我们通过创建FILE*类型的指针来找到这块文件信息区,以此来进行有关文件的操作,如下
3.2 文件的打开与关闭
首先要记住一个原则,文件在使用前应该先打开文件,在使用完后应该要关闭文件
我们用fopen来打开文件,用fclose来关闭文件
FILE * fopen(const char * filename, const char* mode);
int fclose(FILE * stream);
fopen不仅需要输入文件名,还需要输入打开文件的方式,如下
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
实例
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("myFile.txt", "w");
if (pf == NULL)
{
printf("%s", strerror(errno));
return 1;
}
fputs("fopen example", pf);
fclose(pf);
return 0;
}
4. 文件的读写顺序
功能 | 函数名 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
这里解释下什么是流,流是个抽象的概念,是对输入输出设备的抽象,输入流可以看作一个输入通道,输出流可以看作一个输出通道。比如键盘是输入流,屏幕是输出流,文件既可以是输出流也可以是输入流。
接下来给出各个函数的声明
int fgetc ( FILE * stream );//一次从文件中读取一个字符
int fputc ( int character, FILE * stream );//给出一个字符,输出到对应文件中
char * fgets ( char * str, int num, FILE * stream );//从文件中读取num个字符到str指向的数组中
int fputs ( const char * str, FILE * stream );//给出一个字符串,输出到对应文件中
int fscanf ( FILE * stream, const char * format, ... );//相当于在scanf前加上了要读取的文件
int fprintf ( FILE * stream, const char * format, ... );//相当于在printf前加上了要输出的文件
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//从文件中每次读取size大小的数据,一共读取count个,到ptr指向的空间中
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
//从prt所指向的空间中每次读取size大小的数据,一共读取count个,到文件中
5. 文件的随机读写
5.1 fseek
int fseek ( FILE * stream, long int offset, int origin );
文件指针是有位移和偏移量的拿fgetc举例,比如文件中存放了abcd,那么第一次fgetc读取了a,文件指针就会向右偏移一位指向b,因此下次使用fgetc就会读取到b,文件指针最们就可以使用
开始指向a的偏移量为0,以此类推
因此如果我们不想顺序读取,而是想读取特定的值话,我们就可以使用fseek,第一个参数确定目标文件,第二个参数确定调整的偏移量,注意偏移量可以有负数,向左移动一位,就可以输入-1,第三个参数确定指针当前的位置,可以使用SEEK_SET找到指针当前的位置
示例:
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET);
fputs(" sam", pFile);
fclose(pFile);
return 0;
}
5.2 ftell
long int ftell ( FILE * stream );
返回文件指针当前相对于起始位置的偏移量
5.3 rewind
void rewind ( FILE * stream );
让文件指针返回起始位置
6. 文本文件和二进制文件
数据文件按照储存方式的不同可以分为文本文件与二进制文件
数据在内存中以二进制的形式存储,如果直接输出到文件中,就是二进制文件
如果转换成ASCLL码的形式存储,就是文本文件
7.文件读取结束的判定
feof的作用是:在文件读取结束后,判断是由于读取到文件末尾而导致文件读取结束,还是读取失败而结束,不能用feof来判断文件是否读取结束
1.判断返回值是否为EOF(fgetc),或者 NULL(fgets)
例如:
fgetc,判断是否为EOF
fgets,判断是否为NULL
2.二进制文件读取结束判断,判断返回值是否小于要读取的个数
例如:
fread,判断返回值是否小于要读取的个数
8.文件缓冲区
内存向硬盘输出数据时候,会开辟一块缓冲区,当缓冲区被装满后,才会将数据送往硬盘,在我们进行文件的操作时候,内存也会开辟一块文件缓冲区,当这块缓冲区被写满或者我们执行flose操作关闭文件时候,缓冲区上的数据才会输入的硬盘上
硬盘与程序数据区的关系如下图所示
因为有缓冲区的存在,所以我们要及时的关闭文件,要不然如果写的文件不够大,缓冲区填充不满,那么我们的数据也没办法输入到硬盘上了
最后说一说缓冲区作用,比如现在班级上在做作业,你是一位老师负责解答同学们的问题,如果有一位同学一想到一个问题就跑来问你,那么你就会一直困在这个同学这里出不去,现在你跟他说你攒够十个问题再来问我,那么你就有时间去解答其他同学的问题了,因此我们可以得到,缓冲区可以提高整个程序的效率