【问题标题】:printing on screen and a text file在屏幕上打印和文本文件
【发布时间】:2009-01-14 18:21:05
【问题描述】:

我需要将某些内容转储到文本文件中,并且需要在屏幕上显示。 (我说的是一个 C 程序实用程序) 菜单选项如下所示,

1.显示AA参数 2.显示BB参数 3.显示CC参数 4.全部转储 5. 退出 选择选项 >

如果他们选择1/2/3,它只需要显示在屏幕上,或者如果他们选择选项#4,它需要一个一个显示所有参数,同样需要转储到一个.txt文件中。

我知道,我们可以使用 printf 和 fprintf 函数分别在屏幕上显示和写入文本文件。问题是我显示了超过 20 个参数,每个参数至少有 20 个子参数。

我目前的实现如下,

printf (        "Starting serial number       [%ld]\n", 
        serial_info_p->start_int_idx);
fprintf(file_p, "Starting serial number       [%ld]\n", 
        serial_info_p->start_int_idx)
printf (        "Current Serial number         [%d]\n", 
        serial_info_p->current_int_idx);
fprintf(file_p, "Current Serial number         [%d]\n", 
        serial_info_p->current_int_idx);

有没有一种最简单的方法来实现这个以减少代码行数?

【问题讨论】:

  • 我认为,只是一个简单的管道通过 tee 不是一种选择?

标签: c text tee


【解决方案1】:

编辑:C++ 标签似乎具有误导性,有人可以删除它吗? 谢谢 :)

我使用可变参数宏来自定义 printf 和朋友。

我会这样写:

#define     tee(fp,fmt, ...)                             \
        {                                                \
                printf (fmt, __VA_ARGS__);               \
                fprintf (fp, fmt, __VA_ARGS__);          \
        }

(名称来自 tee(1) 实用程序)

【讨论】:

    【解决方案2】:

    这样的事情允许您添加任意数量的输出流,并允许在运行时通过修改 PrintTarget 链表来更改它们。

    /** gcc -Wall -o print_target print_target.c && ./print_target */
    #include <stdarg.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct PrintTarget* PrintTargetp;
    
    void* xmalloc (size_t size);
    PrintTargetp pntCreate (PrintTargetp head, FILE* target);
    void pntDestroy (PrintTargetp list);
    
    typedef struct PrintTarget
    {
      FILE* target;
      PrintTargetp next;
    } PrintTarget;
    
    void myPrintf (PrintTargetp streams, char* format, ...)
    {
      va_list args; 
      va_start(args, format);
      while (streams)
        {
          vfprintf(streams->target, format, args);
          streams = streams->next;
        }
      va_end(args);
    }
    
    int main(void)
    {
      PrintTargetp streams = pntCreate(NULL, stdout);
      streams = pntCreate(streams, fopen("somefile.txt", "a+")); //XXX IO errors?
    
      myPrintf(streams, "blah blah blah...\n");
      pntDestroy(streams);
      return 0;
    }
    

    下面是辅助函数的定义:

    PrintTargetp pntCreate (PrintTargetp head, FILE* target)
    {
      PrintTargetp node = xmalloc(sizeof(PrintTarget));
      node->target = target;
      node->next   = head;
      return node;
    } 
    
    void pntDestroy (PrintTargetp list)
    {
      while (list) 
        {
          PrintTargetp next = list->next;
          free(list);
          list = next;
          //XXX cycles?
          //XXX close files?
        }
    }
    
    void* xmalloc (size_t size)
    {
      void* p = malloc(size);
      if (p == NULL)
        {
          fputs("malloc error\n", stderr);
          abort();
        }
      return p;
    }
    

    【讨论】:

    • 小函数PrintTarget* pnt_create(PrintTarget* head, FILE* target)void pnt_destroy(PrintTarget* list) 不会有什么坏处。
    • 这是一个非常好的算法。虽然,我不确定它是否有助于减少代码行,因为问题只涉及两个不同的输出流。无论如何,这是一个结构良好的解决方案。
    • @Josh:我添加了pntCreate()pntDestroy() 的示例定义。随意回滚。
    【解决方案3】:

    您也可以将程序的输出通过管道传送到tee(1) 命令。

    【讨论】:

      【解决方案4】:

      如果您正在编写控制台应用程序,您应该能够使用以下方式输出到屏幕(标准输出):

      fprintf(stdout, "Hello World\n");
      

      这应该使您能够将打印数据的代码移动到它自己的函数中,并传递一个 FILE* 以供它打印。然后,如果您传递“stdout”,则该函数可以打印到屏幕,或者如果您传递不同的 FILE*,则该函数可以打印到文件,例如:

      void print_my_stuff(FILE* file) {
          fprintf( file,"Starting serial number       [%ld]\n", serial_info_p->start_int_idx);
          fprintf(file, "Current Serial number         [%d]\n", serial_info_p->current_int_idx);
          .
          .
          .
      }
      

      【讨论】:

        【解决方案5】:

        编辑:我没有注意到您需要 C 解决方案。我会把这个答案留作参考,但它显然需要 C++。

        您可以创建一个新的流类,将输出发送到两个流。我在http://www.cs.technion.ac.il/~imaman/programs/teestream.html 找到了这个实现。我没试过,但应该可以。

        这是链接中的代码:

        #include <iostream>
        #include <fstream>
        
        template<typename Elem, typename Traits = std::char_traits<Elem> >
        struct basic_TeeStream : std::basic_ostream<Elem,Traits>
        {
           typedef std::basic_ostream<Elem,Traits> SuperType;
        
           basic_TeeStream(std::ostream& o1, std::ostream& o2) 
              :  SuperType(o1.rdbuf()), o1_(o1), o2_(o2) { }
        
           basic_TeeStream& operator<<(SuperType& (__cdecl *manip)(SuperType& ))
           {
              o1_ << manip;
              o2_ << manip;
              return *this;
           }
        
           template<typename T>
           basic_TeeStream& operator<<(const T& t)
           {
              o1_ << t;
              o2_ << t;
              return *this;
           }
        
        private:
           std::ostream& o1_;
           std::ostream& o2_;
        };
        
        typedef basic_TeeStream<char> TeeStream;
        

        你会这样使用它:

        ofstream f("stackoverflow.txt");
        TeeStream ts(std::cout, f);
        ts << "Jon Skeet" << std::endl; // "Jon Skeet" is sent to TWO streams
        

        【讨论】:

        • 天哪,这个世界不再需要 Zapp Branigan。一个太多了。
        • @Adam,两个 Jon Skeets 怎么样?我不知道 Stack Overflow 是否可以处理。
        【解决方案6】:

        我会比目前人们所建议的更激进,但也许对你来说太过分了。 ('inline' 关键字是 C99;如果您编码为 C89,则可以省略它而不会产生太大影响。)

        /*
        ** These could be omitted - unless you get still more radical and create
        ** the format strings at run-time, so you can adapt the %-24s to the
        ** longest tag you actually have.  Plus, with the strings all here, when
        ** you change the length from 24 to 30, you are less likely to overlook one!
        */
        static const char fmt_int[]  = "%-24s [%d]\n";
        static const char fmt_long[] = "%-24s [%ld]\n";
        static const char fmt_str[]  = "%-24s [%s]\n";   /* Plausible extra ... */
        
        static inline void print_long(FILE *fp, const char *tag, long value)
        {
            fprintf(fp, fmt_long, tag, value);
        }
        
        static inline void print_int(FILE *fp, const char *tag, int value)
        {
            fprintf(fp, fmt_int, tag, value);
        }
        
        static inline void print_str(FILE *fp, const char *tag, const char *value)
        {
            fprintf(fp, fmt_str, tag, value);
        }
        
        static void dump_data(FILE *fp, const serial_info_t *info)
        {
            dump_long("Starting serial number", info->start_int_idx);
            dump_int( "Current Serial number",  info->current_int_idx);
            /* ... and similar ... */
        }
        

        然后调用代码将调用 dump_data() 一次(带有参数 stdout)用于选项 1、2、3 和两次(一次带有 stdout,一次带有文件指针用于输出文件)用于选项 4。

        如果参数的数量真的很大(达到数百个),我什至会考虑一个数据结构,它编码类型和偏移信息(offsetof 来自&lt;stddef.h&gt;)和指向函数的指针诸如此类,这样dump_data() 中就会有一个循环,遍历一个编码所有必要信息的结构。

        您还可以通过对数据结构的所有整数成员使用相同的基本整数类型(在您的示例中为long)来简化生活。

        Fred Brooks 在“神话人物月”中——如果你还没有读过一本非常值得一读的书,但请确保你阅读了 20 周年纪念版——在第 9 章中说:

        给我看你的流程图[代码]并隐藏你的表格[数据结构],我将继续被迷惑。给我看你的表格,我通常不需要你的流程图;它们会很明显。

        此代码的表格驱动版本最终可能会节省空间,但在必须以相同方式更改一百个相关函数时会感到沮丧,而表格数据中的简单更改可能会解决整个问题。

        【讨论】:

          【解决方案7】:
          #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
          
          FILE *f = fopen("somefile.txt", "a+");
          FILE *fp[] = { stdout, f };
          int i = 0;
          
          for (i = 0; i < ARRAY_LEN(fp); i++) {
              fprintf(fp[i], "Starting serial number [%ld]\n", serial_info_p->start_int_idx);
              fprintf(fp[i], "Current serial number [%ld]\n", serial_info_p->start_int_idx);
          }
          
          fclose(f);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-03-06
            • 1970-01-01
            • 1970-01-01
            • 2012-12-08
            • 1970-01-01
            • 2021-07-20
            相关资源
            最近更新 更多