Еліпсис, або як створити свій аналог printf

Вересень 13th, 2008

Еліпсисом (англ ellipsis) називають елемент синтаксису мови C, що представляє собою три крапки. Наприклад, як у оголошенні фукнції printf :
int printf(const char *format, ...);

Еліпсис означає, що на цьому місці знаходится якась кількість змінних якогось типу. Функція сама має визначити, які саме змінні їй передали.

Для доступу до схованих за трьома крапками змінних використовують макроси va_start(), va_arg(), та va_end() (оголошені в stdarg.h). Виглядає це якось так:

  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. void shos(char *fmt, …)
  4. {
  5.     va_list ap;
  6.     int d;
  7.     char c, *s;
  8.     va_start(ap, fmt);
  9.     while (*fmt)
  10.     {
  11.         switch(*fmt) {
  12.             case ‘$’: /* string */
  13.                   s = va_arg(ap, char *);
  14.                   printf("%s", s);
  15.                   break;
  16.             case ‘#’: /* int */
  17.                   d = va_arg(ap, int);
  18.                   printf("%d", d);
  19.                   break;
  20.             default:
  21.                   printf("%c", *fmt);
  22.         }
  23.         fmt++;
  24.     }
  25.     va_end(ap);
  26.     printf("\n");
  27. }

Функція виводить переданий рядок на екран, вставляючи значення чисел (змінних типу int) замість ‘#’ та значення переданних текстових змінних замість ‘$’. Викликавши shos("The $ is #", "answer", 42) маємо отримати фразу “The answer is 42″

Доступ до стеку (схованих за еліпсисом даних) реалізується через змінну типу va_list. Спочатку її ініціалізують макросом va_start, передаючи також останню змінну, тип якої відомий. Далі кожне потрібне значення отримують, викликавши макрос va_arg з вказанням очікуваного типу. А після того, як всю роботу буде завершено, обов’язково слід викликати макрос va_end().

В принципі, для реального життя досить важко придумати випадок, коли б вам знадобилося використовувати еліпсис. Єдине виключення — створення власного аналогу тої ж таки функції printf. Така функція стане в пригоді, коли треба буде відправляти налагоджувальні повідомлення. Схоже на qDebug() в QT, але в нас буде краще. Десь так:

  1. #include <stdio.h>
  2. #include <stdarg.h>
  3.  
  4. enum known_log_levels
  5. {
  6.   LL_LOW = 1,
  7.   LL_MEDIUM,
  8.   LL_HIGH
  9. };
  10.  
  11. signed char currentLogLevel = LL_LOW;
  12.  
  13. void print2log( signed char logLevel, const char* format, … )
  14. {
  15.     va_list args;    
  16.  
  17.     if (currentLogLevel<=logLevel)
  18.     {
  19.         va_start( args, format );
  20.         vfprintf( stderr, format, args );
  21.         va_end( args );
  22.     }
  23. }

Використовується це наступним чином. Спочатку ваша програма має визначити наявний рівень відображення повідомлень (прочитати з конфігураційного файлу, наприклад) і занести це значення у currentLogLevel. Потім, коли виникне якась подія, можна буде зробити щось таке: print2log( LL_LOW, "Event N%u\n", eventnum ); Відповідно, таке повідомлення з’явиться лише в тому випадку, якщо ви встановили рівень попереджень у LL_LOW.

Наша функція print2log занадто спрощена. Проявивши фантазію, можна придумати ще кілька варіантів використання ідеї рівнів відображення. Додавати до більш важливих повідомлень дату, наприклад. Або зберігати повідомлення у файлі, починаючи з певного рівня. Втім, це вже справа особистого смаку.

Категорії: C/C++ | Теґи:, , , ,

Коментарів: 1

  1. Miriam

    отличный пример стоящего материала. благо, автор просто гений….

    http://ww3.hmarka.net/2008/09/ellipsis/…

Залишити коментар