【C语言】文件操作详解


前言

提示:这里可以添加本文要记录的大概内容:

文件操作是C语言中一个至关重要的主题,它允许我们在程序中进行数据的读取、写入和处理。通过文件操作,我们能够实现数据的永久存储和跨程序传递。在这篇博客中,我们将深入探讨C语言中的文件操作,包括文件的打开、读写、关闭等基本操作,以及如何有效地处理文件以满足程序的需求。文件操作不仅是学习C语言的基础,也是构建实用程序和系统的关键一环。

提示:以下是本篇文章正文内容,下面案例可供参考

为什么要使用文件?

在计算机编程和数据处理中,使用文件具有多方面的优势,使其成为不可或缺的工具。

  1. 永久性存储: 文件提供了一种在计算机关机或程序结束后仍能保留数据的方式。通过将数据存储在文件中,可以实现永久性的数据保存,使得数据在不同程序执行周期中保持不变。

  2. 数据共享: 文件是不同程序之间进行数据交换的通用方式。通过读写文件,不同的程序可以共享信息,实现数据的传递和共享。

  3. 备份和恢复: 文件为数据提供了备份和恢复的途径。通过定期创建文件备份,可以在数据丢失或损坏时进行恢复,确保数据的安全性和完整性。

  4. 数据持久性: 将数据存储在文件中使得程序可以在不同运行时点之间传递信息。这对于需要保留状态或历史数据的应用程序非常重要。

  5. 跨平台数据交互: 文件是在不同操作系统之间进行数据交互的通用方式。通过使用文件,可以实现数据在不同平台之间的无缝传递。

  6. 大规模数据存储: 对于大规模的数据,文件提供了一种高效管理和组织数据的方式。数据库系统等基于文件的数据存储结构,为大型数据集的管理提供了灵活性和性能优势。

总的来说,使用文件使得数据能够被持久化、共享、备份,并能够在不同环境中进行传递,为程序的可靠性和灵活性提供了关键支持。

什么是文件?

文件的定义:

在计算机科学中,文件是存储在计算机上用于保存数据的一种资源。它是一系列有序的字节,用于存储信息、文本、图像、程序代码等。文件是计算机中数据的物理载体,通过文件,我们可以持久地存储和组织数据。

文件通常分为文本文件和二进制文件两种主要类型:

  1. 文本文件: 由字符组成,可以被文本编辑器直接阅读和编辑。文本文件包含可打印的字符,如字母、数字和符号,并且通常采用特定的字符编码(如UTF-8或ASCII)。

  2. 二进制文件: 以字节为基本单位,可以包含任意的数据,包括文本、图像、音频、视频等。与文本文件不同,二进制文件不能被文本编辑器直接解读,需要特定的程序进行处理。

文件可以存在于不同的存储介质中,例如硬盘、固态硬盘、光盘、网络存储等。每个文件都有一个唯一的标识符,通常是文件名,用于在文件系统中定位和访问该文件。

在C语言中,文件是通过文件指针(FILE指针)来表示和操作的。通过文件指针,可以打开文件、读写文件内容,并在使用完毕后关闭文件,释放系统资源。文件的概念贯穿于计算机科学的方方面面,是数据管理和交换的核心。

文件名

⼀个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
通常,文件标识常被称为文件名。

二进制文件和文本文件

二进制文件和文本文件的详细说明:

1. 二进制文件:

二进制文件是以字节(byte)为基本单位存储数据的文件类型。在二进制文件中,数据以二进制形式表示,可以包含任意类型的信息,包括文本、图像、音频、视频等。以下是二进制文件的一些特征:

  • 数据表示: 数据以原始的二进制形式存储,不进行字符编码。每个字节可以表示0到255之间的一个值。

  • 可包含任意数据类型: 二进制文件没有规定数据类型,可以包含任何形式的数据。这使得它适用于存储复杂的结构化数据。

  • 不可读: 由于数据以原始形式存储,二进制文件通常不可读。使用文本编辑器打开二进制文件会显示乱码。

  • 通常较小: 相比文本文件,二进制文件通常较小,因为它们不包含可打印字符或换行符。

例子: 图像文件(如JPEG、PNG)、音频文件(如MP3)、可执行文件(如.exe)等都是二进制文件的示例。

2. 文本文件:

文本文件是以文本形式存储的文件类型,其中的数据通常是可打印字符,如字母、数字和符号。以下是文本文件的一些特征:

  • 字符编码: 文本文件使用字符编码(如UTF-8、ASCII)来表示字符。每个字符通常占用一个字节或更多。

  • 只包含可打印字符: 文本文件通常只包含可打印字符和特殊字符(如换行符、制表符),不包含二进制数据。

  • 可读性强: 由于使用字符编码,文本文件通常是可读的。使用文本编辑器可以轻松查看和编辑文本文件。

  • 通常较大: 由于包含了可打印字符和字符编码,文本文件通常较大。

例子: 纯文本文件(如.txt、.csv)、源代码文件(如.c、.java)、HTML文件等都是文本文件的示例。

总的来说,二进制文件和文本文件在存储数据时采用不同的表示方式,具有不同的特性和用途。选择使用哪种文件类型取决于所需的数据结构和处理方式。

文件的打开和关闭

流和标准流

流(Stream)和标准流(Standard Streams)的详细说明:

1. 流(Stream):

在计算机编程中,流是一种用于输入和输出的抽象概念。流是数据在程序和外部资源之间传输的通道。数据流可以是字节流(binary stream)或字符流(character stream),分别用于处理二进制数据和文本数据。

  • 字节流(binary stream): 字节流是以字节为单位进行读写的流,适用于二进制数据的传输,如图像、音频和视频文件。

  • 字符流(character stream): 字符流是以字符为单位进行读写的流,适用于文本数据的传输,保留了字符编码的信息。

流可以分为输入流和输出流:

  • 输入流(Input Stream): 从外部资源(如文件、键盘、网络连接)读取数据到程序中的流。

  • 输出流(Output Stream): 将程序中的数据写入到外部资源的流。

在许多编程语言中,流提供了一种灵活的方式来处理输入和输出,使得程序可以与不同类型的数据源进行交互。

2. 标准流(Standard Streams):

标准流是指在程序执行过程中默认打开的三个流,分别为标准输入流、标准输出流和标准错误流。这些流在程序运行时自动被打开,可以用来进行基本的输入输出操作。

  • 标准输入流(stdin): 默认与键盘关联,用于接收用户的输入。

  • 标准输出流(stdout): 默认与屏幕关联,用于向屏幕输出信息。

  • 标准错误流(stderr): 也与屏幕关联,用于输出错误信息。

标准流允许程序与环境进行基本的输入输出交互,而不需要显式地打开或关闭文件。

在C语言中,标准流通过三个预定义的文件指针来表示:

  • stdin: FILE *stdin,标准输入流。
  • stdout: FILE *stdout,标准输出流。
  • stderr: FILE *stderr,标准错误流。

这些标准流在头文件 <stdio.h> 中声明,C语言的 printfscanf 等函数默认使用标准流进行输入输出。

文件指针

文件指针的详细说明:

1. 定义:

文件指针是一个在程序中用于管理文件的抽象概念,它是一个指向文件的指针变量。文件指针用于跟踪文件的位置、读取文件内容以及将数据写入文件。在C语言中,文件指针的类型是 FILE*,需要通过标准I/O库函数进行初始化和操作。

2. 文件指针的创建:

文件指针是通过 FILE 结构体指针创建的。通常,文件指针的声明和初始化如下:

#include <stdio.h>

FILE *filePointer;

3. 文件指针的初始化:

文件指针可以通过标准库函数来初始化,其中常用的函数包括:

  • fopen 打开文件并返回文件指针,用于后续的读写操作。

    FILE *fopen(const char *filename, const char *mode);
    

    例如,打开一个文本文件进行读操作:

    filePointer = fopen("example.txt", "r");
    

4. 文件指针的定位:

文件指针通过指向文件的当前位置来定位。常用的文件指针定位函数包括:

  • fseek 设置文件指针的位置。

    int fseek(FILE *stream, long offset, int whence);
    

    例如,将文件指针定位到文件开头:

    fseek(filePointer, 0, SEEK_SET);
    

5. 文件指针的读写操作:

通过文件指针,可以进行文件的读取和写入操作。常用的读写函数包括:

  • fread 从文件中读取数据。

    size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
    
  • fwrite 将数据写入文件。

    size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
    

6. 文件指针的关闭:

文件使用完毕后,应该通过 fclose 函数关闭文件指针,释放资源。

int fclose(FILE *stream);

例如:

fclose(filePointer);

文件指针在C语言中是进行文件操作的关键工具,它通过管理文件的位置、读取文件内容和写入数据,为文件的输入输出提供了灵活的接口。正确使用文件指针可以有效地进行文件操作,确保数据的正确读写和文件的正常关闭

常见的文件打开模式

在C语言中,使用 fopen 函数打开文件时,需要指定文件的打开模式,以定义对文件的操作方式。

  1. “r” - 只读模式(Read):

    • 打开文件用于读取。
    • 如果文件不存在,打开操作将失败。
    FILE *file = fopen("example.txt", "r");
    
  2. “w” - 只写模式(Write):

    • 打开文件用于写入。
    • 如果文件不存在,创建一个新文件;如果文件已存在,则截断文件(删除文件中的所有内容)。
    FILE *file = fopen("example.txt", "w");
    
  3. “a” - 追加模式(Append):

    • 打开文件用于写入。
    • 如果文件不存在,创建一个新文件;如果文件已存在,则将数据追加到文件末尾。
    FILE *file = fopen("example.txt", "a");
    
  4. “r+” - 读写模式(Read and Write):

    • 打开文件用于读写。
    • 文件必须已经存在。
    FILE *file = fopen("example.txt", "r+");
    
  5. “w+” - 读写模式(Read and Write):

    • 打开文件用于读写。
    • 如果文件不存在,创建一个新文件;如果文件已存在,则截断文件。
    FILE *file = fopen("example.txt", "w+");
    
  6. “a+” - 读写模式(Read and Write):

    • 打开文件用于读写。
    • 如果文件不存在,创建一个新文件;如果文件已存在,则将数据追加到文件末尾。
    FILE *file = fopen("example.txt", "a+");
    
  7. “b” - 二进制模式:

    • 与上述模式结合,表示以二进制格式打开文件(例如,“rb”、“wb”、“ab”、“r+b” 等)。
    • 在Windows系统中,通常与文本模式结合使用(例如,“rb”、“wb”、“ab”)。
    FILE *file = fopen("example.bin", "rb");
    

这些文件打开模式提供了不同的读写权限和行为,程序员可以根据实际需求选择合适的模式。需要注意的是,在打开文件后,应该检查文件指针是否为 NULL,以确保文件成功打开。

文件的顺序读写

顺序读写文件是指按照文件中数据的顺序,从文件的开头到结尾依次读取或写入数据。下面我将通过C语言的文件操作函数举例说明文件的顺序读写。

1. 顺序写入文件:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");  // 以写入模式打开文件

    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 顺序写入数据到文件
    fprintf(file, "This is line 1.\n");
    fprintf(file, "This is line 2.\n");
    fprintf(file, "This is line 3.\n");

    // 关闭文件
    fclose(file);

    return 0;
}

在这个例子中,fprintf 函数用于将数据按照顺序写入文件。每次调用 fprintf,数据都会被追加到文件的末尾。

2. 顺序读取文件:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "r");  // 以只读模式打开文件

    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    char buffer[100];  // 用于存储读取的数据

    // 顺序读取文件内容
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("Read from file: %s", buffer);
    }

    // 关闭文件
    fclose(file);

    return 0;
}

在这个例子中,使用 fgets 函数按行从文件中顺序读取数据。每次调用 fgets,一行数据被读取到缓冲区 buffer 中,然后可以进行处理,如打印或其他操作。

这两个例子展示了文件的顺序读写操作,它们逐行或逐块地顺序处理文件的内容。在实际应用中,你可能会使用更复杂的数据结构和处理逻辑,但基本的文件顺序读写原理是相似的。

文件的随机读写

文件的随机读写是指在文件中以非顺序的方式访问数据,即可以按照数据在文件中的位置随机跳转、读取或写入。在C语言中,可以通过 fseekftell 函数实现文件的随机读写。

1. 随机写入文件:

ftell 函数用于获取文件位置指针的当前位置,通常用于确定文件的大小或在文件中的当前位置。下面是一个使用 ftell 的例子,展示如何获取文件的当前位置并进行随机写入:

#include <stdio.h>

struct Data {
    int id;
    char name[20];
};

int main() {
    FILE *file = fopen("random_write_ftell_example.bin", "wb+");  // 以二进制读写模式打开文件

    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 随机写入数据到文件
    struct Data data1 = {1, "John"};
    struct Data data2 = {2, "Alice"};
    struct Data data3 = {3, "Bob"};

    fwrite(&data1, sizeof(struct Data), 1, file);

    long position = ftell(file);  // 获取当前文件位置指针的位置
    printf("Current position: %ld\n", position);

    fseek(file, sizeof(struct Data) * 2, SEEK_SET);  // 将文件位置指针移动到第三个数据的位置
    fwrite(&data3, sizeof(struct Data), 1, file);

    position = ftell(file);  // 获取当前文件位置指针的位置
    printf("Current position: %ld\n", position);

    fseek(file, sizeof(struct Data), SEEK_SET);  // 将文件位置指针移动到第二个数据的位置
    fwrite(&data2, sizeof(struct Data), 1, file);

    // 关闭文件
    fclose(file);

    return 0;
}

在这个例子中,我使用 ftell 获取文件位置指针的当前位置,并在写入数据前后打印了当前位置。这有助于理解文件位置指针在不同时刻的位置。请注意,ftell 返回的是 long 类型的值。

2. 随机读取文件:

#include <stdio.h>

int main() {
    FILE *file = fopen("random_example.txt", "r+");  // 以读写模式打开文件

    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    char buffer[100];  // 用于存储读取的数据

    // 随机读取文件内容
    fseek(file, 13, SEEK_SET);  // 将文件位置指针移动到第 13 个字节的位置
    fgets(buffer, sizeof(buffer), file);
    printf("Read from file: %s", buffer);

    // 关闭文件
    fclose(file);

    return 0;
}

在这个例子中,使用 fseek 函数将文件位置指针移动到文件的第 13 个字节的位置,然后使用 fgets 函数读取数据。这就是文件的随机读取操作。你可以根据需要随机定位文件位置指针,以读取或写入文件中的特定数据。

需要注意的是,随机读写文件需要小心处理文件位置指针的移动,确保其在有效的范围内,以避免越界或其他问题。

文件读取结束的判定

在文件读取过程中,feof 函数的返回值不能直接用来判断文件是否结束,因为 feof 主要用于判断最后一次读取操作是否遇到文件末尾(判断读取结束的原因是否是:遇到文件末尾结束)。正确的做法是通过读取函数的返回值来判断文件是否结束。

  1. 文本文件读取结束判定:

    • 对于文本文件,可以通过判断读取函数的返回值是否为 EOFNULL 来确定是否到达文件末尾。
    • 例如,使用 fgetc 函数时,判断返回值是否为 EOF
      int ch = fgetc(file);
      if (ch == EOF) {
          // 文件读取结束或出错
      }
      
    • 使用 fgets 函数时,判断返回值是否为 NULL
      char buffer[100];
      if (fgets(buffer, sizeof(buffer), file) == NULL) {
          // 文件读取结束或出错
      }
      
  2. 二进制文件读取结束判定:

    • 对于二进制文件,可以通过判断读取函数的返回值是否小于实际要读的个数来确定是否到达文件末尾。
    • 例如,使用 fread 函数时,判断返回值是否小于实际要读的个数:
      size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer), file);
      if (bytesRead < sizeof(buffer)) {
          // 文件读取结束或出错
      }
      

这样的判定方式更为准确,因为它直接关联到读取操作的结果,而不是依赖于 feof 的状态。

文件缓冲区

文件缓冲区(File Buffer)是在文件操作中用于暂存数据的内存区域。这个缓冲区的存在是为了提高文件读写的效率,减少频繁的磁盘IO操作。文件缓冲区是C语言标准库为文件操作提供的一种机制,用于在程序和文件之间进行数据传输。

详细解释:

  1. 缓冲区的作用: 文件缓冲区的存在使得文件操作更加高效。而不是每次都直接从磁盘读取或写入一个字节,文件缓冲区允许程序一次性读取或写入一块数据。这些数据暂存在内存中,直到缓冲区满、手动刷新或关闭文件时,才会将数据写入磁盘。

  2. 缓冲区的管理: 缓冲区由C语言标准库进行管理。标准库提供了一些函数(例如 setbufsetvbuf)以及默认的缓冲模式来控制文件缓冲区的行为。

  3. 标准I/O流: 在C语言中,文件操作通常通过标准I/O流来完成。标准I/O流提供了一个缓冲区,使得程序可以更高效地与文件进行交互。

举例说明:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");  // 以只写模式打开文件

    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 向文件写入数据
    fprintf(file, "This is an example line 1.\n");
    fprintf(file, "This is an example line 2.\n");

    // 关闭文件,刷新缓冲区
    fclose(file);

    return 0;
}

在这个例子中,通过 fprintf 函数向文件写入两行数据。由于使用的是标准I/O流,这些数据实际上会被暂存在文件缓冲区中,并不会立即写入磁盘。只有在关闭文件时,或者在需要刷新缓冲区时(例如使用 fflush 函数),数据才会被写入磁盘。

文件缓冲区的使用使得程序可以更有效地进行文件操作,减少了频繁的磁盘IO操作,提高了性能。

总结

通过本文的阐述,我们对C语言中的文件操作有了更深刻的理解。文件操作不仅是将数据存储到硬盘或从硬盘读取数据的手段,更是程序与外部环境进行数据交互的桥梁。无论是读写文本文件、二进制文件,还是处理文件指针和文件流,文件操作都是C语言中不可或缺的一部分。希望通过本文,读者能够更加熟练地运用文件操作,提升程序的稳定性和实用性。