# 文件操作:数据的持久化存储
## 将数据保存到磁盘
在上一篇文章中,我们学习了结构体和共用体的使用。现在,让我们来学习C语言中的文件操作。文件操作允许我们将数据持久化存储到磁盘上,实现数据的长期保存和读取。
—
## 一、文件操作的基本概念
### 1.1 什么是文件
文件是存储在外部存储设备上的数据集合。在C语言中,文件分为两种类型:
| 文件类型 | 描述 | 特点 |
|———-|——|——|
| **文本文件** | 以字符形式存储 | 可读,占用空间较大 |
| **二进制文件** | 以二进制形式存储 | 高效,占用空间较小 |
### 1.2 文件指针
```c
#include <stdio.h>
int main() {
// 声明文件指针
FILE *fp;
// 打开文件
fp = fopen("example.txt", "w");
if (fp == NULL) {
printf("文件打开失败\\n");
return 1;
}
// 写入内容
fprintf(fp, "Hello, File!\\n");
// 关闭文件
fclose(fp);
return 0;
}
```
### 1.3 文件打开模式
| 模式 | 描述 |
|——|——|
| `"r"` | 只读方式打开文本文件 |
| `"w"` | 只写方式打开文本文件,覆盖原有内容 |
| `"a"` | 追加方式打开文本文件 |
| `"r+"` | 读写方式打开文本文件 |
| `"w+"` | 读写方式创建新文件 |
| `"a+"` | 读写方式打开文件,追加模式 |
| `"rb"` | 只读方式打开二进制文件 |
| `"wb"` | 只写方式打开二进制文件 |
| `"ab"` | 追加方式打开二进制文件 |
—
## 二、文件的打开与关闭
### 2.1 fopen函数
```c
#include <stdio.h>
int main() {
FILE *fp;
// 打开文件
fp = fopen("test.txt", "w");
// 检查是否成功打开
if (fp == NULL) {
printf("文件打开失败!\\n");
perror("原因");
return 1;
}
printf("文件打开成功!\\n");
// 关闭文件
fclose(fp);
return 0;
}
```
### 2.2 fclose函数
```c
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) {
perror("文件打开失败");
return 1;
}
// 使用文件…
// 关闭文件
if (fclose(fp) != 0) {
printf("文件关闭失败!\\n");
return 1;
}
printf("文件关闭成功!\\n");
return 0;
}
```
—
## 三、文件的读写操作
### 3.1 字符读写
```c
#include <stdio.h>
int main() {
FILE *fp;
char ch;
// 写入文件
fp = fopen("chars.txt", "w");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
for (ch = 'A'; ch <= 'Z'; ch++) {
fputc(ch, fp);
}
fclose(fp);
// 读取文件
fp = fopen("chars.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
while ((ch = fgetc(fp)) != EOF) {
printf("%c ", ch);
}
printf("\\n");
fclose(fp);
return 0;
}
```
### 3.2 字符串读写
```c
#include <stdio.h>
int main() {
FILE *fp;
char buffer[100];
// 写入字符串
fp = fopen("strings.txt", "w");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
fputs("Hello, World!\\n", fp);
fputs("这是第二行。\\n", fp);
fclose(fp);
// 读取字符串
fp = fopen("strings.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
return 0;
}
```
### 3.3 格式化读写
```c
#include <stdio.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
struct Student s = {"张三", 25, 95.5f};
FILE *fp;
// 写入格式化数据
fp = fopen("student.txt", "w");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
fprintf(fp, "%s %d %.1f\\n", s.name, s.age, s.score);
fclose(fp);
// 读取格式化数据
struct Student s2;
fp = fopen("student.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
fscanf(fp, "%s %d %f", s2.name, &s2.age, &s2.score);
printf("姓名:%s,年龄:%d,成绩:%.1f\\n", s2.name, s2.age, s2.score);
fclose(fp);
return 0;
}
```
—
## 四、二进制文件操作
### 4.1 二进制写入
```c
#include <stdio.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
struct Student students[3] = {
{"张三", 25, 95.5f},
{"李四", 23, 88.0f},
{"王五", 24, 92.3f}
};
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
// 写入二进制数据
fwrite(students, sizeof(struct Student), 3, fp);
fclose(fp);
printf("二进制文件写入成功!\\n");
return 0;
}
```
### 4.2 二进制读取
```c
#include <stdio.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
struct Student students[3];
FILE *fp = fopen("students.dat", "rb");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
// 读取二进制数据
fread(students, sizeof(struct Student), 3, fp);
fclose(fp);
// 输出数据
for (int i = 0; i < 3; i++) {
printf("学生%d:\\n", i + 1);
printf(" 姓名:%s\\n", students[i].name);
printf(" 年龄:%d\\n", students[i].age);
printf(" 成绩:%.1f\\n", students[i].score);
}
return 0;
}
```
—
## 五、文件定位
### 5.1 fseek函数
```c
#include <stdio.h>
int main() {
FILE *fp = fopen("example.txt", "w+");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
// 写入内容
fprintf(fp, "Hello, World!");
// 将文件指针移动到文件开头
fseek(fp, 0, SEEK_SET);
// 读取内容
char buffer[20];
fgets(buffer, sizeof(buffer), fp);
printf("读取内容:%s\\n", buffer);
// 将文件指针移动到第7个字符
fseek(fp, 6, SEEK_SET);
fgets(buffer, sizeof(buffer), fp);
printf("从第7个字符开始:%s\\n", buffer);
// 将文件指针向前移动5个字符
fseek(fp, -5, SEEK_CUR);
fgets(buffer, sizeof(buffer), fp);
printf("向前移动5个字符:%s\\n", buffer);
fclose(fp);
return 0;
}
```
### 5.2 ftell函数
```c
#include <stdio.h>
int main() {
FILE *fp = fopen("example.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
// 获取当前位置
long pos = ftell(fp);
printf("初始位置:%ld\\n", pos);
// 读取一些内容
char buffer[10];
fgets(buffer, sizeof(buffer), fp);
// 获取当前位置
pos = ftell(fp);
printf("读取后的位置:%ld\\n", pos);
fclose(fp);
return 0;
}
```
### 5.3 rewind函数
```c
#include <stdio.h>
int main() {
FILE *fp = fopen("example.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
// 读取第一行
char buffer[50];
fgets(buffer, sizeof(buffer), fp);
printf("第一行:%s", buffer);
// 回到文件开头
rewind(fp);
// 再次读取第一行
fgets(buffer, sizeof(buffer), fp);
printf("再次读取第一行:%s", buffer);
fclose(fp);
return 0;
}
```
—
## 六、文件操作的常见函数
### 6.1 常用函数汇总
| 函数 | 功能 | 示例 |
|——|——|——|
| `fopen` | 打开文件 | `fp = fopen("file.txt", "r");` |
| `fclose` | 关闭文件 | `fclose(fp);` |
| `fgetc` | 读取一个字符 | `ch = fgetc(fp);` |
| `fputc` | 写入一个字符 | `fputc('A', fp);` |
| `fgets` | 读取一行字符串 | `fgets(buf, size, fp);` |
| `fputs` | 写入一行字符串 | `fputs("hello", fp);` |
| `fscanf` | 格式化读取 | `fscanf(fp, "%d", &num);` |
| `fprintf` | 格式化写入 | `fprintf(fp, "%d", num);` |
| `fread` | 二进制读取 | `fread(buf, size, count, fp);` |
| `fwrite` | 二进制写入 | `fwrite(buf, size, count, fp);` |
| `fseek` | 文件定位 | `fseek(fp, offset, origin);` |
| `ftell` | 获取位置 | `pos = ftell(fp);` |
| `rewind` | 回到开头 | `rewind(fp);` |
| `feof` | 判断文件结束 | `while (!feof(fp))` |
—
## 七、文件操作的错误处理
### 7.1 检查文件是否存在
```c
#include <stdio.h>
int fileExists(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp != NULL) {
fclose(fp);
return 1;
}
return 0;
}
int main() {
if (fileExists("example.txt")) {
printf("文件存在\\n");
} else {
printf("文件不存在\\n");
}
return 0;
}
```
### 7.2 错误处理示例
```c
#include <stdio.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("无法打开文件");
printf("错误代码:%d\\n", ferror(fp));
return 1;
}
// 检查是否到达文件末尾
if (feof(fp)) {
printf("已到达文件末尾\\n");
}
fclose(fp);
return 0;
}
```
—
## 八、实战练习
### 练习1:文件复制
```c
#include <stdio.h>
int main() {
FILE *source, *dest;
char ch;
source = fopen("source.txt", "r");
if (source == NULL) {
perror("无法打开源文件");
return 1;
}
dest = fopen("dest.txt", "w");
if (dest == NULL) {
perror("无法打开目标文件");
fclose(source);
return 1;
}
// 逐字符复制
while ((ch = fgetc(source)) != EOF) {
fputc(ch, dest);
}
printf("文件复制成功!\\n");
fclose(source);
fclose(dest);
return 0;
}
```
### 练习2:统计文件行数
```c
#include <stdio.h>
int countLines(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
perror("无法打开文件");
return -1;
}
int count = 0;
char ch;
while ((ch = fgetc(fp)) != EOF) {
if (ch == '\\n') {
count++;
}
}
// 如果文件不以换行结尾,最后一行也算
if (count > 0 || !feof(fp)) {
count++;
}
fclose(fp);
return count;
}
int main() {
int lines = countLines("example.txt");
if (lines >= 0) {
printf("文件共有%d行\\n", lines);
}
return 0;
}
```
### 练习3:学生信息管理系统(带文件存储)
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STUDENTS 100
typedef struct {
int id;
char name[50];
float score;
} Student;
void saveStudents(Student students[], int count) {
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) {
perror("保存失败");
return;
}
fwrite(students, sizeof(Student), count, fp);
fclose(fp);
printf("保存成功!\\n");
}
int loadStudents(Student students[]) {
FILE *fp = fopen("students.dat", "rb");
if (fp == NULL) {
return 0;
}
int count = 0;
while (fread(&students[count], sizeof(Student), 1, fp) == 1) {
count++;
}
fclose(fp);
return count;
}
int main() {
Student students[MAX_STUDENTS];
int count = loadStudents(students);
// 添加新学生
strcpy(students[count].name, "新学生");
students[count].id = count + 1;
students[count].score = 85.5f;
count++;
saveStudents(students, count);
return 0;
}
```
—
## 九、总结与延伸
### 本节重点回顾
1. **文件类型**:文本文件和二进制文件
2. **文件指针**:`FILE *`类型
3. **文件打开**:`fopen(filename, mode)`
4. **文件关闭**:`fclose(fp)`
5. **字符读写**:`fgetc`和`fputc`
6. **字符串读写**:`fgets`和`fputs`
7. **格式化读写**:`fscanf`和`fprintf`
8. **二进制读写**:`fread`和`fwrite`
9. **文件定位**:`fseek`、`ftell`、`rewind`
10. **错误处理**:检查返回值、使用`perror`
### 下节预告
下一篇文章我们将学习《综合实战:实现一个简易计算器》,将前面所学知识综合应用。
—
📌 **知识点卡片**
– 文件类型:文本文件(可读)、二进制文件(高效)
– 文件指针:`FILE *fp`
– 打开模式:`r`, `w`, `a`, `rb`, `wb`, `ab`等
– 字符读写:`fgetc`, `fputc`
– 字符串读写:`fgets`, `fputs`
– 格式化读写:`fscanf`, `fprintf`
– 二进制读写:`fread`, `fwrite`
– 文件定位:`fseek(fp, offset, SEEK_SET/SEEK_CUR/SEEK_END)`
💡 **小技巧**
– 始终检查`fopen`的返回值
– 成对使用`fopen`和`fclose`
– 使用`perror`打印错误信息
– 二进制文件适合存储结构体数据
– 使用`feof`判断文件是否结束
⚠️ **注意事项**
– 文件操作完成后必须关闭文件
– 注意文件路径的正确性
– 二进制文件和文本文件不能混用
– `fgets`会读取换行符,需要注意处理
—
> 如果你对文件操作有任何疑问,欢迎在评论区留言讨论!下一篇见!




