目录
摘要:
一:文件操作的流程
1:fopen
2:fclose
3:文件的路径
4:顺序和随机的区别
5:默认流
二:顺序读写函数
1:fputc
2:fgetc
3:fputs
4:fgets
5:fprintf
6:fscanf
7:fwite
8:fread
摘要:
本博客介绍并使用8种顺序读写函数~
一:文件操作的流程
读写的含义:
读(读出):即从文件里面读出数据----------->和scanf从键盘里面读出数据类似
写(写入):即把数据写入文件里面----------->和printf把数据写入到屏幕上类似
想对文件进行读写操作,则流程一定为:打开文件--->读/写文件--->关闭文件,而我们对文件可能是只读,可能是只写,可能是追加,可能是既读又写,所以在使用fopen打开文件的时候,函数规定需要在fopen的对应参数选择对应的打开模式~
1:fopen
FILE * fopen ( const char * filename, const char * mode )
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
filename | const char * | 要打开的文件名(可包含路径) |
mode | const char * | 打开模式 |
函数作用:
建立调用fopen的程序与filename文件之间的连接
返回一个
FILE *类型的指针,供后续读写操作使用
返回值:
成功:返回
FILE *失败:返回
NULL
注意事项:
返回值为FILE*,是结构体FILE的指针,通过该指针可以进行文件的相关操作
必须检查返回值!后序读写前,务必先判断fopen的返回值是否为
NULL
常用的打开模式如下:
| 文件使用方式 | 含义 | 如果指定文件不存在 |
|---|---|---|
| “r” (只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
| “w” (只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
| “a” (追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
| “rb” (只读) | 为了输入数据,打开一个二进制文件 | 出错 |
| “wb” (只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
| “ab” (追加) | 向一个二进制文件尾添加数据 | 建立一个新的文件 |
| “r+” (读写) | 为了读和写,打开一个文本文件 | 出错 |
| “w+” (读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
| “a+” (读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
| “rb+” (读写) | 为了读和写打开一个二进制文件 | 出错 |
| “wb+” (读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
| “ab+” (读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
2:fclose
int fclose ( FILE * stream );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
stream | FILE * | 参数就是fopen的返回值 |
函数作用:
关闭文件,并且断开程序与文件之间的连接(顺便会刷新缓冲区)
返回值:
| 返回值 | 含义 |
|---|---|
0 | 关闭成功 |
EOF(通常是 -1) | 关闭失败(如缓冲区刷新失败、无效流指针等) |
注意事项:
fclose关闭文件后,安全起见,必须将 FILE * 类型的fp设为 NULL,但由于fclose一般都是代码的末尾执行,后面也不会在使用fp,所以往往会省去这一步,但加上才是更优秀的做法!
所以文件操作的整体框架就为:
#include <stdio.h> int main() { //打开⽂件 FILE* pFile = fopen("myfile.txt", "w"); //根据需求 进行⽂件读/写操作 //关闭文件 fclose(pFile); return 0; }3:文件的路径
在调用fopen时,可以直接在第一个参数中传入文件名(如"data.txt"),此时系统会自动在当前工作目录下查找或创建该文件。当然,也可以传入完整的绝对路径"C:\\project\\data.txt"来指定任意目录位置下的文件。
某些打开模式,是要求在指定路径下必须存在目标文件的,所以当使用这些打开模式之前,需要事先手动新建目标文件,或者使用能够自动新建的写函数去创建文件;而有些打开模式发现目录不存在会自动创建文件~
4:顺序和随机的区别
顺序读写函数和随机读写函数,都是因为存在文件位置指示器这个东西,每次打开一个文件,其对应的FILE结构体中就会维护着一个文件位置指示器(也叫文件指针、文件偏移量),它记录着下一次读写操作将从文件的哪个字节位置开始!
每次使用顺序读写函数后,文件位置指示器会自动向后移动相应字节数,因此读写操作会自然地形成顺序去进行读写!
而随机读写函数,其可以修改文件位置指示器的值,可以跳跃到文件的任意位置进行读写,而不必按照顺序一个个处理,所以叫做随机读写函数!
5:默认流
❓️:那为什么scanf,printf的时候,直接使用就行了,不需要打开什么??
💡:因为c语言程序,只要运行起来,就会默认打开3个流
①:标准输入流 stdin
②:标准输出流 stdout
③:标准错误流 stderr
三个流的类型依然是 FILE*,和读写函数的参数一致,也可以理解为这一步电脑帮你省略了。
二:顺序读写函数
顺序读写函数有8种,如下:
| 函数名 | 功能 | 适用于 |
|---|---|---|
| fgetc | 字符输入函数 | 所有输入流 |
| fputc | 字符输出函数 | 所有输出流 |
| fgets | 文本行输入函数 | 所有输入流 |
| fputs | 文本行输出函数 | 所有输出流 |
| fscanf | 格式化输入函数 | 所有输入流 |
| fprintf | 格式化输出函数 | 所有输出流 |
| fread | 二进制输入 | 文件 |
| fwrite | 二进制输出 | 文件 |
解释:上⾯说的适用于所有输入流⼀般指适用于标准输⼊流和其他输⼊流(如文件输⼊流);所有输出流⼀般指适用于标准输出流和其他输出流(如文件输出流)
⚠️:此篇博客都是对当前路径下的test.txt文件进行操作,所以前后会有因果关系
1:fputc
int fputc ( int character, FILE * stream );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
character | int | 要写入的字符的ascll码值 |
stream | FILE * | 指向要写入的文件流的指针 |
注:character也可以直接传递'a'这种字符,因为字符的本质就是ascll码值(int类型)
函数作用:
将字符写入到文件中,成功写入后,文件位置指示器自动向后移动一个字节
返回值:
| 返回值 | 含义 |
|---|---|
| int (写入的字符的ascll码值) | 写入成功 |
EOF(-1) | 写入失败(读取时到达文件尾或发生错误) |
注意事项:
fputc作为写函数,自然需要以写相关的打开模式去打开文件
使用示例:
a:写入单个字符
#include <stdio.h> int main() { FILE* fp = fopen("test.txt", "w"); if (fp == NULL) { perror("打开文件失败"); return 1; } fputc('A', fp); fclose(fp); return 0; }解释:
test.txt文件不存在,所以fopen以写的形式打开文件的时候,会先在指定路径下新建该文件,这里的路径只有文件名字,所以默认路径就是当前程序对应的目录
文件如下:
b:循环写入字符
解释:因为是w模式打开文件,而不是追加模式,所以下一次的写之前,会清空上一次所写的内容
2:fgetc
int fgetc ( FILE * stream );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
stream | FILE * | 指向要读取的文件的FILE结构体 |
函数作用:
从指定的文件流(stream)中读取一个字符
返回值:
返回该字符的 ASCII 值。如果读取时到达文件尾或发生错误,则返回
EOFEnd Of File,一个特殊的常量,通常在大多数系统中定义为 -1
注意事项:
会读取包括空格、换行在内的所有字符
使用示例:
①:读取单个字符
#include <stdio.h> int main() { FILE* fp = fopen("test.txt", "r"); if (fp == NULL) { perror("打开文件失败"); return 1; } int ch = fgetc(fp); if (ch != EOF) { printf("读取到的字符:%c\n", ch); } fclose(fp); return 0; }②:循环读取字符
#include <stdio.h> int main() { FILE* fp = fopen("test.txt", "r"); if (fp == NULL) { perror("打开文件失败"); return 1; } int ch; while ((ch = fgetc(fp)) != EOF) { putchar(ch); } fclose(fp); return 0; }解释:fgetc无法读取时返回值为EOF,所以当我们想循环读取文件的时候,就可以使用返回值不为EOF进行判断,从而循环地去读完整个文件
3:fputs
int fputs ( const char * str, FILE * stream );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
str | const char * | 要写入的字符串(以\0结尾) |
stream | FILE * | 指向要写入的文件流的指针 |
函数作用:
将字符串写入到指定的文件流中(将字符串str写入到文件流stream中),且文件位置指示器会向后移动写入的字节数
返回值:
| 返回值 | 含义 |
|---|---|
>= 0 | 写入成功(返回最后一个写入的非负字符数) |
EOF(-1) | 写入失败 |
注意事项:
fputs不会自动添加换行,需要换行要手动加\n(否则两次fputs会出现在同一行)字符串必须以
\0结尾,否则fputs会一直找字符串中的'\0',可能会导致越界访问
使用示例:
#include <stdio.h> int main() { FILE* fp = fopen("test.txt", "w"); if (fp == NULL) { perror("打开文件失败"); return 1; } fputs("hello world", fp); fclose(fp); return 0; }解释:写入了一个字符串,因为是w方式打开文件,所以之间的文件内容会被清空再写入
4:fgets
char * fgets ( char *str, int num, FILE * stream );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
str | char * | 字符串的指针,该字符串用于存储读取到的字符串 |
num | int | 最多读取的字符个数(包含结尾的\0) |
stream | FILE * | 指向要读取的文件流的指针 |
函数作用:
从文件流stream中读取一个最大字符数为(num-1)的字符串str
返回值:
| 返回值 | 含义 |
|---|---|
str(传入的指针str) | 读取成功 |
NULL | 到达文件末尾或发生读取错误 |
注意事项:
num参数包含了结尾的\0,所以实际最多读num-1个字符缓冲区
str要足够大,至少num字节
fgets停止读取的三种情况:
| 情况 | 说明 |
|---|---|
① 遇到换行符\n | 读取换行符,将'\n'存入str,然后停止,末尾加\0 |
② 已读取num-1个字符 | 还没遇到换行符,停止,末尾加\0 |
| ③ 遇到文件尾 EOF | 没有读到任何字符,返回NULL;如果读到部分字符后遇到 EOF,也会停止并加\0 |
❓️:如果fgets读取的时候遇到了'\0'呢,会提前停止吗?
💡:不会!fgets是把\0当作普通数据字符读取,不会因为读到\0就停止!
验证示例:
我们往文件中写入字符'A' '\0' 'B' '\n' 'C',然后再用fgets读取10个字节
❓️:为什么不用fputs直接写入字符串"A\0B\nC"?
💡:因为fputs写入的时候,遇到\0就会停止,不再往后写入,这是特性,否则fputs停不下来
#include <stdio.h> int main() { // 用 fputs 写入(但 fputs 遇到 \0 会停止写入) FILE* fp = fopen("test.txt", "w"); fputs("A", fp); fputc('\0', fp); // 手动写入 \0 fputs("B\nC", fp); fclose(fp); // 用 fgets 读取 fp = fopen("test.txt", "r"); char buf[100] = { 0 }; fgets(buf, 100, fp); for (int i = 0; i < 10; i++) { printf("buf[%d] = %d\n", i, buf[i]); } fclose(fp); return 0; }解释:
| 数组下标 | 十进制值 | 对应字符 | 说明 |
|---|---|---|---|
| buf[0] | 65 | 'A' | 从文件读取的第一个字符 |
| buf[1] | 0 | '\0' | 从文件读取的空字符(fprintf写入的%c) |
| buf[2] | 66 | 'B' | \0后面的B,说明遇到\0没停止 |
| buf[3] | 10 | '\n' | 换行符,遇到它fgets才停止读取 |
| buf[4] | 0 | '\0' | fgets自动添加的字符串结束符 |
| buf[5~9] | 0 | '\0' | 缓冲区剩余空间被初始化为 0(未使用部分) |
这代表遇到第一个'\0'没有停止下来,而遇到'\n'换行符才会停止
❓️:为什么要这么设置?
💡:因为文本文件中压根就没有'\0',你在记事本中见过'\0'吗?但是存在换行!所以检测'\n'!!
5:fprintf
int fprintf ( FILE * stream, const char * format, ... );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
stream | FILE * | 指向要写入的文件流的指针 |
format | const char * | 格式控制字符串(包含格式占位符如%d、%s等) |
... | 可变参数 | 对应格式占位符要输出的变量 |
函数作用:
将格式化的数据写入到指定的文件流中
返回值:
| 返回值 | 含义 |
|---|---|
>= 0 | 成功写入的字符总数 |
| 负数 | 写入失败 |
注意事项:
格式字符串与变量类型要匹配,否则结果不可预料
使用示例:
#include <stdio.h> int main() { FILE* fp = fopen("test.txt", "w"); if (fp == NULL) { perror("打开文件失败"); return 1; } int age = 25; float score = 92.5; char name[] = "张三"; fprintf(fp, "%s\n", name); fprintf(fp, "%d\n", age); fprintf(fp, "%.1f\n", score); //合成一行的写法 //fprintf(fp, "%s %d %.1f\n", name, age, score); fclose(fp); fp = NULL; return 0; }解释:说白了就是在printf的用法上,增加一个FILE*类型的参数fp而已
6:fscanf
int fscanf ( FILE * stream, const char * format, ... );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
stream | FILE * | 指向要读取的文件流的指针 |
format | const char * | 格式控制字符串(指定如何解析数据) |
... | 可变参数 | 对应格式占位符的变量地址(用于存储读取的值) |
函数作用:
从文件中读取格式化的数据
返回值:
| 返回值 | 含义 |
|---|---|
| 正整数 | 成功匹配并赋值的参数个数 |
0 | 没有匹配到任何数据(但可能有字符被读取) |
EOF(-1) | 读取时到达文件尾或发生错误 |
注意事项:
功能与
scanf完全相同,区别是scanf从键盘读取,fscanf从文件读取自动跳过空白字符(空格、换行、制表符等)
遇到类型不匹配或文件尾时停止
使用示例:
#include <stdio.h> int main() { FILE* fp = fopen("test.txt", "r"); if (fp == NULL) { perror("打开文件失败"); return 1; } char name[50]; int age; float score; fscanf(fp, "%s", name); fscanf(fp, "%d", &age); fscanf(fp, "%f", &score); printf("%s\n", name); printf("%d\n", age); printf("%.1f\n", score); fclose(fp); return 0; }解释:说白了就是在scanf的用法上,增加一个FILE*类型的参数fp而已
7:fwite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
ptr | const void * | 指向要写入的数据的指针 |
size | size_t | 每个元素的大小(字节数) |
count | size_t | 要写入的元素个数 |
stream | FILE * | 指向要写入的文件流的指针 |
函数作用:
将数据块以二进制的形式写入文件流(
fwrite从由ptr指向的内存块中取出count个元素,每个元素的大小为size字节,并将它们写入到stream指向的文件流中)
返回值:
| 返回值 | 含义 |
|---|---|
count | 成功写入的元素个数(等于count表示全部成功) |
< count | 部分写入成功(通常表示磁盘满或发生错误) |
注意事项:
文件通常以二进制模式打开(
"wb"、"ab"、"wb+")写入的是原始二进制数据,用记事本打开可能显示乱码
返回的是元素个数,不是字节数(实际字节数 = 返回值 × size)
适合配合
fread使用
使用示例:
#include <stdio.h> int main() { FILE *fp = fopen("test.txt", "wb"); if (fp == NULL) { perror("打开文件失败"); return 1; } int arr[] = {10, 20, 30, 40, 50}; fwrite(arr, sizeof(int), 5, fp); fclose(fp); return 0; }解释:fwrite以二进制的形式向test.txt文件中写入数据后,我们此时打开test.txt文件就是乱码
8:fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数解释:
| 参数 | 类型 | 说明 |
|---|---|---|
ptr | void * | 指向存储读取数据的缓冲区的指针 |
size | size_t | 每个元素的大小(字节数) |
count | size_t | 要读取的元素个数 |
stream | FILE * | 指向要读取的文件流的指针 |
函数作用:
从文件中以二进制形式读取数据到内存中
适合读取数组、结构体等二进制数据
文件位置指示器向后移动
size × 实际读取的元素个数字节
返回值:
| 返回值 | 含义 |
|---|---|
count | 成功读取的元素个数(等于count表示全部成功) |
< count | 部分读取成功(可能到达文件尾或发生错误) |
0 | 没有读取到任何元素(文件尾或错误) |
注意事项:
文件通常以二进制模式打开(
"rb"、"ab"、"rb+")读取的是原始二进制数据,必须与写入时的格式完全一致
缓冲区
ptr必须足够大,至少size × count字节适合配合
fwrite使用
使用示例:
#include <stdio.h> int main() { // 先写入数据 FILE* fp = fopen("test.txt", "wb"); int arr1[] = { 10, 20, 30, 40, 50 }; fwrite(arr1, sizeof(int), 5, fp); fclose(fp); // 再读取数据 fp = fopen("test.txt", "rb"); int arr2[5]; fread(arr2, sizeof(int), 5, fp); for (int i = 0; i < 5; i++) { printf("%d ", arr2[i]); } fclose(fp); return 0; }📌 [ 作者 ] shylyly
📃 [ 首次发布 ] 2024.3.28
❌ [ 最新修改 ] 2026.10.14
📜 [ 声明 ] 由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教