文章目录
[1. 为什么使用文件?](#1. 为什么使用文件?)
[2. 什么是文件?](#2. 什么是文件?)
[2.1 文件分类](#2.1 文件分类)
[2.2 文件名](#2.2 文件名)
[3. 二进制文件和文本文件](#3. 二进制文件和文本文件)
[3.1 区别](#3.1 区别)
[3.2 示例对比](#3.2 示例对比)
[4. 文件的打开和关闭](#4. 文件的打开和关闭)
[4.1 流和标准流](#4.1 流和标准流)
[4.2 文件指针](#4.2 文件指针)
[4.3 打开和关闭文件](#4.3 打开和关闭文件)
[4.4 文件打开模式](#4.4 文件打开模式)
[5. 文件的顺序读写](#5. 文件的顺序读写)
[5.1 顺序读写函数](#5.1 顺序读写函数)
[5.2 函数对比](#5.2 函数对比)
[6. 文件的随机读写](#6. 文件的随机读写)
[6.1 fseek函数](#6.1 fseek函数)
[6.2 ftell函数](#6.2 ftell函数)
[6.3 rewind函数](#6.3 rewind函数)
[7. 文件读取结束的判定](#7. 文件读取结束的判定)
[7.1 错误使用feof的误区](#7.1 错误使用feof的误区)
[7.2 正确的判断方法](#7.2 正确的判断方法)
[7.3 示例代码](#7.3 示例代码)
[8. 文件缓冲区](#8. 文件缓冲区)
[8.1 缓冲文件系统](#8.1 缓冲文件系统)
[8.2 缓冲区的重要性](#8.2 缓冲区的重要性)
[8.3 刷新缓冲区](#8.3 刷新缓冲区)
[8.4 缓冲模式设置](#8.4 缓冲模式设置)
[9. 总结与最佳实践](#9. 总结与最佳实践)
[9.1 文件操作流程](#9.1 文件操作流程)
[9.2 错误处理要点](#9.2 错误处理要点)
[9.3 性能优化建议](#9.3 性能优化建议)
[9.4 常见陷阱与解决方案](#9.4 常见陷阱与解决方案)
引言
在C语言编程中,文件操作是数据持久化的核心。程序运行时产生的数据如果仅存储在内存中,程序结束后就会丢失。文件操作使得数据能够长期保存在磁盘上,实现数据的持久化存储。本文将全面介绍C语言中的文件操作,从基本概念到高级应用,帮助你掌握文件处理的各项技能。
1. 为什么使用文件?
程序中的数据通常存储在内存中,当程序退出时,内存被回收,数据也随之丢失。为了实现数据的长期保存和重复使用,我们需要将数据存储到磁盘文件中。文件操作使得程序能够:
-
持久化保存数据
-
在不同运行实例间共享数据
-
处理大规模数据而不受内存限制
2. 什么是文件?
2.1 文件分类
从功能角度,文件可分为两类:
-
程序文件:包含程序代码的文件,如源文件(.c)、目标文件(.obj)、可执行文件(.exe)
-
数据文件:程序运行时读写的数据文件,用于存储输入输出数据
本章主要讨论数据文件。
2.2 文件名
文件名由三部分组成:文件路径 + 文件名主干 + 文件后缀
例如:c:\code\test.txt
3. 二进制文件和文本文件
3.1 区别
-
文本文件:以ASCII码形式存储,每个字符占1字节,便于人类阅读
-
二进制文件:以二进制形式存储,与内存中数据格式一致,节省空间
3.2 示例对比
整数10000的存储:
-
文本文件:ASCII形式存储"10000",占用5字节
-
二进制文件:二进制形式存储,占用4字节(假设int为4字节)
cpp
#include <stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.dat", "wb");
fwrite(&a, sizeof(int), 1, pf); // 二进制形式写入
fclose(pf);
return 0;
}
4. 文件的打开和关闭
4.1 流和标准流
C语言通过流的概念来统一各种输入输出设备的操作。程序启动时自动打开三个标准流:
-
stdin:标准输入流(通常为键盘) -
stdout:标准输出流(通常为显示器) -
stderr:标准错误流(通常为显示器)
4.2 文件指针
FILE结构体存储文件的详细信息,通过FILE*指针访问文件。
4.3 打开和关闭文件
cpp
// 打开文件
FILE* fopen(const char* filename, const char* mode);
// 关闭文件
int fclose(FILE* stream);
4.4 文件打开模式

cpp
#include <stdio.h>
int main()
{
FILE* pFile = fopen("myfile.txt", "w");
if (pFile != NULL)
{
fputs("Hello, World!", pFile);
fclose(pFile);
}
return 0;
}
5. 文件的顺序读写
5.1 顺序读写函数

5.2 函数对比
cpp
// 格式化输入函数家族
scanf(); // 从标准输入读取
fscanf(); // 从指定流读取
sscanf(); // 从字符串读取
// 格式化输出函数家族
printf(); // 向标准输出写入
fprintf(); // 向指定流写入
sprintf(); // 向字符串写入
示例:
cpp
#include <stdio.h>
int main() {
FILE* fp = fopen("data.txt", "w");
if (fp) {
fprintf(fp, "Number: %d, Float: %.2f, String: %s\n", 42, 3.14, "Hello");
fclose(fp);
}
// 从文件读取
fp = fopen("data.txt", "r");
if (fp) {
int num;
float f;
char str[20];
fscanf(fp, "Number: %d, Float: %f, String: %s\n", &num, &f, str);
printf("Read: %d, %.2f, %s\n", num, f, str);
fclose(fp);
}
return 0;
}
6. 文件的随机读写
6.1 fseek函数
cpp
int fseek(FILE* stream, long int offset, int origin);
参数origin的取值:
-
SEEK_SET:文件开头 -
SEEK_CUR:当前位置 -
SEEK_END:文件结尾
示例:
cpp
#include <stdio.h>
int main() {
FILE* pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET); // 移动到第9字节
fputs("sam", pFile); // 覆盖"apple"为"sam"
fclose(pFile);
return 0;
}
// 结果:文件内容变为"This is a sample."
6.2 ftell函数
cpp
long int ftell(FILE* stream);
返回文件指针相对于起始位置的偏移量。
示例:
cpp
#include <stdio.h>
int main() {
FILE* pFile = fopen("myfile.txt", "rb");
if (pFile) {
fseek(pFile, 0, SEEK_END);
long size = ftell(pFile);
printf("File size: %ld bytes\n", size);
fclose(pFile);
}
return 0;
}
6.3 rewind函数
cpp
void rewind(FILE* stream);
将文件指针重新定位到文件开头。
示例:
cpp
#include <stdio.h>
int main() {
FILE* pFile = fopen("myfile.txt", "w+");
for (char c = 'A'; c <= 'Z'; c++) {
fputc(c, pFile);
}
rewind(pFile); // 回到文件开头
char buffer[27];
fread(buffer, 1, 26, pFile);
buffer[26] = '\0';
printf("%s\n", buffer); // 输出ABCDEFGHIJKLMNOPQRSTUVWXYZ
fclose(pFile);
return 0;
}
7. 文件读取结束的判定
7.1 错误使用feof的误区
feof()函数不能直接用于判断文件是否结束,它用于判断文件结束的原因是否为遇到文件尾。
7.2 正确的判断方法
文本文件:
-
fgetc:判断返回值是否为EOF -
fgets:判断返回值是否为NULL
二进制文件:
fread:判断返回值是否小于实际要读的个数
7.3 示例代码
文本文件读取结束判断:
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE* fp = fopen("test.txt", "r");
if (!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
int c;
while ((c = fgetc(fp)) != EOF) {
putchar(c);
}
if (ferror(fp)) {
puts("I/O error when reading");
} else if (feof(fp)) {
puts("End of file reached successfully");
}
fclose(fp);
return 0;
}
二进制文件读取结束判断:
cpp
#include <stdio.h>
#define SIZE 5
int main() {
double a[SIZE] = {1., 2., 3., 4., 5.};
FILE* fp = fopen("test.bin", "wb");
fwrite(a, sizeof(*a), SIZE, fp);
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof(*b), SIZE, fp);
if (ret_code == SIZE) {
puts("Array read successfully");
for (int i = 0; i < SIZE; i++) {
printf("%f ", b[i]);
}
putchar('\n');
} else {
if (feof(fp)) {
printf("Unexpected end of file\n");
} else if (ferror(fp)) {
perror("Error reading file");
}
}
fclose(fp);
return 0;
}
8. 文件缓冲区
8.1 缓冲文件系统
ANSIC标准采用缓冲文件系统,系统自动为每个打开的文件在内存中创建文件缓冲区。
工作原理:
-
输出时:数据先送到缓冲区,装满后才一起写入磁盘
-
输入时:数据从磁盘读入缓冲区,再从缓冲区送到程序数据区
8.2 缓冲区的重要性
缓冲区机制提高了文件操作效率,但也带来了一些注意事项:
-
需要适时刷新缓冲区
-
关闭文件时会自动刷新缓冲区
-
程序异常结束时可能丢失缓冲区数据
8.3 刷新缓冲区
cpp
#include <stdio.h>
#include <windows.h>
int main() {
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf); // 数据写入缓冲区
printf("睡眠10秒 - 此时打开test.txt可能看不到内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf); // 强制刷新缓冲区到磁盘
printf("再睡眠10秒 - 现在文件有内容了\n");
Sleep(10000);
fclose(pf); // 关闭文件也会刷新缓冲区
pf = NULL;
return 0;
}
8.4 缓冲模式设置
cpp
#include <stdio.h>
int main() {
// 设置缓冲区大小为1024字节
char buffer[1024];
FILE* fp = fopen("test.txt", "w");
setvbuf(fp, buffer, _IOFBF, sizeof(buffer)); // 全缓冲
fputs("This is a test", fp);
// 立即刷新缓冲区
fflush(fp);
fclose(fp);
return 0;
}
9. 总结与最佳实践
9.1 文件操作流程
-
打开文件 :使用
fopen(),检查返回值是否为NULL -
读写操作:根据需求选择合适的读写函数
-
关闭文件 :使用
fclose(),确保资源释放
9.2 错误处理要点
cpp
FILE* fp = fopen("file.txt", "r");
if (fp == NULL) {
perror("Error opening file");
// 处理错误
}
// 读写操作...
if (ferror(fp)) {
perror("Error reading/writing file");
}
fclose(fp);
9.3 性能优化建议
-
批量读写 :使用
fread/fwrite批量操作,减少系统调用 -
合理缓冲区:根据数据量设置合适的缓冲区大小
-
避免频繁开关:对同一文件多次操作时,保持文件打开
-
使用二进制格式:大数据量时使用二进制格式提高效率
9.4 常见陷阱与解决方案

结语
掌握C语言文件操作是成为合格程序员的重要一步。通过理解文件系统的基本原理、熟练使用各种文件操作函数,并遵循最佳实践,你可以编写出高效、健壮的文件处理程序。文件操作不仅在数据处理、日志记录等常规场景中至关重要,在系统编程、嵌入式开发等领域也有广泛应用。
欢迎在评论区交流讨论,如果觉得有帮助,请点赞收藏支持!
更多C语言技术文章,请访问我的博客主页:我能坚持多久-CSDN博客