【C语言学习】C语言文件操作
🐱作者:一只大喵咪1201
🐱专栏:《C语言学习》
🔥格言:你只管努力,剩下的交给时间!
文件操作
- 📂文件的介绍
- 📂文件的打开和关闭
- 📒文件指针
- 📒打开和关闭
- 📂文件的顺序读写
- 📂文件的随机读写
- 📂文本文件和二进制文件
- 📂文件读取结束的判定
- 📂文件缓冲区
- 📂总结
📂文件的介绍
本喵之前写的C语言代码,无论是数组,结构体等这些数据,都是存放在内存空间上的,它们只在程序执行的过程中存在,而一旦程序执行完以后,它们的内存空间就被系统回收了,而这些数据也就不在了,所以它们的生命周期也就是整个程序执行的时间,有些类型的数据,比如局部变量存在的时间更短,那么我们有没有办法让程序中的数据一直存在,不会随着程序的结束而消失呢?接下来本喵讲的文件操作就可以实现这样的要求。
文件的类型:
我们一般所说的文件分为俩类,程序文件和数据文件,无论哪种文件都存放在磁盘上,也就是我们所说的硬盘上。
程序文件:
包括:
- 源程序文件,后缀为.c
- 目标文件,在windows环境下后缀为.obj
- 可执行程序,在windows环境下后缀为.exe
数据文件:
数据文件中的内容是程序在执行过程中需要读取的数据,也可以是写入的数据
本喵以前的程序都是从键盘读取数据,输出打印在屏幕上,而使用文件操作时,数据是从文件中读取,输出也是写在文件中的。
这里本喵仅介绍数据文件的操作。
文件名:
每一个文件都有一个唯一的文件标识,只有这样我们才能在使用它的时候准确找到它,文件标识被我们常叫做文件名。
- 文件名包括:文件路径+文件名主干+文件后缀
- 例如:c:\code\test.c
- 写在代码中时是
c:\\code\\test.c这样的,因为要转义\,所以要写俩个\。
- 文件后缀决定了文件的打开方式。
📂文件的打开和关闭
📒文件指针
我们的程序中实际上是对内存的操作,那么在硬盘上的文件是怎么被程序操作的呢?
在内存中有一块区域叫做文件信息区。
这块文件信息区上存放的是一个结构体类型的数据
struct _iobuf
{ char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname;
};
typedef struct _iobuf FILE;
不同的编译器上,结构体中的成员类型可能不通,但都是大同小异,效果是一样的。
- 该结构体就是自定义的文件类型,通过关键字typedef重命名为FILE
- 每当打开一个文件的时候,系统会根据文件的情况在文件信息区自动创建一个这样的结构体变量,并且自动填充其中的成员变量,我们不用关心它填充的是什么
在文件信息区创建好这个结构体变量以后,将它的地址赋值给一个FLIE*类型的指针变量,这个指针就叫做文件指针
FILE* pf;
通过文件指针pf来维护这个结构体变量,进而达到操作存放在硬盘上的文件的目的。

大致的流程就像图中那样
- 通过文件指针pf来维护文件信息区中的结构体变量FILE,对一系列成员变量进行相应的操作
- 结构体中的成员变量的状态以及值发生变化后就会产生相应的操作,进而实现从文件中读写数据的目的。
这一切都是系统自动完成的,我们只需要知道对文件进行操作的时候需要一个文件指针变量和相应的文件操作函数。
不同的文件在打开的时候会创建不同的结构体变量

但是它们操各个文件的流程都是一样的。
📒打开和关闭
打开文件:
FILE * fopen ( const char * filename, const char * mode );
这是函数fopen的函数声明,需要引用头文件stdio.h,作用就是打开硬盘中的文件。
- 形参:
- const char* filename:用于接收文件名,文件名是以字符串的形式接收的,如果该文件和当前工程在同一个目录下则不需要写文件路径,只写文件名主干+后缀即可,如"test.txt",如果不在同一目录下就需要写清除文件路径。
- const char* mode:决定以什么样的方式打开文件,同样接收的是字符串,比如"r"表示已只读的方式打开。
- 返回类型:
- 打开成功时返回的是一个FILE类型的文件指针,也就是将文件信息区上的结构体变量的地址返回来了,我们同样需要一个FILE类型的文件指针来接收这个地址。
- 打开失败的时候会返回一个空指针NULL。
打开文件的类型有很多种,如下表
| 文件打开方式 | 含义 | 如果指定文件不存在 |
|---|---|---|
| “r”(只读) | 为了读取(输入)数据,打开一个已经存在的文本文件手机 | 出错 |
| “w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
| “a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
| “rb”(只读) | 为了输入(读取)数据,打开一个二进制文件 | 出错 |
| “wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
| “ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
| “r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
| “w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
| “a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
| “rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
| “wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
| “ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
#include int main()
{FILE* pf = fopen("test.txt", "w");//以写的方式打开文件//判断文件是否打开if (pf == NULL){perror("fopen");return 1;}//使用//。。。。//关闭文件fclose(pf);pf = NULL;//失忆return 0;
}
这是以写的方式打开一个名字为test.txt的文件。
注意:
- 以写的方式打开一个文件时
- 如果这个文件不存在,则会创建一个该名字的文件
- 如果这个文件已经存在,在打开的同时将会清空文件中的内容,当写入数据的时候是从头开始写的。
- 一定要对打开文件后返回的指针进行有效性判断,防止对NULL空指针进行使用
- 文件使用结束后一定要关闭文件
关闭文件:
int fclose ( FILE * stream );
这是函数fclose的函数声明,需要引用头文件stdio.h,作用是关闭已经打开的文件。
- 形参:是一个FILE*类型的文件指针变量,用于接收打开文件后返回的指针。
- 返回类型:是一个int类型
- 关闭成功的话会返回0
- 关闭失败的话返回EOF
文件的打开和关闭是成对存在的,有打开就必须有关闭
原因本喵在下面给大家讲解。
上面是一个以写的方式打开的例子,下面写一个以读的方式开的例子
int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}//使用fclose(pf);pf = NULL;return 0;
}
由于上面已经创建过文件test.txt文件,所以我们在这里可以顺利打开。
注意:
- 以读的方式打开一个文件时
- 如果这个文件存在,则直接打开
- 如果这个文件不存在,则报错,返回一个空指针NULL
- 同样再最后一定要关闭文件
📂文件的顺序读写
在上面我们已经知道了如何打开一个文件,那么文件打开以后如何对它进行读写操作呢?这样就要用到一系列的读写库函数
| 功能 | 函数名 | 适用于 |
|---|---|---|
| 字符输入函数 | fgetc | 所有输入流 |
| 字符输出函数 | fputc | 所有输出流 |
| 文本行输入函数 | fgets | 所有输入流 |
| 文本行输出函数 | fputs | 所有输出流 |
| 格式化输入函数 | fscanf | 所有输入流 |
| 格式化输出函数 | fprintf | 所有输出流 |
| 二进制输入 | fread | 文件 |
| 二进制输出 | fwrite | 文件 |
这些库函数就是用于对文件进行读写操作的,下面本喵来给大家一一演示
- 字符输入函数fgetc
int fgetc ( FILE * stream );
这是函数fgetc的声明,需要引用头文件stdio.h,用于读取文件中的一个字符。
- 形参:FILE类型的指针变量,用于接收打开文件后返回的指针
- 返回类型:
- 读取成功,返回int类型的整数,由于char类型的字符也是属于整型家族,所以返回的值就是读到的字符
- 读取失败返回EOF,并且会报错
- 读取文件内容结束,返回EOF,不会报错
我们先在工程目录下创建一个test.txt文件,里面放字符abcdef

然后读取文件中的字符
int main()
{char ch = 0;FILE* pf = fopen("test.txt", "r");//打开文件//判断指针有效性if (pf == NULL){perror("fopen");return 1;}//读取while ((ch = fgetc(pf)) != EOF){printf("%c\n", ch);}//关闭fclose(pf);pf = NULL;return 0;
}

成功将文件中的字符全部读取出来。
如果在文件目录下没有test.txt这个文件会怎么样呢?

我们将目录下的test.txt文件删掉以后,代码就会报错。
注意:
- 读取文件中的内容时,必须读的方式打开,如果以写的方式打开是无法读取的
- 字符输出函数fputc
int fputc ( int character, FILE * stream );
这是fputc的函数声明,需要引用头文件stdio.h,用来向文件中写入一个字节。
- 形参:
- int character:用于接收要写出的字符
- FILE* stream:用于接收打开文件后返回的指针
- 返回类型:int整型
- 输出成功,返回写入的字符
- 输出失败,返回EOF,并且报错
下面我们将字符’a’到字符’z’写入test.txt文件中
int main()
{char ch = 0;FILE* pf = fopen("test.txt", "w");//打开文件//指针有效性判断if (pf == NULL){perror("fopen");return 1;}//输出for (ch = 'a'; ch <= 'z'; ch++){fputc(ch, pf);}//关闭fclose(pf);pf == NULL;return 0;
}

在工程目录下,我们可以看到文件名为test.txt的文件,文件中的内容是字符从a到z。
这是原本没有这个文件,新创建的文件,如果有这个文件,但是我们要输出其他内容呢?
int main()
{char ch = 0;FILE* pf = fopen("test.txt", "w");//打开文件//指针有效性判断if (pf == NULL){perror("fopen");return 1;}//输出for (ch = 'A'; ch <= 'Z'; ch++){fputc(ch, pf);}//关闭fclose(pf);pf == NULL;return 0;
}

此时原本的小写就被大写覆盖了
- 以只写的方式打开的时候,原本文件中的内容就会被清除
- 文本输入函数fgets
char * fgets ( char * str, int num, FILE * stream );
这是fgets的函数声明,需要引用头文件stdio.h,用来读取文件中的字符串。
- 形参:
- char* str:用于接收读取出来的字符串存储空间的首地址
- int num:用于接收字符串中的字符个数,包括字节接收标志’\0’
- FILE* stream:用于接收打开文件后返回的指针
- 返回类型:char*类型的指针变量
- 读取成功,返回存放字符串的内存空间的首地址
- 读取提前遇到文件结束标志EOF,返回空指针NULL,并且告诉eof,并不报错,属于正常结束
- 读取过程中发生错误,返回空指针NULL,并且告诉ferror,并且报错
同样,我们在工程目录下创建文件test.txt,将字符串

同样是有很多字符
int main()
{char arr[20];FILE* pf = fopen("test.txt", "r");//指针有效性判断if (pf == NULL){perror("fopen");return 1;}//读字符串fgets(arr, 12, pf);//打印字符串printf("%s\n", arr);//printf("%s\n",fgets(arr,12,pf));//关闭fclose(pf);pf == NULL;return 0;
}

虽然文件中也是字符,但是读取是按照字符串的方式读取出去的。
文件中并没有写‘\0’,在将字符串放入储存空间的时候自动补了’\0’
- 文本输出函数fputs
int fputs ( const char * str, FILE * stream );
这是fputs的函数声明,需要引用头文件stdio.h,用来向文件中写入字符串。
- 形参:
- const char* str:用于接收要写的字符在内存空间中的首地址
- FILE* stream:用于接收打开文件后返回的指针
- 返回类型:int类型
- 输出成功,返回一个非负的整数
- 输出失败,返回EOF,并且报错
我们将字符串“I LOVE SHANGHAI"输出到test.txt文件中。
int main()
{char arr[] = "I LOVE SHANGHAI";FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return 1;}fputs(arr, pf);fclose(pf);pf = NULL;return 0;
}

可以看到,在文件中是我们输出的字符串,同样没有字符结束标志’\0’
- 格式化输入函数fscanf
int fscanf ( FILE * stream, const char * format, ... );
这是fscanf的函数声明,需要引用头文件stdio.h,用来从文件中按照一定的格式读取数据。
对比scanf的函数定义
int scanf ( const char * format, ... );
可以看到,区别在于
- fscanf中有俩个形参,第一个形参是用来接收文件打开后返回的指针,第二个形参和scanf的形参一模一样
所以fscanf的用法和scanf的用法是一样的,区别就在于fscanf需要多一个指针变量的参数。
我们在test.txt文件中放入三个不同类型的数据

int main()
{char ch = 0;int a = 0;float b = 0.0f;FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}fscanf(pf, "%c %d %f", &ch, &a, &b);//格式化读取数据printf("%c\n", ch);printf("%d\n", a);printf("%f\n", b);fclose(pf);pf == NULL;return 0;
}

按照我们的格式成功读取出对应的数字。
注意:
- 文件中数字的格式要按照程序中的读取的格式来放,如果格式不能对应,取出的数字就会出错

如果将不同类型之间的空格去掉

打印的结果就会变成这样,结果并不是我们想要的。
- 格式化输出函数fprintf
int fprintf ( FILE * stream, const char * format, ... );
这是fprintf的函数声明,需要引用头文件stdio.h,用来向文件中输出格式化数据。
对比printf的函数定义
int printf ( const char * format, ... );
同样fprintf只是多了一个pf文件指针,其他和printf的用法一样。
我们向文件test.txt中输出‘a’ 1999 96.00的格式化数据
int main()
{char ch = 'a';int a = 1999;float b = 96.00f;FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return 1;}fprintf(pf, "%c %d %.2f\n", ch, a, b);fclose(pf);pf == NULL;return 0;
}

符合我们的要求。
- 二进制输出函数fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
这是fwrite的函数声明,需要引用头文件stdio.h,用来向文件中以二进制的形式写入数据。
- 形参:
1.const void* ptr:用于接收存放输出数据的内存空间的地址
- size_t size:用于接收读取数据类型的大小
- size_t count:用于接收需要读取的数据个数
- FILE* stream:用于接收打开文件后返回的地址
- 返回类型:size_t类型,也就是unsigned int类型,表示输出的数据个数
- 输出成功,返回的值与形参中需要输出的个数相等
- 输出失败,返回0,并且报错
我们创建一个结构体类型的数据,以二进制的形式输出到test.txt文件中
struct S
{char c1;int i;char c2;
};int main()
{struct S s = { 'a',1998,'b' };struct S* ps = &s;FILE* pf = fopen("test.txt", "wb");if (pf == NULL){perror("fopen");return 1;}fwrite(ps, sizeof(struct S), 1, pf);fclose(pf);pf = NULL;return 0;
}

我们发现,文件中的内容压根看不懂,这其实就是二进制数以文本的形式表现出来的。
注意:
- 这里文件的打开方式是”wb“
那怎么验证这个内容是正确的呢?接下来就用二进制的方式将其读取我们再看结果
- 二进制输入函数fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
这是fread的函数声明,需要引用头文件stdio.h,用来从二进制形式的文件中读取数据。
- 形参:
- void* ptr:用于接收用来放读取出来数据的空间地址
- size_t size:用于接收读取数据的类型大小
- size_t count:用于接收需要读取的数据个数
- FILE* stream:用于接收打开文件后返回的地址
- 返回类型:size_t类型
- 读取成功,返回的值和形参中所需要读取的个数相等
- 读取失败,返回的值和形参中所需要读取的个数不相等
我们来读取上面程序创建的存放二进制形式的文件test.txt
struct S
{char c1;int i;char c2;
};int main()
{struct S s = { 0 };struct S* ps = &s;FILE* pf = fopen("test.txt", "rb");if (pf == NULL){perror("fopen");return 1;}fread(ps, sizeof(struct S), 1, pf);printf("%c\n", ps->c1);printf("%d\n", ps->i);printf("%c\n", ps->c2);fclose(pf);pf = NULL;return 0;
}

可以看到,结果和我们在上一个程序中写入的内容是一样的。
二进制形式的文件我们虽然读不懂,但是可以通过程序将其交给系统去读。
注意:
- 这里的打开方式使用的”rb“
以上便是操作文件所用的函数。
📂文件的随机读写
本喵在上面所讲的文件读写函数都是将数据按照顺序读写,那么可不可以只读文件中的某一个数据而不读其他的呢?接下来本喵介绍几个可以读取任意位置数据的函数。
- 定位文件指针偏移量的函数fseek
int fseek ( FILE * stream, long int offset, int origin );
这是函数fseek的的声明,需要引用头文件stdio.h,作用是定位要读取数据的位置。
- 形参:
- FILE* stream:用于接收打开文件后返回的指针
- long int offset:用于接收要读取的数据相对参考位置的偏移量
- int oring:用于接收参考位置
- 返回类型:int类型
- 操作成功,返回0
- 操作失败,返回一个非负的数,并且报错
- 以二进制文件的操作模式打开时,该函数的参考位置有三种:
- SEEK_SET:表示文件的起始位置
- SEEK_CUR:表示文件的当前位置
- SEEK_END:表示文件的结束位置
我们在使用该函数的时候,形参在这3个中选择一个就好。
- 以文本文件的操作模式打开时,该函数的参考位置有俩种:
- 数字0
- 函数ftell返回的偏移量
int main()
{FILE* pf = fopen("test.txt", "wb");if (pf == NULL){perror("open");return 1;}fputs("0123456789", pf);fseek(pf, 5, SEEK_SET);fputs("abc", pf);fclose(pf);pf = NULL;return 0;
}


原本我们在文件中放的是0到9的字符串
- 我们将偏移量设成5,起始位置是文件开始,此时对文件的数据进行操作时就是从偏移量为5的数据,也就是字符5开始操作的
- 将abc写入文件中后,直接覆盖掉了原本文件中的字符567
- 返回文件指针相对于起始位置偏移量的函数ftell
long int ftell ( FILE * stream );
这是ftell的函数声明,需要引用头文件stdio.h,作用是返回此时操作的数据相对于文件起始位置的偏移量。
- 形参:用于接收打开文件后返回的地址
返回类型:操作的数据相对于文件起始位置的偏移量
int main()
{FILE* pf = fopen("test.txt", "rb");if (pf == NULL){perror("fopen");return 1;}fseek(pf, 3, SEEK_SET);//偏移量设置为3int ret = ftell(pf);//查看偏移量fclose(pf);pf = NULL;printf("%d\n", ret);return 0;
}

返回的偏移量就是我们前面设置的偏移量。
- 使文件指针回归的起始位置的函数rewind
void rewind ( FILE * stream );
这是rewind的函数声明,需要引用头文件stdio.h,作用是使文件指针回归的起始位置。
参数类型本喵就不再讲了。
int main()
{FILE* pf = fopen("test.txt", "rb");if (pf == NULL){perror("fopen");return 1;}fseek(pf, 3, SEEK_SET);//偏移量设置为3int ret = ftell(pf);//查看偏移量printf("回归前:%d\n", ret);rewind(pf);ret = ftell(pf);//查看偏移量printf("回归后:%d\n", ret);fclose(pf);pf = NULL;return 0;
}

我们原本设置的3的偏移量变成了0,说明此时文件指针已经回归到文件的起始位置。
以上便是和文件指针访问位置有关的函数,通过配合这几个函数,我们可以随心所欲访问文本中的某个或者某几个数据。
📂文本文件和二进制文件
数据文件又分为文本文件和二进制文件
- 数据文件:数据在内存中以二进制的形式存储,如果不加任何转化输出的外存中的,这样的文件被叫做数据文件
- 文本文件:将内存中的二进制形式的数据通过转化,以ASCII码值的形式输出到外存中,这样的文件被叫做文本文件
通俗来说,文本文件是我们可以看懂的,二进制文件是我们看不懂的。
二进制文件可以使文件所占的磁盘空间更小
来看代码
#include int main()
{int a = 10000;FILE* pf = fopen("test.txt", "w");//以写的形式打开文件if (pf == NULL){perror("fopen");return 1;}fprintf(pf, "%d\n", a);fclose(pf);pf = NULL;return 0;
}

这是以文本文件的形式将数字10000输出到硬盘中的样子,大小是五个字节。
int main()
{int a = 10000;FILE* pf = fopen("test.txt", "wb");//以写的形式打开文件if (pf == NULL){perror("fopen");return 1;}fwrite(&a,sizeof(a),1,pf);fclose(pf);pf = NULL;return 0;
}

这是以二进制形式将数据输出到硬盘中,是一个二进制文件,大小是4个字节。
注意:前面的一堆0是文件的一种格式,它不代表数据
我们可以直观的看到,数字10000放在文本文件是5个字节,放在二进制文件是4个字节大小,明显小了一个字节。

上图中绿色框是我们以二进制是形式打开的文本文件,蓝色框是以二进制形式打开的二进制文件。
- 数字10000以文本文件的形式储存时,就是将字符’1’和4个字符‘0’存放在硬盘中,每个字符都有一个对应的ASCII码值,以二进制的形式打开以后看到的就是这5个字符的ASCII值,一共5个字节大小
- 数字10000以二进制文件的形式储存时,是将内存中的二进制不加转换的存放在硬盘中,数字10000是int类型,大小是4个字节,所以以二进制形式打开以后看到的就是10000的二进制形式按照小端存储方式在内存中的样子,大小是4个字节
通过以上分析,我们发现,二进制文件真的可以节省空间。
📂文件读取结束的判定
本喵在介绍顺序读取函数的时候都挨个分析了每个函数的返回值,下面本喵来总结一下
判断文件读取结束还需要使用到俩个函数:
- feof
int feof ( FILE * stream );
这是函数feof的声明,需要引用头文件stdio.h,作用是判断读取结束是否是正常结束。
- 形参:文件指针类型
- 返回类型:int类型
- 正常结束返回一个非负数
- 发生错误结束返回0
- ferror
int ferror ( FILE * stream );
这是ferror的函数声明,需要引用头文件stdio.h,作用是判断文件读取结束是否是发生错误结束的。
- 形参:文件指针
- 返回类型:int类型
- 如果是发生错误结束,返回一个非负数
- 如果是正常结束,返回0
我们只读,数据文件又分为文本文件和二进制文件,它们的文件结束判定也是不同的。
- 文本文件读取结束,判断返回值是否是EOF或者NULL
如:
fgetc返回EOF
fgets返回NULL- 二进制文件读取结束,判断返回值是否小于要读取的个数
如:
判断fread的返回值是否小于要读取的个数
文本文件例子:
int main()
{char c = 0;FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}while ((c = fgetc(pf)) != EOF){printf("%c ", c);}printf("\n");//判断读取结束的原因if (feof)printf("正常读取结束\n");else if (ferror)printf("遇到错误读取结束\n");fclose(pf);pf = NULL;return 0;
}

此时是遇到EOF结束的读取,所以是正常读取结束。
读取二进制文件的例子:
int main()
{//创建二进制文件double arr[10] = { 0.0,1.0,2.,3.0,4.0,5.0,6.0,7.0,8.0,9.0 };double arr1[10] = { 0 };FILE* pf = fopen("test.txt", "wb");if (pf == NULL){perror("fopen");return 1;}fwrite(arr, sizeof(double), 10, pf);fclose(pf);pf = NULL;//读取二进制文件pf = fopen("test.txt", "rb");if (pf == NULL){perror("fopen");return 1;}int sum = fread(arr1, sizeof(double), 10, pf);if (sum < 10)printf("读取发生错误\n");else if (sum == 10)printf("正常读取结束\n");fclose(pf);pf = NULL;return 0;
}

因为此时读取到的个数与需要读取的个数相等,所以是正常读取结束的。
📂文件缓冲区
流
在上面介绍顺序文件读写函数的表格中,有没有发现fread函数和fwrite函数只适用于二进制文件,而其他的函数是使用于所有流的,那么这里的流是指什么呢?
- 流是一种设计理念,因为我们在写程序的时候并不需要关心数据输入或者输出的底层逻辑是什么样的,我们只是将这些数据输出到流中,或者从流中输入,至于流中的数据是怎么实现和键盘,屏幕等设备交互的我们不需要管
- 但是流也有类型,比如我们对文件的输出输入就属于文件流,还有键盘流等等,它们就像是一条条条小的河流,它们统称为流。
看个例子
int main()
{int a = 10;fprintf(stdout, "%d\n", a);printf("%d\n", a);return 0;
}

使用fprintf也可以达到和printf一样的效果
- printf是标准输出函数,而我们的显示器就是标准出设备,那么此时输出的数据就是属于标准输出流
- fprintf中,将第一个形参改为stdout时,意味着此时输出的数据也是属于标准输出流,如果是指针变量pf的时候,那么此时的数据是文件输出流
- 同样的,可以将第一个形参改成不同流的指针。所以我们说fprintf适用于所有流。其他的输入输出函数也是同样的道理。
- 而fwrite和fread只能用于二进制文件,它不适合所有流。
文件缓冲区
我们已经知道了可以对文件进行输出和输入,那么这个过程大致是什么样的呢?是从内存中输出一个数就直接放到硬盘上的文件中了码?

其实程序中的数据和硬盘的中的数据交互要经过一个缓冲区,无论是键盘还是显示器,还是文件。
- ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
- 从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
- 如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
通过一个例子来给大家证明这个缓冲区的存在:
#include
#include
int main()
{FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return 1;}fputs("abcdef", pf);//先将代码放在输出缓冲区printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n"); Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n"); Sleep(10000);fclose(pf); //注:fclose在关闭文件的时候,也会刷新缓冲区pf = NULL; return 0;
}

- 在将字符串输出到文件中后的10秒内,test.txt文件中是没有内容的,说明此时数据还在输入缓冲区,缓冲区没有满,所有没有传送到硬盘。

- 而在经过fflush(pf)刷新缓冲区以后的10秒内,文件中有了内容,就是我们在前面使用fputs输出的字符串,说明此时缓冲区中的内容内输出到了文件中,也就是传到了硬盘上。
fclose执行完后也会刷新缓冲区
通过上面的例子相信大家感受到了文件缓冲区的存在,同样的,键盘输入,以及屏幕输出等都是存在缓冲区的,都是需要等缓冲区满了以后才会发送一次数据。
这么设计的目的是为了提高系统的效率,如果数据都是一个一个传送的,那系统就不用干别的了,正是因为有了缓冲区,在充满缓冲区的这个过程内,系统是可以去进行别的任务的,所有很大程度上提高了系统的效率。
📂总结
文件操作中并没有很难理解的知识点,只是函数比较多,我们只要把这些函数能够熟练的使用,那么我们对于文件的操作就会更加得心应手。希望这篇文章对各位有所帮助。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

