32-一个程序示例
13.7.8 一个程序示例
接下来,我们用一个程序示例说明这些函数的用法。该程序把一系列文件中的内容附加在另一个文件的末尾。该程序存在一个问题:如何给文件传递信息。可以通过交互或使用命令行参数来完成,我们先采用交互式的方法。下面列出了程序的设计方案。
- 询问目标文件的名称并打开它。
- 使用一个循环询问源文件。
- 以读模式依次打开每个源文件,并将其添加到目标文件的末尾。
为演示 setvbuf() 函数的用法,该程序将使用它指定一个不同的缓冲区大小。下一步是细化程序打开目标文件的步骤:
1.以附加模式打开目标文件;
2.如果打开失败,则退出程序;
3.为该文件创建一个4096字节的缓冲区;
4.如果创建失败,则退出程序。
与此类似,通过以下具体步骤细化拷贝部分:
1.如果该文件与目标文件相同,则跳至下一个文件;
2.如果以读模式无法打开文件,则跳至下一个文件;
3.把文件内容添加至目标文件末尾。
最后,程序回到目标文件的开始处,显示当前整个文件的内容。
作为练习,我们使用 fread() 和 fwrite() 函数进行拷贝。程序清单13.5给出了这个程序。
程序清单13.5 append.c 程序
/* append.c -- 把文件附加到另一个文件末尾 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 4096
#define SLEN 81
void append(FILE *source, FILE *dest);
char * s_gets(char * st, int n);
int main(void)
{
FILE *fa, *fs; // fa 指向目标文件,fs 指向源文件
int files = 0; // 附加的文件数量
char file_app[SLEN]; // 目标文件名
char file_src[SLEN]; // 源文件名
int ch;
puts("Enter name of destination file:");
s_gets(file_app, SLEN);
if ((fa = fopen(file_app, "a+")) == NULL)
{
fprintf(stderr, "Can't open %s\n", file_app);
exit(EXIT_FAILURE);
}
if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0)
{
fputs("Can't create output buffer\n", stderr);
exit(EXIT_FAILURE);
}
puts("Enter name of first source file (empty line to quit):");
while (s_gets(file_src, SLEN) && file_src[0] != '\0')
{
if (strcmp(file_src, file_app) == 0)
fputs("Can't append file to itself\n", stderr);
else if ((fs = fopen(file_src, "r")) == NULL)
fprintf(stderr, "Can't open %s\n", file_src);
else
{
if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0)
{
fputs("Can't create input buffer\n", stderr);
continue;
}
append(fs, fa);
if (ferror(fs) != 0)
fprintf(stderr, "Error in reading file %s.\n",
file_src);
if (ferror(fa) != 0)
fprintf(stderr, "Error in writing file %s.\n",
file_app);
fclose(fs);
files++;
printf("File %s appended.\n", file_src);
puts("Next file (empty line to quit):");
}
}
printf("Done appending. %d files appended.\n", files);
rewind(fa);
printf("%s contents:\n", file_app);
while ((ch = getc(fa)) != EOF)
putchar(ch);
puts("Done displaying.");
fclose(fa);
return 0;
}
void append(FILE *source, FILE *dest)
{
size_t bytes;
static char temp[BUFSIZE]; // 只分配一次
while ((bytes = fread(temp, sizeof(char), BUFSIZE, source)) > 0)
fwrite(temp, sizeof(char), bytes, dest);
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
如果 setvbuf() 无法创建缓冲区,则返回一个非零值,然后终止程序。可以用类似的代码为正在拷贝的文件创建一块4096字节的缓冲区。把 NULL 作为 setvbuf() 的第2个参数,便可让函数分配缓冲区的存储空间。
该程序获取文件名所用的函数是 s_gets() ,而不是 scanf() ,因为 scanf() 会跳过空白,因此无法检测到空行。该程序还用 s_gets() 代替 fgets() ,因为后者在字符串中保留换行符。
以下代码防止程序把文件附加在自身末尾:
if (strcmp(file_src, file_app) == 0)
fputs("Can't append file to itself\n",stderr);
参数 file_app 表示目标文件名, file_src 表示正在处理的文件名。
append() 函数完成拷贝任务。该函数使用 fread() 和 fwrite() 一次拷贝4096字节,而不是一次拷贝1字节:
void append(FILE *source, FILE *dest)
{
size_t bytes;
static char temp[BUFSIZE]; // 只分配一次
while ((bytes = fread(temp, sizeof(char), BUFSIZE, source)) > 0)
fwrite(temp, sizeof(char), bytes, dest);
}
因为是以附加模式打开由 dest 指定的文件,所以所有的源文件都被依次添加至目标文件的末尾。注意, temp 数组具有静态存储期(意思是在编译时分配该数组,不是在每次调用 append() 函数时分配)和块作用域(意思是该数组属于它所在的函数私有)。
该程序示例使用文本模式的文件。使用 "ab+" 和 "rb" 模式可以处理二进制文件。