程序运行时产生的数据通常存储在内存中,一旦程序结束,这些数据就会丢失。 文件操作允许我们将数据持久化保存到磁盘文件中,并在需要时读取出来。 C语言通过标准库提供了一组文件操作函数,支持文本文件和二进制文件的读写。 本章将系统讲解C语言中文件操作的相关函数及使用方法。
在C语言中,使用 FILE 类型的指针来操作文件。FILE 是一个结构体类型,在 <stdio.h> 中定义,
它包含了文件的各种信息,如文件位置指示器、缓冲区状态等。我们通过 fopen() 函数打开文件并获取文件指针。
FILE *fopen(const char *filename, const char *mode);
mode 参数指定文件的打开模式:
| 模式 | 说明 | 文件存在 | 文件不存在 | 32 32"r" | 只读(文本) | 正常打开 | 打开失败,返回NULL | 32 32"w" | 只写(文本) | 内容被清空 | 创建新文件 | 32 32"a" | 追加(文本) | 在文件末尾追加 | 创建新文件 | 32 32"r+" | 读写(文本) | 正常打开 | 打开失败 | 32 32"w+" | 读写(文本) | 内容被清空 | 创建新文件 | 32 32"a+" | 读和追加(文本) | 可在末尾追加,可读 | 创建新文件 | 32 32"rb", "wb", "ab"等 | 二进制模式 | 同上(b表示二进制) | 同上 | 32
|---|
\n 会被转换为 \r\n,二进制模式则不会进行转换。Unix/Linux系统下两者无区别。
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
文件操作完成后,必须调用 fclose() 关闭文件,释放资源并确保缓冲区数据写入磁盘。
int fclose(FILE *stream);
if (fclose(fp) != 0) {
printf("关闭文件失败\n");
}
fprintf 和 fscanf 与 printf/scanf 类似,只是将输入输出目标改为文件。
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
// 写入数据
fprintf(fp, "姓名: %s 年龄: %d 成绩: %.1f\n", "张三", 20, 88.5);
fprintf(fp, "姓名: %s 年龄: %d 成绩: %.1f\n", "李四", 22, 92.0);
fclose(fp);
// 读取数据
fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
char name[50];
int age;
float score;
while (fscanf(fp, "姓名: %s 年龄: %d 成绩: %f\n", name, &age, &score) == 3) {
printf("%s, %d, %.1f\n", name, age, score);
}
fclose(fp);
return 0;
}
fputc 写入一个字符,fgetc 读取一个字符,常用于逐字符处理。
#include <stdio.h>
int main() {
// 写入文件
FILE *fp = fopen("chars.txt", "w");
if (fp) {
char str[] = "Hello, C Language!";
for (int i = 0; str[i] != '\0'; i++) {
fputc(str[i], fp);
}
fclose(fp);
}
// 读取并输出
fp = fopen("chars.txt", "r");
if (fp) {
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
putchar('\n');
fclose(fp);
}
return 0;
}
fgetc 返回 int 类型,当读到文件末尾或出错时返回 EOF(通常为-1)。
fputs 写入字符串(不自动添加换行),fgets 读取一行(包含换行符,除非行太长)。
#include <stdio.h>
int main() {
FILE *fp = fopen("lines.txt", "w");
if (fp) {
fputs("第一行\n", fp);
fputs("第二行\n", fp);
fclose(fp);
}
fp = fopen("lines.txt", "r");
if (fp) {
char line[100];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("读取: %s", line); // 注意line中已包含换行符
}
fclose(fp);
}
return 0;
}
fgets 读取时最多读取 size-1 个字符,并自动在末尾添加 '\0'。若遇到换行符,会将换行符也读入。
二进制文件以字节流的方式存储数据,常用于保存结构体、数组等原始内存数据,读写速度更快。
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
参数说明:
ptr:指向数据缓冲区的指针。size:每个数据项的大小(字节数)。count:要读写的数据项个数。stream:文件指针。#include <stdio.h>
typedef struct {
int id;
char name[20];
float score;
} Student;
int main() {
Student stu[] = {
{1001, "张三", 88.5},
{1002, "李四", 92.0},
{1003, "王五", 78.5}
};
int n = sizeof(stu) / sizeof(Student);
// 写入二进制文件
FILE *fp = fopen("students.dat", "wb");
if (fp) {
fwrite(stu, sizeof(Student), n, fp);
fclose(fp);
}
// 读取二进制文件
Student readStu[10];
fp = fopen("students.dat", "rb");
if (fp) {
int count = fread(readStu, sizeof(Student), 10, fp);
for (int i = 0; i < count; i++) {
printf("%d\t%s\t%.1f\n", readStu[i].id, readStu[i].name, readStu[i].score);
}
fclose(fp);
}
return 0;
}
文件内部有一个位置指示器,记录当前读写位置。我们可以使用以下函数调整位置:
fseek(FILE *stream, long offset, int whence); —— 移动位置指示器。ftell(FILE *stream); —— 获取当前位置(相对于文件开头的字节数)。rewind(FILE *stream); —— 将位置指示器重置到文件开头。whence 参数可取:
SEEK_SET:文件开头SEEK_CUR:当前位置SEEK_END:文件末尾#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "w+");
if (!fp) return 1;
fprintf(fp, "0123456789");
rewind(fp); // 回到开头
// 移动到第5个字节(索引5)
fseek(fp, 5, SEEK_SET);
int ch = fgetc(fp);
printf("位置5的字符: %c\n", ch); // 输出 '5'
// 获取当前位置
long pos = ftell(fp);
printf("当前位置: %ld\n", pos); // 输出 6
// 从当前位置向后移动3个字节
fseek(fp, 3, SEEK_CUR);
ch = fgetc(fp);
printf("移动后字符: %c\n", ch); // 输出 '9'
// 移动到文件末尾,并获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
printf("文件大小: %ld 字节\n", size);
fclose(fp);
return 0;
}
文件操作极易出错,如文件不存在、权限不足、磁盘满等。应该始终检查函数返回值,并使用 perror() 或 ferror() 定位问题。
#include <stdio.h>
int main() {
FILE *fp = fopen("nonexist.txt", "r");
if (fp == NULL) {
perror("打开文件失败"); // 打印系统错误信息
return 1;
}
// 读写过程中可以检查错误
int ch = fgetc(fp);
if (ferror(fp)) {
printf("读取文件时发生错误\n");
clearerr(fp); // 清除错误标志
}
fclose(fp);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int id;
char name[50];
float score;
} Student;
void saveToFile(Student stu[], int count, const char *filename) {
FILE *fp = fopen(filename, "wb");
if (fp == NULL) {
perror("保存文件失败");
return;
}
fwrite(stu, sizeof(Student), count, fp);
fclose(fp);
printf("保存成功,共 %d 条记录\n", count);
}
int loadFromFile(Student stu[], int max, const char *filename) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
printf("文件不存在或无法打开,将创建新文件\n");
return 0;
}
int count = fread(stu, sizeof(Student), max, fp);
fclose(fp);
return count;
}
void addStudent(Student stu[], int *count) {
if (*count >= 100) {
printf("学生数量已达上限\n");
return;
}
Student s;
printf("请输入学号: ");
scanf("%d", &s.id);
printf("请输入姓名: ");
scanf("%s", s.name);
printf("请输入成绩: ");
scanf("%f", &s.score);
stu[*count] = s;
(*count)++;
printf("添加成功\n");
}
void displayStudents(Student stu[], int count) {
if (count == 0) {
printf("暂无学生信息\n");
return;
}
printf("\n学号\t姓名\t成绩\n");
for (int i = 0; i < count; i++) {
printf("%d\t%s\t%.1f\n", stu[i].id, stu[i].name, stu[i].score);
}
printf("\n");
}
int main() {
Student students[100];
int count = 0;
const char *filename = "students.dat";
int choice;
// 加载已有数据
count = loadFromFile(students, 100, filename);
printf("已加载 %d 条学生记录\n", count);
do {
printf("\n===== 学生成绩管理系统 =====\n");
printf("1. 添加学生\n");
printf("2. 查看所有学生\n");
printf("3. 保存并退出\n");
printf("请选择: ");
scanf("%d", &choice);
switch (choice) {
case 1:
addStudent(students, &count);
break;
case 2:
displayStudents(students, count);
break;
case 3:
saveToFile(students, count, filename);
printf("退出系统\n");
break;
default:
printf("无效选择\n");
}
} while (choice != 3);
return 0;
}
文件操作使得程序能够持久化保存数据,是开发实际应用程序的必备技能。通过掌握 fopen、fclose、读写函数以及文件定位,你可以实现数据的存储与读取。
至此,C语言基础部分已全部结束。接下来的学习可以继续深入指针高级应用、数据结构等内容。