печать на экране и текстовый файл - PullRequest
3 голосов
/ 14 января 2009

Мне нужно выбросить определенные вещи в текстовый файл, и то же самое должно быть отображено на экране. (Я говорю о программе на C) Пункт меню выглядит следующим образом:

1. display AA parameters
2. display BB parameters
3. display CC parameters
4. dump all
5. Exit
Select option >

Если они выбирают 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);

Есть ли самый простой способ реализовать это, чтобы сократить количество строк кода?

Ответы [ 7 ]

7 голосов
/ 14 января 2009

Редактировать: тег C ++, кажется, вводит в заблуждение, может кто-нибудь удалить его, пожалуйста? спасибо:)

Я использую макросы variadic для настройки printf и друзей.

Я бы написал что-то вроде этого:

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

(название происходит от утилиты tee (1))

3 голосов
/ 14 января 2009

Примерно так можно добавить любое количество выходных потоков и изменить их во время выполнения, просто изменив связанный список 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;
}
2 голосов
/ 14 января 2009

Вы также можете просто перенаправить вывод вашего прорга в команду tee(1).

1 голос
/ 14 января 2009

Редактировать: Я не заметил, что вам нужно решение 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
1 голос
/ 14 января 2009

Если вы пишете консольное приложение, вы должны иметь возможность выводить на экран (стандартный вывод), используя что-то вроде:

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

Это должно позволить вам переместить код, который печатает ваши данные, в свою собственную функцию, и передать ФАЙЛ * для его печати. Затем функция может выводить на экран, если вы передаете «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);
    .
    .
    .
}
0 голосов
/ 15 января 2009

Я бы пошел радикальнее, чем то, что люди предлагали до сих пор, но, возможно, это слишком много для вас. (Ключевое слово «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 из <stddef.h>) и указатели на функции и тому подобное, чтобы в dump_data() был просто цикл, повторяющийся по структуре, которая кодирует всю необходимую информацию.

Вы также можете упростить жизнь, используя один и тот же базовый целочисленный тип (long в вашем примере) для всех целочисленных членов структуры данных.

Фред Брукс в «Мифическом месяце человека» - книга, которую стоит прочитать, если вы еще этого не сделали, но обязательно прочитайте издание «Двадцатая годовщина», - в главе 9 говорится:

Покажи мне свои блок-схемы [код] и скрой свои таблицы [структуры данных], и я буду продолжать мистифицировать. Покажите мне свои таблицы, и мне обычно не нужны ваши блок-схемы; они будут очевидны.

Управляемая таблицами версия этого кода может в итоге сэкономить место, а также разочарование в связи с необходимостью одинаково изменить сотню связанных функций, тогда как простое изменение табличных данных могло бы исправить всю партию.

0 голосов
/ 14 января 2009
#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);
...