文件操作(C语言)
文章目录
- 前言
- 一、为什么使用文件
- 二、什么是文件
- 2.1 程序文件
- 2.2 数据文件
- 2.3 文件名
- 三、二进制文件和文本文件
- 文本文件
- 二进制文件
- 四、文件的打开和关闭
- 4.1 流和标准流
- 4.1.1 流
- 4.1.2 标准流
- 4.2 文件指针
- 4.3 文件的打开和关闭
- 五、文件的顺序读写
- 5.1 顺序读写函数介绍
- 5.2 对比一组函数
- 六、文件的随机读写
- 七、文件读取结束的判定
- 7.1 错误示例
- 7.2 正确的文本文件读取示例
- 7.3 二进制文件读取示例
- 八、文件缓冲区
- 总结
前言
在 C 语言程序中,所有变量都是存储在内存里的,程序结束后这些数据就会丢失。为了保存数据,下次能够重新读取,我们就需要文件操作。文件就像我们生活中的文件夹,通过“写入”和“读取”的方式将数据持久化存储。本文将详细介绍 C 语言中文件的各种操作方法,并通过完整的示例代码帮助你理解每一步的实现。
一、为什么使用文件
在实际应用中,我们使用文件操作主要有以下目的:
- 持久化数据:保存用户信息、程序状态等,下次启动程序还能恢复。
- 处理大数据量:内存有限,而文件可以存储远超内存容量的数据。
- 数据共享:不同程序之间通过读写同一文件进行数据交换。
- 记录日志:程序运行时记录错误、调试信息,方便查找问题。
二、什么是文件
文件其实就是硬盘上的数据集合,它可以保存文本、图片、视频、程序代码等各种信息。
2.1 程序文件
- 程序文件:如
.c
、.h
文件,是我们编写源代码的文件;编译后生成的.exe
或.out
文件就是可执行程序。
2.2 数据文件
- 数据文件:用于存储程序运行时产生的数据,如用户输入、计算结果、配置信息等。常见的格式有文本文件(.txt)和二进制文件(.dat、.bin)。
2.3 文件名
文件名由主文件名和扩展名组成,如 data.txt
。在 C 语言中,打开文件时必须提供正确的文件名及路径。
三、二进制文件和文本文件
文本文件
- 文本文件以字符方式存储数据,能被记事本等文本编辑器直接打开查看,具有可读性。
- 示例内容(numbers.txt):
10 20 30
二进制文件
- 二进制文件以原始字节存储数据,常用于保存结构体数据或大数据量信息,直接查看通常会显示乱码。
- 适用于需要高效存取和节省存储空间的场景。
四、文件的打开和关闭
4.1 流和标准流
4.1.1 流
流(stream)是程序和文件之间传输数据的“通道”。你可以把它比作一根水管,数据像水一样在里面流动。
4.1.2 标准流
C 语言提供三个默认的标准流,用于输入输出操作:
- stdin:标准输入(默认是键盘)
- stdout:标准输出(默认是屏幕)
- stderr:标准错误输出(默认也是屏幕,用于错误信息输出)
4.2 文件指针
在 C 语言中,文件操作是通过 FILE *
类型的文件指针实现的。它指向一个表示打开文件的结构体。
4.3 文件的打开和关闭
下面给出一个完整的示例程序,用于打开文件并关闭文件。如果文件不存在,则输出错误信息:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 尝试以只读模式打开文件 data.txt
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("文件打开失败");
return 1; // 返回非0表示错误
}
printf("文件打开成功!\n");
// 此处可以进行文件读写操作
// 关闭文件
fclose(fp);
return 0;
}
编译并运行此程序前,请确保当前目录下存在一个名为 data.txt
的文件。
五、文件的顺序读写
5.1 顺序读写函数介绍
在顺序读写中,文件指针从文件开头开始按顺序读取或写入数据。常用函数包括:
- 文本操作函数:
fprintf()
和fscanf()
:格式化写入和读取数据。fgets()
和fputs()
:逐行读取和写入文本。
- 二进制操作函数:
fread()
和fwrite()
:用于读写二进制数据。
5.2 对比一组函数
下面的示例展示了如何使用不同的函数从不同来源读取数据和向不同目标写入数据。完整示例程序如下:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int num;
char buffer[20];
// 1. 从键盘输入数字
printf("请输入一个数字:");
scanf("%d", &num);
printf("从键盘读取到的数字:%d\n", num);
// 2. 写入数字到文件
FILE *fp = fopen("output.txt", "w");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
fprintf(fp, "数字:%d\n", num);
fclose(fp);
// 3. 从字符串中读取数字
char str[] = "456";
sscanf(str, "%d", &num);
printf("从字符串读取到的数字:%d\n", num);
// 4. 格式化写入字符串
sprintf(buffer, "数字为:%d", num);
printf("sprintf 生成的字符串:%s\n", buffer);
return 0;
}
此示例展示了使用 scanf
/fprintf
/sscanf
/sprintf
分别实现从键盘、文件和字符串的输入输出。
六、文件的随机读写
随机读写允许我们不必从文件开头开始,而是可以跳转到文件中的任意位置进行读写操作。下面示例演示如何使用 fseek()
、ftell()
和 rewind()
实现随机读写。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 打开或创建一个二进制文件,用于读写
FILE *fp = fopen("binary.dat", "wb+");
if (fp == NULL) {
perror("无法打开文件");
return 1;
}
// 定义一个整数数组,写入文件
int arr[5] = {10, 20, 30, 40, 50};
if (fwrite(arr, sizeof(int), 5, fp) != 5) {
perror("写入数据失败");
fclose(fp);
return 1;
}
// 使用 fseek 移动到文件中第三个整数的位置
if (fseek(fp, 2 * sizeof(int), SEEK_SET) != 0) {
perror("fseek 失败");
fclose(fp);
return 1;
}
// 读取第三个整数
int value;
if (fread(&value, sizeof(int), 1, fp) != 1) {
perror("读取数据失败");
fclose(fp);
return 1;
}
printf("第三个数字:%d\n", value);
// 获取当前文件指针位置
long pos = ftell(fp);
printf("当前文件指针位置:%ld\n", pos);
// 使用 rewind 将文件指针重新置于文件开头
rewind(fp);
// 读取并输出全部整数
int num;
printf("完整读取的数组:\n");
for (int i = 0; i < 5; i++) {
if (fread(&num, sizeof(int), 1, fp) != 1) {
perror("读取数组失败");
break;
}
printf("数字 %d: %d\n", i + 1, num);
}
fclose(fp);
return 0;
}
在这个完整的示例中,我们首先写入一个整数数组,然后通过 fseek()
定位到数组中第三个整数的位置,使用 ftell()
检查当前指针位置,最后使用 rewind()
重置文件指针,并顺序读取整个数组。
七、文件读取结束的判定
在读取文件时,我们需要判断是否已经到达文件结尾。一个常见误区是直接使用 feof()
判断文件结束。实际上,只有当读取失败后 feof()
才会返回非零值。
7.1 错误示例
下面的示例展示了错误的使用方法,可能导致最后一次循环多读取一次无效数据:
#include <stdio.h>
int main(void) {
FILE *fp = fopen("numbers.txt", "r");
int x;
// 错误示例:使用 feof() 判断文件结束
while (!feof(fp)) {
fscanf(fp, "%d", &x);
printf("%d\n", x);
}
fclose(fp);
return 0;
}
7.2 正确的文本文件读取示例
正确的做法是检查每次读取的返回值,只有成功读取时才处理数据。下面给出一个完整示例程序:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("numbers.txt", "r");
if (fp == NULL) {
perror("无法打开 numbers.txt");
return 1;
}
int x;
// 当 fscanf 成功读取到一个整数时返回 1
while (fscanf(fp, "%d", &x) == 1) {
printf("读取数字:%d\n", x);
}
fclose(fp);
return 0;
}
请确保当前目录下有一个 numbers.txt
文件,内容可以如下:
10
20
30
40
50
7.3 二进制文件读取示例
下面给出一个完整的二进制文件读取示例程序。首先需要有一个包含整数的二进制文件(如通过写入示例生成),再进行读取操作:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("numbers.bin", "rb");
if (fp == NULL) {
perror("无法打开 numbers.bin");
return 1;
}
int x;
while (fread(&x, sizeof(int), 1, fp) == 1) {
printf("读取数字:%d\n", x);
}
fclose(fp);
return 0;
}
你可以使用下面的程序先生成 numbers.bin
文件:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("numbers.bin", "wb");
if (fp == NULL) {
perror("无法创建 numbers.bin");
return 1;
}
int nums[5] = {10, 20, 30, 40, 50};
if (fwrite(nums, sizeof(int), 5, fp) != 5) {
perror("写入失败");
}
fclose(fp);
return 0;
}
编译并运行该程序后,再运行上面的二进制读取程序查看结果。
八、文件缓冲区
为了提高文件操作的效率,C 语言会使用一个内存缓冲区来暂存数据,等缓冲区满了或程序显式刷新时再写入磁盘。常用函数有:
fflush(fp)
:将缓冲区中的数据立即写入文件。setbuf(fp, NULL)
:关闭缓冲区,使得每次操作都直接写入磁盘(不建议频繁使用,会影响性能)。setvbuf()
:进行更详细的缓冲区设置。
下面是一个完整的示例程序,展示如何使用 fflush()
强制写入数据:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("log.txt", "w");
if (fp == NULL) {
perror("无法打开 log.txt");
return 1;
}
// 写入一条日志记录
fprintf(fp, "日志记录:程序启动\n");
// 立即刷新缓冲区,将数据写入文件
fflush(fp);
// 继续写入另一条日志记录
fprintf(fp, "日志记录:程序正在运行...\n");
fclose(fp);
return 0;
}
运行后,你可以打开 log.txt
查看是否及时写入了第一条日志记录。
总结
本文详细介绍了 C 语言中文件操作的基础知识及常用函数,并提供了每个部分的完整示例代码。你已经了解了:
- 文件的基本概念和分类(程序文件、数据文件、文本与二进制文件)
- 如何使用
fopen()
、fclose()
打开与关闭文件 - 顺序读写函数(
fprintf
、fscanf
、fgets
、fputs
、fread
、fwrite
)的使用方法 - 随机读写技术:利用
fseek()
、ftell()
、rewind()
定位文件内容 - 如何正确判断文件结束,以及避免 feof() 的常见误区
- 文件缓冲区机制及如何使用
fflush()
立即写入磁盘
建议你亲自运行这些示例代码,调试和修改后,能更好地掌握 C 语言的文件操作。希望这篇博客能帮助你在实际编程中更加得心应手!