- 为什么使用文件
- 什么是文件
- 1.程序文件
- 2.数据文件
- 3.文件名
- 文件的打开和关闭
- 1.文件指针
- 2.文件的打开和关闭
- 文件的顺序读写
- fputc
- fgetc
- fputs
- fgets
- fprintf
- fscanf
- fwrite
- fread
- 对比一组函数
- sprintf
- sscanf
- 文件的随机读写
- fseek
- ftell
- rewind
- 文本文件和二进制文件
- 文件读取结束的判定
- feof
- 文本文件读取是否结束,判断返回值是否为EOF(fgetc),后者NULL(fgets) 例如:
- 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数 例如:
- 当我们运行程序时,我们想把我们运行程序时把写入的数据永久保存起来,而不是等程序结束后就被销毁
- 只有我们自己将数据删除时,数据才将不复存在
- 这就涉及到了数据持久化的问题,我们一般把数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。
- 使用文件我们可以将数据直接存放在电脑的磁盘上,做到了数据的持久化。
| 磁盘上的文件是文件。 |
| 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能角度来分类) |
2.数据文件包括源程序文件(后缀为.c),目标文件(Windows环境后缀为.obj),可执行程序(Windows环境后缀为.exe)。
3.文件名文件的内容不一定是程序,而是运行是读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含三部分:文件路径+文件主干名+文件后缀。
- 例如
c:codetest.txt
为了方便起见,文件标识常被称为文件名
文件的打开和关闭 1.文件指针缓冲文件系统中,关键的概念是文件类型指针,简称文件指针。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件名字,文件的状态及文件当前的位置等),这些信息是保存在一个结构体变量中。该结构体类型是有系统声明的,取名FILE。
文件在读写之前应该先打开文件, 在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC规定使用fopen函数来打开文件,fclose来关闭文件。
//打开文件 FILE* fopen(const char* filename, const char* mode); //关闭文件 int fclose(FILE* stream);
- 打开方式如下:
| 文件使用方式 | 含义 | 如果指定文件不存在 |
|---|---|---|
| "r"(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
| "w"(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
| "a"(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
| “rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
| “wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
| “ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
| “r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
| “w+”(读写) | 为了读和写,建立一个新的文件 | 建立一个新的文件 |
| “a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
| “rb+”(读写) | 为了读和写,打开一个二进制文件 | 出错 |
| “wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
| “ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立 一个新的文件 |
我们以’r’(只读)为例,当不存在输入的文本文件时,就会报错
#includeint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //关闭文件 fclose(pf); pf = NULL; return 0; }
此时并没有data.txt文件,就会报错
如果存在data.txt文件
将不会报错
| 功能 | 函数名 | 适用于 |
|---|---|---|
| 字符输入函数 | fgetc | 所有输入流 |
| 字符输出函数 | fputc | 所有输出流 |
| 文本行输入函数 | fgets | 所有输入流 |
| 文本行输出函数 | fputc | 所有输出流 |
| 格式化输入函数 | fscanf | 所有输入流 |
| 格式化输出函数 | fprintf | 所有输出流 |
| 二进制输入 | fread | 文件 |
| 二进制输出 | fwrite | 文件 |
注:
fputc向文件输入数据
#includeint main() { //打开文件 FILE* pf = fopen("data.txt", "w");//'w'是写文件 if (pf == NULL) { perror("fopen"); return -1; } //写文件 fputc('a', pf); fputc('b', pf); fputc('c', pf); fputc('d', pf); //关闭文件 fclose(pf); return 0; }
此时文件中会出现在我们输入的字符
我们也可以将字符输出在屏幕上
#includeint main() { fputc('a', stdout); fputc('b', stdout); fputc('c', stdout); fputc('d', stdout); return 0; }
stdout是标准输出流
向内存中输入数据
经过上面的操作之后,data.txt文件中,已经有abcd四个字符了,我们将他们保存在内存中,并打印到屏幕上
#includefputsint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //读文件 int ch = fgetc(pf); printf("%c", ch); ch = fgetc(pf); printf("%c", ch); ch = fgetc(pf); printf("%c", ch); //关闭文件 fclose(pf); pf = NULL; return 0; }
向文本中输入一行数据
#includeint main() { //打开文件 FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { perror("fopen"); return -1; } //写文件 fputs("hello worldn", pf);//如果不加上n,当下一次再次输入时,是在尾部继续加,而不是换行加 fputs("hello chinan", pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
此时文件中的内容是
向内存中输入一行数据
在指定的流中读取一行的数据,如果遇到换行则结束读取,或者当读取到最大字符数时,同样结束读取
#includefprintfint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //写文件 char arr[10] = { 0 }; fgets(arr, 10, pf);//arr是读取数据后存放的指针,10是最大读取数 printf("%s", arr); //关闭文件 fclose(pf); pf = NULL; return 0; }
将格式化的数据打印到流中。
#includestruct S { int n; double d; }; int main() { struct S s = { 100, 3.14 }; //打开文件 FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { perror("fopen"); return -1; } //写文件 fprintf(pf, "%d %lf", s.n, s.d); //关闭文件 fclose(pf); pf = NULL; return 0; }
运行一次后的data.txt文件
将格式化的数据打印到流中。
#includefwritestruct S { int n; double d; }; int main() { struct S s = { 0 }; //打开文件 FILE* pf = fopen("data.txt", "r");//此时的data.txt文件是上一次运行时的,里面的内容是100和3.140000 if (pf == NULL) { perror("fopen"); return -1; } //读文件 fscanf(pf, "%d %lf", &(s.n), &(s.d)); printf("%d %lf", s.n, s.d); //关闭文件 fclose(pf); pf = NULL; return 0; }
将数据写入流。
#includestruct S { int n; double d; char name[10]; }; int main() { struct S s = { 100, 3.14, "zhangsan"}; //打开文件 FILE* pf = fopen("data.txt", "wb"); if (pf == NULL) { perror("fopen"); return -1; } //写文件 -- 二进制的方式写 fwrite(&s, sizeof(s), 1, pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
此时的文件内容是
是一些我们看不懂的二进制内容
从流中读取数据
#include对比一组函数struct S { int n; double d; char name[10]; }; int main() { struct S s = { 0}; //打开文件 FILE* pf = fopen("data.txt", "rb"); if (pf == NULL) { perror("fopen"); return -1; } //写文件 -- 二进制的方式写 fread(&s, sizeof(s), 1, pf); printf("%d %lf %s", s.n, s.d, s.name); //关闭文件 fclose(pf); pf = NULL; return 0; }
- scanf:从标准输入(键盘)读取格式化的数据
- fscanf:从所有的输入流读取格式化的数据(相当于包含了scanf的功能)
- sscanf:从字符串中,读格式化的数据
sprintf
- printf:把格式化的数据输出到标准输出流(屏幕)上
- fprintf:把格式化的数据输出到所有输出流(屏幕/文件)上(相当于包含了printf的功能)
- sprintf:写一个格式化的数据到字符串里
写一个格式化的数据到字符串里
#includesscanfstruct S { int n; double d; char name[10]; }; int main() { char arr[100] = { 0 }; struct S s = {100, 3.14, "zhangsan"}; //把一个格式化的数据转换成字符串 sprintf(arr, "%d %lf %s", s.n, s.d, s.name); //打印 printf("%s", arr); }
从字符串中,读格式化的数据
#include文件的随机读写 fseekstruct S { int n; double d; char name[10]; }; int main() { char arr[100] = { 0 }; struct S s = { 100, 3.14, "zhangsan" }; struct S tmp = { 0 }; //把一个格式化的数据转换成字符串 sprintf(arr, "%d %lf %s", s.n, s.d, s.name); //printf("%sn", arr); sscanf(arr, "%d %lf %s", &(tmp.n), &(tmp.d), tmp.name); //打印 printf("%d %lf %s", tmp.n, tmp.d, tmp.name); return 0; }
根据文件指针的位置和偏移量来定位文件指针
int fseek(FILE* stream, long int offset, int origin); //offset表示偏移量,origin表示起始位置 //如果你想要的定位的位置在起始位置的左边,那么offset为负数
fseek指定了三个起始位置的值:
- SEEK_CUR:文件指针当前指向的位置
- SEEK_END:文件的末尾
- SEEK_SET:文件的开头
- 例子
我们在代码路径下创建一个文本文件
内容是
当我们刚刚打开文件准备操作时,文件指针指向的是文件的起始位置
#includeint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); } //读文件 int ch = fgetc(pf); printf("%c", ch); //关闭文件 fclose(pf); pf = NULL; return 0; }
如果我们想一开始就指向其它的位置(例如c),那么如何操作呢?
#includeint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); } //读文件 //随机读取 fseek(pf, 2, SEEK_SET);//从起始位置开始,字符c相对于起始位置的偏移量为2 int ch = fgetc(pf); printf("%cn", ch); //关闭文件 fclose(pf); pf = NULL; return 0; }
当我们读取完c时,此时指针已经指向了d字符。
如果我现在读完文件中的c字符,又想读取b字符怎么办呢?
#includeftellint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); } //读文件 //随机读取 fseek(pf, 2, SEEK_SET);//从起始位置开始,字符c相对于起始位置的偏移量为2 int ch = fgetc(pf); printf("%cn", ch); fseek(pf, -2, SEEK_CUR);//-2表示向前偏移两个 ch = fgetc(pf); printf("%cn", ch); //关闭文件 fclose(pf); pf = NULL; return 0; }
计算文件指针相对于起始位置的偏移量
long int ftell(FILE* stream);
- 当你进行了一系列操作之后,想知道此时文件指针在哪个位置,可以利用ftell函数来计算此时文件指针相对于起始位置的偏移量
#includeint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); } //读文件 //随机读取 fseek(pf, 2, SEEK_SET);//从起始位置开始,字符c相对于起始位置的偏移量为2 int ch = fgetc(pf);//c printf("%cn", ch); fseek(pf, -2, SEEK_CUR);//-2表示向前偏移两个 ch = fgetc(pf);//b printf("%cn", ch); //计算指针相对于起始位置的偏移量 int ret = ftell(pf); printf("%dn", ret); //关闭文件 fclose(pf); pf = NULL; return 0; }
在使用ftell函数之前,文件指针此时已经指向了C字符
那么,c字符相对于起始位置的偏移量应该是2,我们再看看代码结果
让文件指针回到起始位置
void rewind(FILE* stream);
rewind函数可以让文件指针回到文件的起始位置,当我们想从起始位置重新开始却又不想重新定义变量,就可以使用rewind函数
#include文本文件和二进制文件int main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); exit(-1); } //读文件 //随机读取 fseek(pf, 2, SEEK_SET);//从起始位置开始,字符c相对于起始位置的偏移量为2 int ch = fgetc(pf);//c printf("%cn", ch); fseek(pf, -2, SEEK_CUR);//-2表示向前偏移两个 ch = fgetc(pf);//b printf("%cn", ch); //计算指针相对于起始位置的偏移量 int ret = ftell(pf); printf("%dn", ret); //让文件指针回到起始位置 rewind(pf); ch = getc(pf); printf("%cn", ch); //关闭文件 fclose(pf); pf = NULL; return 0; }
文件读取结束的判定 feof根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码形式存储,则需要在存储前转换。以ASCII字符形式存储的文件就是文本文件。
- 在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束
- 而是应用于当文件读取结束的时候,判断是读取失败结束还是遇到文件结尾
- 文本文件读取是否结束,判断返回值是否为EOF(fgetc),后者NULL(fgets)
例如:
- fgetc判断是否为EOF.
- fgets判返回值是否为NULL;
- 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
例如:
- fread判断返回值是否小于要读的个数。
正确的使用:
#includeint main() { //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } int ch = 0; while ((ch = getc(pf)) != EOF)//文件结束的标志 { printf("%c ", ch); } //判断是什么原因结束的 if (ferror(pf))//文件遇到错误 { printf("文件遇到错误结束n"); } else if (feof(pf))//遇到文件末尾 { printf("遇到文件末尾结束n"); } fclose(pf); pf = NULL; return 0; }
feof的用途:文件读取结束了,判断是不是遇到文件末尾而结束的。
ferror的用途:文件读取结束了,判断是不是遇到错误结束了。



