当前位置:嗨网首页>书籍在线阅读

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" 模式可以处理二进制文件。