C 文件处理

本文介绍了在 C 语言中读写文本文件和二进制文件的方法。

在 C 语言标准库中,提供了 fprintf()fscanf()fread()fwrite()fseek() 等函数进行文件操作。

为什么需要文件?

  • 文件是计算机存储设备中用于存储数据的容器。
  • 当程序终止时,位于内存中的数据将丢失。而文件则会一直保存在存储设备上。
  • 可以直接将文件中的内容通过读取操作导入到程序中,从而节省手工录入数据的时间。
  • 文件很方便复制和迁移。

文件类型

我们一般在程序中处理两种类型的文件:

  1. 文本文件
  2. 二进制文件

文本文件

文本文件是指那些能看到文件内容的文件,里面就是普通的字符文本。比如我们常见的 .txt, .xml, .html, .c, .yaml 等都是文本文件。

我们可以使用任何简单的文本编辑器(例如记事本)轻松的打开和创建文本文件。

当您打开这些文件时,您将看到文件中的所有内容都是纯文本。您可以轻松编辑或删除内容。

文本文件易于阅读,比二进制文件占用更多的存储空间。

二进制文件

除了文件文件之外的其他文件,我们一般称之为二进制文件。比如 .exe, .bin, .dll, .so 等。

如果我们用文件编辑器打开二进制文件,可能会看到一些乱码符号或一些不认识的字符。

二进制文件不是以纯文本形式存储数据,而是以二进制形式(01)存储数据。

二进制文件可以容纳更多的数据,不易读取,并且比文本文件提供更好的安全性。

文件操作

在 C 中,您可以对文件(文本或二进制)执行 5 种主要操作:

  1. 创建一个新文件
  2. 打开现有文件
  3. 关闭文件
  4. 读取文件内容
  5. 将数据写入文件

处理文件

处理文件时,需要声明一个文件类型的指针。文件和程序之间的通信需要此声明。

FILE *fptr;

打开文件 - 用于创建和编辑

我们使用头文件 stdio.h 中定义的 fopen() 函数打开文件。

在标准 I/O 中打开文件的语法是:

ptr = fopen("fileopen", "mode");

例如,

fopen("D:\\newfile.txt","w");

fopen("D:\\oldfile.bin","rb");
  • 假设目录 d:\ 中不存在文件 newfile.txt 。第一个函数创建一个名为 newfile.txt 的新文件,并根据写入模式 w 将其打开以进行写入。 写入模式允许您创建和编辑(覆盖)文件的内容。
  • 假设目录 d:\ 中存在文件 oldfile.bin。第二个函数打开现有文件并使用读取二进制模式 rb 读取。 读取模式只允许读取文件,不能写入文件。

在标准 I/O 中打开模式

模式 模式的含义 在文件不存在期间
r 读取 如果文件不存在,则 fopen() 返回 NULL。
rb 读取二进制 如果文件不存在,则 fopen() 返回 NULL。
w 写入 如果文件存在,则覆盖其内容。 如果文件不存在,它将被创建。
wb 写入二进制 如果文件存在,则覆盖其内容。如果文件不存在,它将被创建。
a 文件末尾追加数据。 如果文件不存在,它将被创建。
ab 以二进制模式在文件追加数据。 如果文件不存在,它将被创建。
r+ 打开读取和写入。 如果文件不存在,则 fopen() 返回 NULL。
rb+ 以二进制模式打开读取和写入。 如果文件不存在,则 fopen() 返回 NULL。
w+ 打开读取和写入。 如果文件存在,则覆盖其内容。 如果文件不存在,它将被创建。
wb+ 以二进制模式打开读取和写入。 如果文件存在,则覆盖其内容。 如果文件不存在,它将被创建。
a+ 打开读取和追加。 如果文件不存在,它将被创建。
ab+ 以二进制模式打开读取和追加。 如果文件不存在,它将被创建。

关闭文件

对文件读/写完成后,我们应该使用 fclose() 函数关闭文件。

fclose(fptr);

这里, fptr 是与要关闭的文件相关联的文件指针。

读取和写入文本文件

我们使用函数 fprintf()fscanf() 写入和读取文本文件。

他们只是 printf()scanf() 的文件版本。唯一的区别是, fprint()fscanf() 预期的指针是 FILE 结构。

示例 1:写入文本文件

#include <stdio.h>
#include <stdlib.h>

int main()
{
   int num;
   FILE *fptr;

   fptr = fopen("C:\\program.txt","w");

   if(fptr == NULL)
   {
      printf("Error!");
      exit(1);
   }

   printf("Enter num: ");
   scanf("%d",&num);

   fprintf(fptr,"%d",num);
   fclose(fptr);

   return 0;
}

该程序从用户那里获取一个数字并存储在文件中 program.txt

编译运行该程序后,可以看到在计算机的 C 盘中创建了一个文本文件 program.txt,文件中的内容就是您输入的整数。

示例 2:从文本文件中读取

#include <stdio.h>
#include <stdlib.h>

int main()
{
   int num;
   FILE *fptr;

   if ((fptr = fopen("C:\\program.txt","r")) == NULL){
       printf("Error! opening file");

       exit(1);
   }

   fscanf(fptr,"%d", &num);

   printf("Value of n=%d", num);
   fclose(fptr);

   return 0;
}

该程序读取 program.txt 文件中的整数并将其打印到屏幕上。

如果您成功运行了示例 1 并创建了文件,则运行此程序后将打印出文件中的整数。

其他函数,如 fgetchar()fputc() 等,可以以类似的方式使用。

读取和写入二进制文件

函数 fread()fwrite() 用于读取和写入二进制文件。

写入二进制文件

fwrite() 函数用于写入二进制文件。这些函数有四个参数:

  1. 要写入的数据地址
  2. 要写入的数据大小
  3. 要写入数据的数量
  4. 指向要写入的文件的指针。
fwrite(addressData, sizeData, numbersData, pointerToFile);

示例 3:使用 fwrite() 写入二进制文件

#include <stdio.h>
#include <stdlib.h>

struct threeNum
{
   int n1, n2, n3;
};

int main()
{
   int n;
   struct threeNum num;
   FILE *fptr;

   if ((fptr = fopen("C:\\program.bin","wb")) == NULL){
       printf("Error! opening file");

       // Program exits if the file pointer returns NULL.
       exit(1);
   }

   for(n = 1; n < 5; ++n)
   {
      num.n1 = n;
      num.n2 = 5*n;
      num.n3 = 5*n + 1;
      fwrite(&num, sizeof(struct threeNum), 1, fptr);
   }
   fclose(fptr);

   return 0;
}

在这个程序中,我们在 C 盘中创建一个新文件 program.bin

我们声明了一个结构体 threeNum,它包含三个数字类型的属性 n1n2n3。在主函数中定义了结构体变量 num

现在,在 for 循环中,我们使用 fwrite() 将值存储到文件中。

第一个参数是 num 的地址,第二个参数是结构体 threeNum 的大小。

由于我们只插入了一个 num 实例,第三个参数是 1 。最后一个参数 *fptr 指向我们存储数据的文件。

最后,我们关闭了文件。

从二进制文件中读取

函数 fread() 也有 4 个参数,类似于上面的 fwrite() 函数。

fread(addressData, sizeData, numbersData, pointerToFile);

示例 4:使用 fread() 从二进制文件中读取

#include <stdio.h>
#include <stdlib.h>

struct threeNum
{
   int n1, n2, n3;
};

int main()
{
   int n;
   struct threeNum num;
   FILE *fptr;

   if ((fptr = fopen("C:\\program.bin","rb")) == NULL){
       printf("Error! opening file");

       exit(1);
   }

   for(n = 1; n < 5; ++n)
   {
      fread(&num, sizeof(struct threeNum), 1, fptr);
      printf("n1: %d\tn2: %d\tn3: %d", num.n1, num.n2, num.n3);
   }
   fclose(fptr);

   return 0;
}

在这个程序中,我们通过循环逐一的读取 program.bin 文件中的数据。

简单来说,我们每次从 *fptr 指向的文件中读取一个threeNum 大小的数据给结构体 threeNum 变量 num

最终输出了在示例 3 中插入的相同记录。

使用 fseek() 获取数据

如果你在一个文件中有很多记录,并且需要访问某个特定位置的记录,按照上面的方法,你需要遍历它之前的所有记录来获取该记录。

这会浪费大量的内存和操作时间。使用 fseek() 可以更快的获取所需的数据。 fseek() 能够将光标定位到文件中的给定记录。

fseek() 的语法

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

第一个参数 stream 是指向文件的指针。第二个参数 offset 是要查找的记录的位置,第三个参数指定偏移量开始的位置。

fseek() 中定义的不同位置:

位置 含义
SEEK_SET 从文件开头开始偏移。
SEEK_END 从文件末尾开始偏移。
SEEK_CUR 从文件中光标的当前位置开始偏移。

示例 5:fseek()

#include <stdio.h>
#include <stdlib.h>

struct threeNum
{
   int n1, n2, n3;
};

int main()
{
   int n;
   struct threeNum num;
   FILE *fptr;

   if ((fptr = fopen("C:\\program.bin","rb")) == NULL){
       printf("Error! opening file");

       exit(1);
   }

   fseek(fptr, -sizeof(struct threeNum), SEEK_END);

   for(n = 1; n < 5; ++n)
   {
      fread(&num, sizeof(struct threeNum), 1, fptr);
      printf("n1: %d\tn2: %d\tn3: %d\n", num.n1, num.n2, num.n3);
      fseek(fptr, -2*sizeof(struct threeNum), SEEK_CUR);
   }
   fclose(fptr);

   return 0;
}

该程序将以相反的顺序(从后到前)从 program.bin 文件中读取记录并打印出来。