了解如何在 C 语言中使用文件输入和输出

了解 I/O 可以帮助你更快地完成任务。
76 位读者喜欢这篇文章。
4 manilla folders, yellow, green, purple, blue

Open Clip Art Library (公共领域)。由 Jen Wike Huger 修改。

如果你想学习 C 语言的输入和输出,首先要查看 stdio.h 包含文件。正如你可能从名称中猜到的那样,该文件定义了所有标准 ("std") 输入和输出 ("io") 函数。

大多数人学习的第一个 stdio.h 函数是 printf 函数,用于打印格式化输出。或者 puts 函数,用于打印简单的字符串。这些函数非常适合向用户打印信息,但如果你想做更多的事情,你需要探索其他函数。

你可以通过编写一个常见 Linux 命令的副本来了解这些函数和方法。 cp 命令将一个文件复制到另一个文件。如果你查看 cp 的 man 手册,你会看到 cp 支持广泛的命令行参数和选项。但在最简单的情况下,cp 支持将一个文件复制到另一个文件。

cp infile outfile

你可以用 C 语言编写你自己的这个 cp 命令版本,只需使用几个基本函数来读取写入文件。

一次读取和写入一个字符

你可以使用 fgetcfputc 函数轻松地进行输入和输出。这些函数一次读取和写入一个字符的数据。 用法在 stdio.h 中定义,非常简单: fgetc 从文件中读取(获取)一个字符,而 fputc 将一个字符放入文件中。

int fgetc(FILE *stream);
int fputc(int c, FILE *stream);

编写 cp 命令需要访问文件。在 C 语言中,你可以使用 fopen 函数打开文件,该函数接受两个参数:文件的名称和你想要使用的模式。该模式通常为 r,表示从文件读取,或 w,表示写入文件。该模式也支持其他选项,但对于本教程,请仅关注读取和写入。

因此,将一个文件复制到另一个文件就变成打开源文件和目标文件的问题,然后一次从第一个文件读取一个字符,然后将该字符写入第二个文件。当文件结束时,fgetc 函数返回从输入文件读取的单个字符,或者文件结束EOF)标记。一旦你读取了 EOF,你就完成了复制,你可以关闭两个文件。 该代码如下所示

  do {
    ch = fgetc(infile);
    if (ch != EOF) {
      fputc(ch, outfile);
    }
  } while (ch != EOF);

你可以使用此循环编写你自己的 cp 程序,通过使用 fgetcfputc 函数一次读取和写入一个字符。 cp.c 源代码如下所示

#include <stdio.h>

int
main(int argc, char **argv)
{
  FILE *infile;
  FILE *outfile;
  int ch;

  /* parse the command line */

  /* usage: cp infile outfile */

  if (argc != 3) {
    fprintf(stderr, "Incorrect usage\n");
    fprintf(stderr, "Usage: cp infile outfile\n");
    return 1;
  }

  /* open the input file */

  infile = fopen(argv[1], "r");
  if (infile == NULL) {
    fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
    return 2;
  }

  /* open the output file */

  outfile = fopen(argv[2], "w");
  if (outfile == NULL) {
    fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
    fclose(infile);
    return 3;
  }

  /* copy one file to the other */

  /* use fgetc and fputc */

  do {
    ch = fgetc(infile);
    if (ch != EOF) {
      fputc(ch, outfile);
    }
  } while (ch != EOF);

  /* done */

  fclose(infile);
  fclose(outfile);

  return 0;
}

你可以使用 GNU 编译器集合 (GCC) 将该 cp.c 文件编译成一个完整的可执行文件

$ gcc -Wall -o cp cp.c

-o cp 选项告诉编译器将编译后的程序保存到 cp 程序文件中。 -Wall 选项告诉编译器打开所有警告。 如果你没有看到任何警告,则表示一切正常。

读取和写入数据块

通过一次读取和写入一个字符的数据来编程你自己的 cp 命令可以完成工作,但速度不是很快。 复制像文档和文本文件这样的“日常”文件时,你可能不会注意到,但是在复制大文件或通过网络复制文件时,你会真正注意到其中的区别。 一次处理一个字符需要大量的开销。

编写此 cp 命令的更好方法是将输入的块读取到内存中(称为缓冲区),然后将该数据集合写入第二个文件。 这要快得多,因为程序可以一次读取更多数据,这需要从文件中“读取”的次数更少。

你可以使用 fread 函数将文件读取到变量中。 此函数采用多个参数:要将数据读入的数组或内存缓冲区 (ptr)、要读取的最小事物的大小 (size)、你要读取的这些事物的数量 (nmemb) 以及要从中读取的文件 (stream)

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

不同的选项为更高级的文件输入和输出提供了相当大的灵活性,例如读取和写入具有特定数据结构的文件。 但是在从一个文件读取数据将数据写入另一个文件的简单情况下,你可以使用作为字符数组的缓冲区。

你可以使用 fwrite 函数将缓冲区写入另一个文件。 这使用与 fread 函数相似的一组选项:从中读取数据的数组或内存缓冲区、需要写入的最小事物的大小、需要写入的这些事物的数量以及要写入的文件。

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

在程序将文件读取到缓冲区,然后将该缓冲区写入另一个文件的情况下,数组 (ptr) 可以是固定大小的数组。 例如,你可以使用一个名为 bufferchar 数组,其长度为 200 个字符。

有了这个假设,你需要更改 cp 程序中的循环,以便从文件读取数据到缓冲区,然后将该缓冲区写入另一个文件

  while (!feof(infile)) {
    buffer_length = fread(buffer, sizeof(char), 200, infile);
    fwrite(buffer, sizeof(char), buffer_length, outfile);
  }

这是你更新后的 cp 程序的完整源代码,该程序现在使用缓冲区来读取和写入数据

#include <stdio.h>

int
main(int argc, char **argv)
{
  FILE *infile;
  FILE *outfile;
  char buffer[200];
  size_t buffer_length;

  /* parse the command line */

  /* usage: cp infile outfile */

  if (argc != 3) {
    fprintf(stderr, "Incorrect usage\n");
    fprintf(stderr, "Usage: cp infile outfile\n");
    return 1;
  }

  /* open the input file */

  infile = fopen(argv[1], "r");
  if (infile == NULL) {
    fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
    return 2;
  }

  /* open the output file */

  outfile = fopen(argv[2], "w");
  if (outfile == NULL) {
    fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
    fclose(infile);
    return 3;
  }

  /* copy one file to the other */

  /* use fread and fwrite */

  while (!feof(infile)) {
    buffer_length = fread(buffer, sizeof(char), 200, infile);
    fwrite(buffer, sizeof(char), buffer_length, outfile);
  }

  /* done */

  fclose(infile);
  fclose(outfile);

  return 0;
}

由于你想将此程序与另一个程序进行比较,因此请将此源代码另存为 cp2.c。 你可以使用 GCC 编译该更新的程序

$ gcc -Wall -o cp2 cp2.c

与以前一样,-o cp2 选项告诉编译器将编译后的程序保存到 cp2 程序文件中。 -Wall 选项告诉编译器打开所有警告。 如果你没有看到任何警告,则表示一切正常。

是的,它真的更快

使用缓冲区读取和写入数据是编写此版本的 cp 程序的更好方法。 因为它可以一次将文件的块读取到内存中,所以程序不需要经常读取数据。 在较小的文件上使用任何一种方法,你可能不会注意到任何差异,但是如果你需要复制更大的文件,或者在较慢的介质(如网络连接)上复制数据时,你将真正看到其中的差异。

我使用 Linux time 命令运行了运行时比较。 此命令运行另一个程序,然后告诉你该程序完成需要多长时间。 对于我的测试,我想看看时间的差异,所以我复制了一个 628MB 的 CD-ROM 映像文件到我的系统上。

我首先使用标准的 Linux cp 命令复制映像文件,以查看需要多长时间。 通过首先运行 Linux cp 命令,我还消除了 Linux 内置文件缓存系统不会为我的程序提供虚假性能提升的可能性。 使用 Linux cp 的测试花费的时间远不到一秒钟

$ time cp FD13LIVE.iso tmpfile

real	0m0.040s
user	0m0.001s
sys	0m0.003s

使用我自己版本的 cp 命令复制同一文件花费的时间明显更长。 一次读取和写入一个字符花费了将近 5 秒钟的时间来复制文件

$ time ./cp FD13LIVE.iso tmpfile

real	0m4.823s
user	0m4.100s
sys	0m0.571s

从输入读取数据到缓冲区,然后将该缓冲区写入输出文件要快得多。 使用此方法复制文件花费的时间不到一秒钟

$ time ./cp2 FD13LIVE.iso tmpfile

real	0m0.944s
user	0m0.224s
sys	0m0.608s

我的演示 cp 程序使用了一个 200 个字符的缓冲区。 我确信如果我一次将更多文件读入内存,该程序将运行得更快。 但是对于这种比较,你已经可以看到性能上的巨大差异,即使使用一个小的 200 字符缓冲区也是如此。

接下来阅读什么
标签
photo of Jim Hall
Jim Hall 是一位开源软件倡导者和开发人员,最出名的是他在 GNOME 中的可用性测试,以及作为 FreeDOS 的创始人和项目协调员。

评论已关闭。

© . All rights reserved.