在 C 语言编程中,文件操作是一项极为重要的技能,它允许程序与外部文件进行数据交互,从而实现数据的持久化存储、读取以及复杂数据处理任务。本文将带您深入了解 C 语言的文件操作,涵盖从基本概念到实际应用的各个方面。

一、文件操作的基本概念


在 C 语言中,文件被视为一系列字节的序列。根据数据的组织形式和访问方式,文件可分为文本文件和二进制文件。


  • 文本文件:由字符序列组成,每个字符以 ASCII 码形式存储,便于人类阅读和编辑。例如,一个简单的文本文件可能包含一篇文章、一段代码或者一些配置信息。
  • 二进制文件:以二进制形式存储数据,数据的格式取决于程序对其的定义和解释。它可以存储各种类型的数据,如图像、音频、视频以及经过序列化的复杂数据结构等。

二、文件操作的基本步骤

(一)打开文件


使用 fopen 函数来打开一个文件,其原型为:FILE *fopen(const char *filename, const char *mode);


  • filename 是要打开的文件名,可以包含路径信息。如果文件在当前目录下,直接写文件名即可;如果在其他目录,需要指定完整路径。
  • mode 是打开文件的模式,常见的模式有:
  • "r":以只读方式打开文本文件。如果文件不存在,则打开失败。
  • "w":以只写方式打开文本文件。如果文件存在,则清空文件内容;如果文件不存在,则创建新文件。
  • "a":以追加方式打开文本文件。在文件末尾添加新内容,如果文件不存在,则创建新文件。
  • "rb""wb""ab":分别对应二进制文件的只读、只写、追加模式。


例如,要以只读方式打开一个名为 data.txt 的文本文件,可以这样写:

FILE *fp;
fp = fopen("data.txt", "r");
if (fp == NULL) {
    // 文件打开失败的处理
    perror("fopen");
    return 1;
}

(二)文件读写操作

1. 字符读写


  • 读字符:使用 fgetc 函数从文件中读取一个字符,其原型为:int fgetc(FILE *stream);。每次调用 fgetc 函数,它会从指定的文件流 stream 中读取一个字符,并返回该字符的 ASCII 码值。如果读到文件末尾,返回 EOF-1)。


例如,以下代码从文件中逐个字符读取并打印:

int ch;
while ((ch = fgetc(fp))!= EOF) {
    putchar(ch);
}


  • 写字符:使用 fputc 函数向文件中写入一个字符,其原型为:int fputc(int c, FILE *stream);。其中 c 是要写入的字符,stream 是目标文件流。


例如,向文件中写入一个字符串中的字符:

char str[] = "Hello, World!";
for (int i = 0; str[i]!= '\0'; i++)
{
    fputc(str[i], fp);
}

2. 字符串读写


  • 读字符串fgets 函数用于从文件中读取一行字符串,其原型为:char *fgets(char *s, int n, FILE *stream);。它会从 stream 文件流中读取最多 n - 1 个字符,并将其存储到字符数组 s 中,直到遇到换行符 \n 或者文件末尾。读取的字符串会在末尾自动添加 \0 作为字符串结束标志。如果成功读取,返回 s;如果遇到文件末尾且未读取到任何字符,返回 NULL


例如:

char buffer[100];
while (fgets(buffer, sizeof(buffer), fp)!= NULL) {
    // 处理读取到的字符串
    printf("%s", buffer);
}


  • 写字符串fputs 函数用于向文件中写入一个字符串,其原型为:int fputs(const char *s, FILE *stream);。它将字符串 s 写入到 stream 文件流中,但不会自动添加换行符。


例如:

char lines[][50] = {"This is line 1.", "This is line 2.", "This is line 3."};
for (int i = 0; i < 3; i++) {
    fputs(lines[i], fp);
    fputc('\n', fp); // 手动添加换行符
}

3. 格式化读写


  • 格式化读fscanf 函数类似于 scanf 函数,但它从文件中读取数据并按照指定的格式进行解析。其原型为:int fscanf(FILE *stream, const char *format,...);


例如,从文件中读取整数和浮点数:

int num;
float fnum;
fscanf(fp, "%d%f", &num, &fnum);


  • 格式化写fprintf 函数类似于 printf 函数,用于将格式化的数据写入文件。其原型为:int fprintf(FILE *stream, const char *format,...);


例如:

int age = 25;
float height = 1.75;
fprintf(fp, "Age: %d, Height: %.2f\n", age, height);

4. 二进制数据读写


  • 读二进制数据fread 函数用于从文件中读取二进制数据,其原型为:size_t fread(void *ptr, size_t size, size_t count, FILE *stream);。它从 stream 文件流中读取 count 个大小为 size 字节的数据块,并将其存储到 ptr 指向的内存区域中。返回实际读取的数据块数量。


例如,读取一个结构体数组的二进制数据:

typedef struct {
    int id;
    char name[20];
} Student;

Student students[10];
size_t result = fread(students, sizeof(Student), 10, fp);


  • 写二进制数据fwrite 函数用于向文件中写入二进制数据,其原型为:size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);。它将 ptr 指向的内存区域中 count 个大小为 size 字节的数据块写入到 stream 文件流中。返回实际写入的数据块数量。

例如:

Student newStudent = {101, "John"};
fwrite(&newStudent, sizeof(Student), 1, fp);

(三)关闭文件


使用 fclose 函数关闭已打开的文件,其原型为:int fclose(FILE *stream);。关闭文件非常重要,它可以确保文件缓冲区中的数据被正确写入磁盘,释放文件相关的系统资源。

例如:

fclose(fp);

三、文件操作的错误处理


在文件操作过程中,可能会出现各种错误,如文件不存在、磁盘空间不足、权限问题等。为了确保程序的稳定性和可靠性,需要进行适当的错误处理。


如前面代码示例中,在打开文件后,通过检查 fopen 函数的返回值是否为 NULL 来判断文件是否成功打开。如果打开失败,可以使用 perror 函数打印错误信息,它会输出错误信息到标准错误输出,并在前面加上字符串参数所指定的前缀。

四、文件操作的应用场景

(一)数据存储与读取


例如,编写一个学生成绩管理系统,可以将学生的信息(学号、姓名、成绩等)存储到文件中,下次运行程序时再从文件中读取数据进行处理,实现数据的持久化。

(二)配置文件处理


许多软件都使用配置文件来存储一些可定制的参数,如数据库连接信息、界面布局设置等。程序在启动时读取配置文件,根据其中的参数进行初始化;在运行过程中,如果用户修改了配置,程序可以将新的配置信息写回文件。

(三)日志记录


在程序运行过程中,将重要的事件、错误信息等记录到日志文件中。这样可以方便后续查看程序的运行历史,排查问题。

五、总结


C 语言的文件操作提供了丰富的功能和强大的灵活性,使程序能够与外部文件进行高效的数据交互。通过掌握文件的打开、读写、关闭操作以及错误处理方法,结合不同的应用场景需求,可以开发出功能强大、数据持久化良好的 C 语言程序。无论是处理简单的文本数据还是复杂的二进制数据,文件操作都是 C 语言编程中不可或缺的重要组成部分。希望本文能够帮助您更好地理解和运用 C 语言的文件操作知识,在实际编程中创造更多有价值的应用。