文件操作(C语言版)
目录
一、为什么使用文件
二、什么是文件
2.1、程序文件
2.1.1、源代码文件
2.1.2、头文件与接口
2.1.3、脚本文件
2.1.4、编译与构建文件
2.1.5、配置文件
2.1.6、数据与数据库文件
2.1.7、其他特殊文件
2.2、数据文件
2.3、文件名
2.3.1、文件名的基本构成
2.3.2、文件路径
三、二进制文件和文本文件
四、文件的打开和关闭
4.1、流和标准流
4.1.1、流
4.1.2、标准流
4.2、文件指针
4.3、文件的打开和关闭
五、文件读写函数
5.1、fgetc与fputc
1. fgetc 函数
2. fputc 函数
5.2、fgets与fputs
1. fgets 函数
2. fputs 函数
5.3、fprintf与fscanf
1. fprintf 函数
2. fscanf 函数
5.4、sprintf与sscanf
2. sscanf 函数
5.5、fread与fwrite
1. fwrite 函数
2. fread 函数
5.6、总结
六、文件的随机读取
6.1、rewind函数
6.2、fseek 函数
6.3、ftell 函数
七、文件读取结束的判定
7.1、feof函数
7.2、ferror函数
一、为什么使用文件
二、什么是文件
2.1、程序文件
2.1.1、源代码文件
后缀名 | 语言 | 示例文件 |
.c | C源语言 | test.c |
.cpp | Cpp源代码 | test.cpp |
.java | Java源代码 | test.java |
.py | python脚本 | test.py |
.go | Go源代码 | test.go |
.rs | Rust源代码 | test.rs |
.m | MATLAB源代码 | test.m |
.swift | Swift源代码 | test.swift |
2.1.2、头文件与接口
后缀名 | 用途 | 示例文件 |
.h | C/Cpp头文件 | test.h |
.hpp | Cpp头文件 | test.hpp |
.inc | 通用头文件 | test.inc |
2.1.3、脚本文件
后缀名 | 用途 | 示例文件 |
.sh | Shell脚本(Linux/Unix) | test.sh |
.bat | Windows批处理脚本 | test.bat |
.psl | PowerShell脚本 | test.psl |
.vbs | VBScript脚本 | test.vbs |
2.1.4、编译与构建文件
后缀名 | 用途 | 示例文件 |
.o | C/Cpp编译产生的文件 | test.o |
.obj | Windows下的目标文件 | test.obj |
.exe | Windows可执行文件 | test.exe |
.a | 静态库(Linux/Unix) | test.a |
.so | 动态库(Unix) | test.so |
.dll | Windows动态链接库 | tets.dll |
.jar | Java打包文件 | test.jar |
2.1.5、配置文件
后缀名 | 用途 | 示例文件 |
.json | JSON配置文件 | test.json |
.yml | YANL配置文件 | test.yml |
.ini | 传统配置文件 | test.ini |
.env | 环境变量文件 | test.env |
2.1.6、数据与数据库文件
后缀名 | 用途 | 示例文件 |
.csv | 逗号分隔数据文件 | test.csv |
.sql | SQL脚本文件 | test.sql |
.db | 数据库文件(如SQLite) | test.db |
.xml | XML数据文件 | test.xml |
2.1.7、其他特殊文件
后缀名 | 用途 | 示例文件 |
.gitignore | Git忽律规则文件 | test.gitignore |
.dockerfile | Docker镜像构建文件 | test.dockerfile |
.md | Markdown文档 | test.md |
.log | 日志文件 | test.log |
2.2、数据文件
2.3、文件名
2.3.1、文件名的基本构成
文件名通常由 主文件名 和 扩展名(后缀) 组成,格式为:
主文件名.扩展名
例如:report.docx
、main.c
、index.html
。
1. 主文件名
- 作用:标识文件内容或用途(如
project
、data
)。 - 命名规则:
- 允许字符:字母、数字、
-
、_
(具体取决于操作系统)。 - 长度限制:Windows(260字符)、Linux(255字符)。
- 大小写敏感:Linux区分大小写(
File.txt
≠file.txt
),Windows不区分。
- 允许字符:字母、数字、
2. 扩展名(后缀)
- 作用:指示文件类型(如
.txt
表示文本文件,.exe
表示可执行文件)。 - 常见扩展名:
- 文本文件:
.txt
,.csv
- 程序文件:
.c
,.py
,.java
- 可执行文件:
.exe
(Windows),.out
(Linux)
- 文本文件:
2.3.2、文件路径
文件通过 路径(Path) 定位,分为两种形式:
1. 绝对路径
- 定义:从根目录开始的完整路径。
- 示例:
- Windows:
C:\Users\Alice\Documents\report.docx
- Linux:
/home/alice/docs/report.txt
- Windows:
2. 相对路径
- 定义:相对于当前工作目录的路径。
- 示例(假设当前目录为
/home/alice
):./docs/report.txt
(./
表示当前目录)../downloads/file.zip
(../
表示上级目录)
3. 特殊路径符号
符号 | 含义 | 示例 |
. | 当前目录 | ./test.sh |
.. | 上级目录 | ../test.yml |
~ | 用户主目录 | ~/Downloads/file.zip |
/ | 根目录(Linux) | /etc/hosts |
\ | 路径分隔符(Windows) | C:\Windows\system32 |
三、二进制文件和文本文件

四、文件的打开和关闭
4.1、流和标准流
4.1.1、流
4.1.2、标准流
那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流:
- stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
- stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。
- stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。
4.2、文件指针
struct _ iobuf{char *_ptr;int _cnt;char *_base;int _flag;int _file;int _charbuf;int _bufsiz;char *_tmpfname;};typedef struct _ iobuf FILE ;
不同的C编译器的FILE类型包含的内容不完全相同,但是⼤同⼩异。
每当打开⼀个⽂件的时候,系统会根据⽂件的情况⾃动创建⼀个FILE结构的变量,并填充其中的信息,使⽤者不必关⼼细节。
⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。
下⾯我们可以创建⼀个FILE*的指针变量:
FILE* pf;//⽂件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体变量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接找到与它关联的⽂件。
4.3、文件的打开和关闭
⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。
在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了指针和⽂件的关系。
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );// 关闭⽂件int fclose ( FILE * stream );
mode表⽰⽂件的打开模式,下⾯都是⽂件的打开模式:
实例代码:
#include <stdio.h>
#include <stdlib.h>typedef struct info
{char name[20];int age;int num;char address[20];
}info;
int main()
{info stu[3];for (int i = 0; i < 3; i++){scanf("%s %d %d %s", stu[i].name, &stu[i].age, &stu[i].num, stu[i].address);}//以二进制形式写文件FILE* fp = fopen("001.txt", "wb");if (fp == NULL){perror("open fail");exit(1);}for (int i = 0; i < 3; i++){if (fwrite(&stu[i], sizeof(info), 1, fp) != 1){perror("fwrite fail");exit(1);}}if (fclose(fp) == EOF){perror("close fail");exit(1);}//读二进制文件fp = fopen("001.txt", "rb");if (fp == NULL){perror("open fail");exit(1);}for (int i = 0; i < 3; i++){if (fread(&stu[i], sizeof(info), 1, fp) != 1){perror("fwrite fail");exit(1);}}for (int i = 0; i < 3; i++){printf("%s %d %d %s\n", stu[i].name, stu[i].age, stu[i].num, stu[i].address);}if (fclose(fp) == EOF){perror("close fail");exit(1);}return 0;
}
五、文件读写函数
5.1、fgetc与fputc
1. fgetc
函数
功能
从指定的文件流(FILE*
)中读取一个字符,并返回其ASCII值(int
类型)。如果到达文件末尾(EOF)或发生错误,返回 EOF
(通常是 -1
)。
原型
int fgetc(FILE *stream);
参数
stream
:指向已打开的文件指针(如stdin
或通过fopen
打开的文件)。
返回值
- 成功:返回读取的字符(转换为
int
类型)。 - 失败/EOF:返回
EOF
。
示例
#include <stdio.h>int main() {FILE *file = fopen("test.txt", "r");if (file == NULL) {perror("Error opening file");return 1;}int c;while ((c = fgetc(file)) != EOF) { // 逐字符读取直到文件末尾putchar(c); // 输出到屏幕}fclose(file);return 0;
}
注意事项
- 返回值是
int
而非char
,以便区分有效字符和EOF
。 - 文件必须已以读模式(如
"r"
)打开。
2. fputc
函数
功能
向指定的文件流(FILE*
)写入一个字符,返回写入的字符。失败时返回 EOF
。
原型
int fputc(int char, FILE *stream);
参数
char
:要写入的字符(以int
形式传递,实际写入低8位)。stream
:目标文件指针(如stdout
或通过fopen
打开的文件)。
返回值
- 成功:返回写入的字符。
- 失败:返回
EOF
。
示例
#include <stdio.h>int main() {FILE *file = fopen("output.txt", "w");if (file == NULL) {perror("Error opening file");return 1;}char text[] = "Hello, world!";for (int i = 0; text[i] != '\0'; i++) {fputc(text[i], file); // 逐字符写入文件}fclose(file);return 0;
}
注意事项
- 文件必须已以写模式(如
"w"
、"a"
)打开。 - 可以配合
stdout
直接输出到屏幕
5.2、fgets与fputs
1. fgets
函数
功能
从指定的文件流(FILE*
)中读取一行字符串(或指定长度的字符),并存储到缓冲区中。读取时会保留换行符 \n
(如果存在),并在末尾自动添加 \0
(字符串结束符)。
原型
char *fgets(char *str, int n, FILE *stream);
参数
str
:指向存储读取数据的字符数组(缓冲区)。n
:最大读取字符数(包括\0
),通常设为缓冲区大小。stream
:文件指针(如stdin
或fopen
打开的文件)。
返回值
- 成功:返回
str
(即缓冲区地址)。 - 失败:返回
NULL
。
示例
#include <stdio.h>int main() {FILE *file = fopen("test.txt", "r");if (file == NULL) {perror("Error opening file");return 1;}char buffer[100];while (fgets(buffer, sizeof(buffer), file) != NULL) { // 逐行读取printf("%s", buffer); // 输出到屏幕(包含换行符)}fclose(file);return 0;
}
注意事项
- 读取长度限制:
fgets
最多读取n-1
个字符(留一个位置给\0
)。 - 换行符处理:
- 如果遇到
\n
,会将其存入缓冲区。 - 如果缓冲区满(未遇到
\n
),则不会自动添加换行符。
- 如果遇到
-
stdin
输入:可以用fgets(buffer, size, stdin)
替代gets
(避免缓冲区溢出)。
2. fputs
函数
功能
向指定的文件流(FILE*
)写入一个字符串(不自动添加换行符 \n
)。
原型
int fputs(const char *str, FILE *stream);
参数
str
:要写入的字符串(必须以\0
结尾)。stream
:目标文件指针(如stdout
或fopen
打开的文件)。
返回值
- 成功:返回非负值(通常是
0
或写入的字符数)。 - 失败:返回
EOF
(-1
)。
示例
#include <stdio.h>int main() {FILE *file = fopen("output.txt", "w");if (file == NULL) {perror("Error opening file");return 1;}const char *text = "Hello, world!\n";fputs(text, file); // 写入文件(不自动添加 \n)fputs("Another line", file); // 继续写入(无换行)fclose(file);return 0;
}
注意事项
- 不自动添加换行符:与
puts
不同,fputs
不会在末尾加\n
,需手动添加。 -
stdout
输出:可以用fputs(str, stdout)
替代puts
(但需自行处理换行)。 - 安全性:相比
fprintf
,fputs
更高效,适合写入已知字符串。
5.3、fprintf与fscanf
1. fprintf
函数
功能
将格式化数据写入指定的文件流(如文本文件、标准输出等)。
原型
int fprintf(FILE *stream, const char *format, ...);
- 参数:
stream
:目标文件流(如stdout
、文件指针等)。format
:格式化字符串(类似printf
)。...
:可变参数,对应格式占位符的值。
- 返回值:成功时返回写入的字符数,失败时返回负数。
示例
#include <stdio.h>int main() {FILE *file = fopen("output.txt", "w");if (file == NULL) {perror("Failed to open file");return 1;}int num = 42;fprintf(file, "Value: %d\n", num); // 写入文件fprintf(stdout, "Value: %d\n", num); // 等同于 printffclose(file);return 0;
}
2. fscanf
函数
功能
从文件流中按指定格式读取数据(类似 scanf
,但针对文件)。
原型
int fscanf(FILE *stream, const char *format, ...);
- 参数:
stream
:源文件流(如stdin
、文件指针等)。format
:格式化字符串,定义如何解析输入。...
:可变参数,用于存储读取结果的变量地址。
- 返回值:成功匹配并赋值的参数个数,失败或文件结束时返回
EOF
。
示例
#include <stdio.h>int main() {FILE *file = fopen("input.txt", "r");if (file == NULL) {perror("Failed to open file");return 1;}int num;fscanf(file, "%d", &num); // 从文件读取整数printf("Read value: %d\n", num);fclose(file);return 0;
}
注意事项
- 文件操作必须检查返回值:
- 使用
fopen
后需检查是否为NULL
。 fscanf
在文件结束时返回EOF
,需处理错误情况。
- 使用
- 格式化字符串匹配:
fscanf
的格式占位符(如%d
、%s
)必须与输入数据严格匹配,否则可能导致读取失败。
- 安全性:
fscanf
读取字符串时需避免缓冲区溢出(建议用%Ns
指定最大长度,如%99s
)。
- 二进制文件:
- 对二进制数据,通常使用
fwrite
/fread
而非fprintf
/fscanf
- 对二进制数据,通常使用
5.4、sprintf与sscanf
功能
将格式化数据写入字符串缓冲区(将变量按指定格式转换为字符串)。
原型
int sprintf(char *str, const char *format, ...);
- 参数:
str
:目标字符串缓冲区(需提前分配足够内存)。format
:格式化字符串(与printf
相同)。...
:可变参数,对应格式占位符的值。
- 返回值:成功时返回写入的字符数(不包括
\0
),失败时返回负数。
示例
#include <stdio.h>int main() {char buffer[100];int num = 42;float pi = 3.14159;sprintf(buffer, "Number: %d, Pi: %.2f", num, pi);printf("Buffer: %s\n", buffer); // 输出: "Number: 42, Pi: 3.14"return 0;
}
2. sscanf
函数
功能
从字符串中解析格式化数据(类似 scanf
,但数据来源是字符串而非标准输入)。
原型
int sscanf(const char *str, const char *format, ...);
- 参数:
str
:源字符串(包含待解析的数据)。format
:格式化字符串(定义如何解析数据)。...
:可变参数,用于存储解析结果的变量地址。
- 返回值:成功匹配并赋值的参数个数,失败或无法匹配时返回
EOF
。
示例
#include <stdio.h>int main() {char data[] = "John 25 3.5";char name[20];int age;float score;sscanf(data, "%s %d %f", name, &age, &score);printf("Name: %s, Age: %d, Score: %.1f\n", name, age, score);// 输出: "Name: John, Age: 25, Score: 3.5"return 0;
}
5.5、fread与fwrite
1. fwrite
函数
功能
将内存中的数据块(如结构体、数组)直接写入文件,不进行任何格式转换(二进制写入)。
原型
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
- 参数:
ptr
:指向待写入数据的内存地址(如数组、结构体的指针)。size
:每个数据项的字节大小(如sizeof(int)
)。count
:要写入的数据项数量。stream
:目标文件流(需以二进制模式打开,如"wb"
)。
- 返回值:成功写入的数据项数量(非字节数),若小于
count
表示出错或文件结束。
示例
#include <stdio.h>int main() {FILE *file = fopen("data.bin", "wb"); // 二进制写入模式if (!file) {perror("Failed to open file");return 1;}int nums[] = {1, 2, 3, 4, 5};size_t written = fwrite(nums, sizeof(int), 5, file);printf("Written %zu elements\n", written); // 应输出 5fclose(file);return 0;
}
2. fread
函数
功能
从文件中直接读取二进制数据到内存,不进行格式解析(二进制读取)。
原型
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
- 参数:
ptr
:存储读取数据的内存地址(需提前分配空间)。size
:每个数据项的字节大小(如sizeof(float)
)。count
:要读取的数据项数量。stream
:源文件流(需以二进制模式打开,如"rb"
)。
- 返回值:成功读取的数据项数量,若小于
count
表示出错或文件结束。
示例
#include <stdio.h>int main() {FILE *file = fopen("data.bin", "rb"); // 二进制读取模式if (!file) {perror("Failed to open file");return 1;}int nums[5];size_t read = fread(nums, sizeof(int), 5, file);printf("Read %zu elements:\n", read);for (int i = 0; i < 5; i++) {printf("%d ", nums[i]); // 应输出 1 2 3 4 5}fclose(file);return 0;
}
5.6、总结
读取 | 作用 | 返回值 | 异常判断 | 流 |
fgetc | 读字符 | int | EOF | stdin |
fgets | 读字符串 | char* | NULL | stdin |
scanf | 格式化读取 | int | count | stdin |
fscanf | 格式化读取 | int | count | stdin |
sscanf | 格式化读取(一般是数组) | int | count | stdin |
fread | 二进制读取 | size_t | count | stdin |
注:fread的第二个参数是sizeof(数据类型),第三个参数是count
qsort的第二个参数是count,第三个参数是sizeof(数据类型)
写入 | 作用 | 返回值 | 异常判断 | 流 |
fputc | 写字符 | int | EOF | stdout |
fputs | 写字符串 | int | EOF | stdout |
printf | 格式化写入 | int | count | stdout |
fprintf | 格式化写入 | int | count | stdout |
sprintf | 格式化写入(一般是数组) | int | count | stdout |
fwrite | 二进制写入 | size_t | count | stdout |
注:fwrite的第二个参数是sizeof(数据类型),第三个参数是count
六、文件的随机读取
6.1、rewind函数
功能
将文件指针重置到文件开头,相当于 fseek(fp, 0, SEEK_SET)
的简化版。
原型
void rewind(FILE *stream);
-
参数:
stream
:目标文件流。
- 返回值:无(不返回错误信息,如需检查错误需调用
ferror
)
示例
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {perror("Failed to open file");return 1;
}// 读取文件内容后,重置指针到开头
char buffer[100];
fgets(buffer, sizeof(buffer), fp);
printf("First line: %s", buffer);rewind(fp); // 回到文件开头
fgets(buffer, sizeof(buffer), fp);
printf("First line again: %s", buffer);fclose(fp);
6.2、fseek
函数
功能
将文件指针移动到指定位置,支持从文件头、当前位置或文件尾偏移。
原型
int fseek(FILE *stream, long offset, int whence);
- 参数:
stream
:目标文件流。offset
:偏移量(字节数,可正可负)。whence
:基准位置,取以下值之一:SEEK_SET
(文件开头)SEEK_CUR
(当前位置)SEEK_END
(文件末尾)
- 返回值:成功返回
0
,失败返回非零值(需用ferror
检查错误)
示例
FILE *fp = fopen("data.bin", "rb");
if (fp == NULL) {perror("Failed to open file");return 1;
}// 跳过前 10 字节
fseek(fp, 10, SEEK_SET);// 从当前位置向后移动 5 字节
fseek(fp, 5, SEEK_CUR);// 定位到文件末尾前 20 字节
fseek(fp, -20, SEEK_END);fclose(fp);
6.3、ftell
函数
功能
返回文件指针的当前位置(相对于文件开头的字节偏移量)。
原型
long int ftell(FILE *stream);
- 参数:
stream
:目标文件流。
- 返回值:当前偏移量(字节数),失败返回
-1L
。
示例
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {perror("Failed to open file");return 1;
}fseek(fp, 0, SEEK_END); // 跳到文件末尾
long size = ftell(fp); // 获取文件大小
printf("File size: %ld bytes\n", size);rewind(fp); // 重置指针
printf("Current position after rewind: %ld\n", ftell(fp)); // 输出 0fclose(fp);
七、文件读取结束的判定
7.1、feof函数
功能
检测文件流是否已经到达文件末尾(End Of File, EOF)。
原型
int feof(FILE *stream);
- 参数:
stream
:要检测的文件流。
- 返回值:
- 非零值(true):已到达文件末尾。
- 0(false):未到达文件末尾。
使用场景
- 在循环读取文件时判断是否读完所有数据。
- 注意:
feof
只有在尝试读取超出文件末尾时才会返回true
,因此不能单独用它来控制循环(否则可能导致多读一次)。
示例
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("Failed to open file");return 1;}char buffer[100];while (fgets(buffer, sizeof(buffer), fp) != NULL) {printf("%s", buffer);}if (feof(fp)) {printf("\nReached end of file.\n");} else if (ferror(fp)) {printf("\nError while reading file.\n");}fclose(fp);return 0;
}
说明:
- 使用
fgets
读取文件,直到返回NULL
(表示可能到达文件末尾或发生错误)。 - 再用
feof
确认是否真的到达文件末尾,而不是因为错误
7.2、ferror函数
检测文件流是否发生了读写错误(如磁盘损坏、权限不足等)。
原型
int ferror(FILE *stream);
- 参数:
stream
:要检测的文件流。
- 返回值:
- 非零值(true):发生了错误。
- 0(false):无错误。
使用场景
- 在文件操作后检查是否出错(如
fread
、fwrite
、fscanf
等)。 - 结合
clearerr
可以清除错误标志(见下文)。
示例
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("Failed to open file");return 1;}// 尝试读取一个不存在的字符(触发错误)int ch = fgetc(fp);if (ch == EOF) {if (feof(fp)) {printf("Reached EOF.\n");} else if (ferror(fp)) {printf("Error while reading file.\n");clearerr(fp); // 清除错误标志}}fclose(fp);return 0;
}
说明:
- 如果
fgetc
返回EOF
,可能是文件结束或发生错误。 - 用
ferror
判断是否是错误导致,并用clearerr
清除错误标志(以便后续操作)