如果你想学习 C 语言的输入和输出,首先要查看 stdio.h
包含文件。正如你可能从名称中猜到的那样,该文件定义了所有标准 ("std") 输入和输出 ("io") 函数。
大多数人学习的第一个 stdio.h
函数是 printf
函数,用于打印格式化输出。或者 puts
函数,用于打印简单的字符串。这些函数非常适合向用户打印信息,但如果你想做更多的事情,你需要探索其他函数。
你可以通过编写一个常见 Linux 命令的副本来了解这些函数和方法。 cp
命令将一个文件复制到另一个文件。如果你查看 cp
的 man 手册,你会看到 cp
支持广泛的命令行参数和选项。但在最简单的情况下,cp
支持将一个文件复制到另一个文件。
cp infile outfile
你可以用 C 语言编写你自己的这个 cp
命令版本,只需使用几个基本函数来读取和写入文件。
一次读取和写入一个字符
你可以使用 fgetc
和 fputc
函数轻松地进行输入和输出。这些函数一次读取和写入一个字符的数据。 用法在 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
程序,通过使用 fgetc
和 fputc
函数一次读取和写入一个字符。 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
) 可以是固定大小的数组。 例如,你可以使用一个名为 buffer
的 char
数组,其长度为 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 字符缓冲区也是如此。
评论已关闭。