2015-10-20 5 views
2

Woking с простой функцией для печати similated 2D матрицы, я просто хочу, чтобы иметь возможность передавать массив указателей на тип функции, как void**, наряду с необходимыми размерами m x n, в sizeof type, формат строку а для printf и простой флаг, чтобы отличить с плавающей запятой от integer. Проблема, с которой я сталкиваюсь, - это обработка области для разыменования массива указателей, чтобы каждый элемент мог быть правильно напечатан как исходный тип. Ниже, основная схема:Как пройти (void **) до функции и эффективно Развернуть/Использовать для любого типа?

void mtrx_prnv (size_t m, size_t n, void **matrix, 
       size_t sz, char *fmt, char fp) 
{ 
    if (fp) { // floating point 
     if (sz == 4) { 
      float **mtrx = (float **)matrix; 
      ... 
     } 
     else if (sz == 8) { 
      double **mtrx = (double **)matrix; 
      ... 
     } 
    } 
    else { 
     if (sz == 1) { 
      char **mtrx = (char **)matrix; 
      ... 
     } 
     else if (sz == 2) { 
      ... 
     } 
     ... 
    } 
} 

подход отлично работает, но проблема заключается в том, что после тестирования для плавающей запятой флага 'fp' и SizeOf типа 'sz', любое создание указателя использовать для разыменования ограничиваются объемом тестового блока и заканчивается тем, что требует базового дословного дублирования кода, необходимого для обработки массива указателей в каждом блоке. Код заканчивается длиннее, чем создание множества небольших функций для каждого типа. например .:

void mtrx_prn_float (size_t m, size_t n, float **matrix) {} 

void mtrx_prn_int (size_t m, size_t n, int **matrix) {} 

Есть ли лучше или стандартный способ передать массив указателей типа, как void**, тип SIZEOF, и любые другие флаги необходимы, чтобы правильно разыменовать печатать без такого большого дублирования кода? Если так оно и должно быть, все в порядке, но я хочу убедиться, что я не пропустил простой трюк. Пока мои поиски не принесли ничего полезного (подавляющая часть информации, возвращаемая поиском, о том, как вернуть один тип, а не отделить все типы). How to write C function accepting (one) argument of any type не был здесь.

Полная функция (без дополнительной логики для разделения int/unsigned) приведена ниже.

/* printf array of pointers to type as 2D matrix of 
    size 'm x n' with sizeof type 'sz', format string 
    'fmt' and floating point flag 'fp' (0 - int). 
*/ 
void mtrx_prnv (size_t m, size_t n, void **matrix, 
       size_t sz, char *fmt, char fp) 
{ 
    register size_t i, j; 

    if (fp) { /* floating point */ 

     if (sz == 4) { 
      float **mtrx = (float **)matrix; 
      for (i = 0; i < m; i++) { 
       char *pad = " [ "; 
       for (j = 0; j < n; j++) { 
        printf (fmt, pad, mtrx[i][j]); 
        pad = ", "; 
       } 
       printf ("%s", " ]\n"); 
      } 
     } 
     else if (sz == 8) { 
      double **mtrx = (double **)matrix; 
      for (i = 0; i < m; i++) { 
       char *pad = " [ "; 
       for (j = 0; j < n; j++) { 
        printf (fmt, pad, mtrx[i][j]); 
        pad = ", "; 
       } 
       printf ("%s", " ]\n"); 
      } 
     } 
     else 
      goto err; 
    } 
    else {  /* integer (no unsigned yet) */ 

     if (sz == 1) { 
      char **mtrx = (char **)matrix; 
      for (i = 0; i < m; i++) { 
       char *pad = " [ "; 
       for (j = 0; j < n; j++) { 
        printf (fmt, pad, mtrx[i][j]); 
        pad = ", "; 
       } 
       printf ("%s", " ]\n"); 
      } 
     } 
     else if (sz == 2) { 
      short **mtrx = (short **)matrix; 
      for (i = 0; i < m; i++) { 
       char *pad = " [ "; 
       for (j = 0; j < n; j++) { 
        printf (fmt, pad, mtrx[i][j]); 
        pad = ", "; 
       } 
       printf ("%s", " ]\n"); 
      } 
     } 
     else if (sz == 4) { 
      int **mtrx = (int **)matrix; 
      for (i = 0; i < m; i++) { 
       char *pad = " [ "; 
       for (j = 0; j < n; j++) { 
        printf (fmt, pad, mtrx[i][j]); 
        pad = ", "; 
       } 
       printf ("%s", " ]\n"); 
      } 
     } 
     else if (sz == 8) { 
      long **mtrx = (long **)matrix; 
      for (i = 0; i < m; i++) { 
       char *pad = " [ "; 
       for (j = 0; j < n; j++) { 
        printf (fmt, pad, mtrx[i][j]); 
        pad = ", "; 
       } 
       printf ("%s", " ]\n"); 
      } 
     } 
     else 
      goto err; 
    } 

    return; 

err:; 

    fprintf (stderr, "%s() error: invalid size for fp_flag '%zu'.\n", 
       __func__, sz); 
} 

Как указывается в комментариях, декларация/определение обновлена:

void mtrx_prnv (size_t m, size_t n, void *matrix, 
       size_t sz, char *fmt, char fp); 

Macro Предложенного решение

Существовало несколько превосходных решений предложили, в первую очередь устранить дословное дублирование создание макроса для избыточного текста, а во-вторых, использование обратных вызовов для печати значений элементов. Макрос предусмотрен вблизи капли в растворе:

#define PRINT_MATRIX(type) do { \ 
    type **mtrx = (type **)matrix; \ 
    for (i = 0; i < m; i++) { \ 
     char *pad = " [ "; \ 
     for (j = 0; j < n; j++) { \ 
      printf (fmt, pad, mtrx[i][j]); \ 
      pad = ", "; \ 
     } \ 
     printf ("%s", " ]\n"); \ 
    } \ 
} while (0) 

... 

void mtrx_prnvm (size_t m, size_t n, void *matrix, 
       size_t sz, char *fmt, char fp) 
{ 
    register size_t i, j; 

    if (fp) { /* floating point */ 

     if (sz == 4) { 
      PRINT_MATRIX(float); 
     } 
     else if (sz == 8) { 
      PRINT_MATRIX(double); 
     } 
     else 
      goto err; 
    } 
    else {  /* integer (no unsigned yet) */ 

     if (sz == 1) { 
      PRINT_MATRIX(char); 
     } 
     else if (sz == 2) { 
      PRINT_MATRIX(short); 
     } 
     else if (sz == 4) { 
      PRINT_MATRIX(int); 
     } 
     else if (sz == 8) { 
      PRINT_MATRIX(long); 
     } 
     else 
      goto err; 
    } 

    return; 

err:; 

    fprintf (stderr, "%s() error: invalid size for fp_flag '%zu'.\n", 
       __func__, sz); 
} 
+3

Тип аргумента 'matrix' должен действительно быть просто' void * ', а не' void ** ', если вы хотите, чтобы этот код был правильно переносимым. Изменение этого не должно иметь какого-либо заметного эффекта, так что вы тоже можете это сделать. – davmac

+0

Как сказал @davmac. Любое значение указателя объекта можно безопасно округлить с помощью преобразования в void *, но это не обязательно означает, что 'void *' имеет то же * представление *, что и любой другой тип указателя. В результате, хотя листинг 'void *' to 'other_type **' является безопасным (-ish), литье 'void **' в 'other_type **' не является * безопасным. –

+0

Это имеет смысл. Я всегда могу получить свой 'other_type **' обратно, если матрица 'void *' –

ответ

0

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

#define PRINT_MATRIX(type) do { \ 
    (type) **mtrx = ((type) **)matrix; 
    for (i = 0; i < m; i++) { \ 
     char *pad = " [ "; \ 
     for (j = 0; j < n; j++) { \ 
      printf (fmt, pad, mtrx[i][j]); \ 
      pad = ", "; \ 
     } \ 
     printf ("%s", " ]\n"); \ 
    } \ 
} while (0) 

void mtrx_prnv (size_t m, size_t n, void *matrix, 
       size_t sz, char *fmt, char fp) 
{ 
    if (fp) { // floating point 
     if (sz == 4) { 
      PRINT_MATRIX(float); 
     } 
     else if (sz == 8) { 
      PRINT_MATRIX(double); 
     } 
    } 
    else { 
     if (sz == 1) { 
      PRINT_MATRIX(char); 
     } 
     else if (sz == 2) { 
      PRINT_MATRIX(short); 
     } 
     ... 
    } 
} 
+0

Джон, спасибо. Ваше предложение макроса привело к полному набору макросов для печати, добавления, умножения, транспонирования, transpose_in_place и т. Д. Работал как чемпион и фактически приводил к общему шаблону (аналогичному исходной схеме) для любой операции матричного типа, которая просто обрабатывает условный размер и флаг с плавающей запятой и вызывает макрос для обработки отдельного типа. Дублирование кода - прошло. –

-1

Самым простым решением является, вероятно, чтобы определить макрос, который при вызове определяет функцию для определенного типа (но я лично до сих пор склонны просто написать отдельные функции для каждый тип, макросы всегда чувствовали себя дешевым хакером для меня). Что-то вроде:

#define define_matrix_print(type,fmt) \ 
    void mtrx_prn_##type(size_t m, size_t n, type **matrix) { \ 
     for (int i = 0; i < m; i++) { \ 
      char *pad = " [ "; \ 
      for(int j = 0; j < n; j++) { \ 
       printf(fmt, pad, matrix[i][j]); \ 
       pad = ", "; \ 
      } \ 
      printf(" ]\n"); \ 
     } \ 
    } 

define_matrix_print(char, "%c") 
/* above defines 'mtrx_prn_char(size_t, size_t, char **)' */ 
define_matrix_print(int, "%d") 
/*    'mtrx_prn_int(size_t, size_t, int **)' */ 
/* and so on.           */ 

Другая возможность состоит в том, чтобы использовать указатель на функцию, что-то вроде:

void (*print_func)(void *mtrx, int i, int j, char *fmt, char *pad); 
switch (sz) { 
    case 1: 
     print_func = print_char_mtrx; 
     break; 
    case 2: 
     print_func = print_short_mtrx; 
     break; 
    // others omitted for brevity 
} 
for (i = 0; i < m; i++) { 
    char *pad = " [ "; 
    for (j = 0; j < n; j++) { 
     print_func(matrix, i, j, fmt, pad); 
     pad = ", "; 
    } 
    printf ("%s", " ]\n"); 
} 

Тогда вы заявляете:

void print_char_mtrx(void *mtrx, int i, int j, char *fmt, char *pad) 
{ 
    char ** mtrx_c = mtrx; 
    printf(fmt, pad, mtrx_c[i][j]); 
} 

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

+0

Да, здесь шаблоны облегчают жизнь, но, к счастью, к сожалению, мы только C. Идея макроса, вероятно, будет лучшим вариантом. Это ограничило бы дублирование и в основном уменьшало бы функцию до логики, необходимой для определения типа. –

+0

@ DavidC.Rankin действительно, если вы застряли с C, макросы - это, наверное, путь сюда ** содрогнуться ** :) – davmac

0

Это немного пахнет проблемой XY, и дублирование кода не слишком свежо. Поэтому я рекомендую использовать другую стратегию. Вместо этого используйте обратные вызовы.

Функция mtrx_prnv выполняет итерацию по всем индексам, но использует предоставленный обратный вызов для печати фактических значений. Обратный вызов получает индексы и матрицу как void * (точно так же, как и mtrx_prnv), но преобразует его в соответствующий тип и печатает значение.

void mtrx_prnv(size_t n, size_t m, void * mtrx, void (*prncb)(size_t i, size_t j, void * mtrx)){ 
    size_t i, j; 
    for (i = 0; i < m; i++) { 
     printf(" [ "); 
     for (j = 0; j < n; j++) { 
      prncb(i, j, mtrx); /* Use callback to print value */ 
      printf(", "); /* fix the trailing comma yourself ;) */ 
     } 
     printf (" ]\n"); 
    } 
} 

Пример обратного вызова для int с и float s:

void prnint(size_t i, size_t j, void * vmat){ 
    int ** mtrx = vmat; 
    printf("%d", mtrx[i][j]); 
} 

void prnflt(size_t i, size_t j, void * vmat){ 
    float ** mtrx = vmat; 
    printf("%f", mtrx[i][j]); 
} 

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

Для удобства чтения можно typedef функцию указателя:

typedef void (prncb_f)(size_t i, size_t j, void * mtrx); 

И изменить mtrx_prnv:

void mtrx_prnv(size_tn, size_t m, void * mtrx, prncb_f * cb) 

Addit

Если вам нужно остаться с подписью mtrx_prnv вы могли бы просто выберите логику выбора и пропустите Обратные вызовы:

(Переименовать mtrx_prnv из прежде чем mtrx_prnv_helper)

void mtrx_prnv (size_t m, size_t n, void *matrix, 
      size_t sz, char *fmt, char fp){ 
    if(fp){ 
     if(sz == sizeof(float)){ 
      mtrx_prnv_helper(m, n, matrix, prnflt); 
     }else if(sz == sizeof(double)){ 
      ... 
     } 
    }else{ 
     if(sz == sizeof(int)){ 
      mtrx_prnv_helper(m, n, matrix, prnint); 
     }else{ 
      ... 
     } 
    } 
} 

Но вы потеряете потенциал для других типов данных в матрице.

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

+1

Мне это нравится. Чисто. Несколько оговорок, которые я вижу, - это то, что вызовы функций сравнительно дороги и что компилятор не может оптимизировать вызовы косвенных функций.В маловероятном случае, когда производительность этой функции вывода была важна, зависимость от обратных вызовов могла бы привести к неприемлемым накладным расходам. –

+1

@JohnBollinger - действительная точка, хотя в этом конкретном случае I/O, вероятно, будет намного большим (более узким?) Узким местом. – Kninnug

+0

Потребовалось минутку, чтобы переварить, но я согласен. Я рассмотрел обратный вызов, но обернулся вокруг оси, пытаясь подумать о том, как сделать все это разделение, а не использовать несколько обратных вызовов. Это может также привести к обертке для всего 'mtrx_prnv', целью которого является взять размер и fp_flag и вызвать' mtrx_prn' с надлежащим обратным вызовом. Некоторые из функций селектора, которые могут принимать указатель на массив указателей функций, содержащих обратные вызовы, просеивать логику размера/fp_flag и передавать надлежащий ответ на функцию печати. –

-2

После реализации позволяет избежать дублирования при использовании переменной буфера.

До тех пор, пока это последний аргумент printf, любая длина может поддерживаться одинаково. t должен быть самого большого типа.

void mtrx_prnv(size_t m, size_t n, void *matrix, 
       size_t sz, char *fmt, char fp) 
{ 
    size_t i, j; 

    if (sz != 4 && sz != 8 && (fp || (sz != 1 && sz != 2))) { 
     fprintf(stderr, "%s() error: invalid size for fp_flag '%zu'.\n", 
      __func__, sz); 
     return; 
    } 

    for (i = 0; i < m; i++) { 
     char *p = ((char **)matrix)[i]; 
     double t; 

     memcpy(&t, p, sz); 
     printf(fmt, " [ ", t); 
     for (j = sz; j < n * sz; j += sz) { 
      memcpy(&t, p + j, sz); 
      printf(fmt, ", ", t); 
     } 
     printf(" ]\n"); 
    } 
} 

Использование memcpy вместо прямого броска, чтобы избежать угловых случаев, как большее значение, будучи на границе между двумя страницами, которые могут вызвать Segfault.Он может быть оптимизирован для использования условно и только в последнем элементе каждой страницы.

+1

Проблема в том, что вы не можете разыменовать 'void *', поэтому вы не можете передать определенное значение/ячейку в матрице 'printf'. 'matrix [i] [j]' является незаконным и '(char **) matrix) [i] [j]' работает только тогда, когда матрица состоит из 'char'. – Kninnug

+1

№ 'printf()', являющийся Variadic, не делает так, чтобы это делалось правильно: '((char **) matrix) [i] [j]'. Это выражение оценивается перед вызовом, а затем применяется поощрение аргументов по умолчанию. Результат правильный, только если фактический базовый тип - 'char'. Более того, это не касается основной проблемы OP, которая заключалась в том, чтобы избежать дублирования кода. –

+0

Я отправил его случайно перед отделкой. –

-1

Хотя на вопрос уже дан ответ, я хотел показать альтернативу. Это также использует макрос, чтобы упростить код, но он используется в цикле строк (поскольку печать всего, даже одного номера, занимает гораздо больше времени, чем условные обозначения). Кроме того, параметры функции немного отличаются.

#include <stdio.h> 

typedef enum { 
    UNSIGNED_CHAR, 
    SIGNED_CHAR, 
    UNSIGNED_SHORT, 
    SIGNED_SHORT, 
    UNSIGNED_INT, 
    SIGNED_INT, 
    UNSIGNED_LONG, 
    SIGNED_LONG, 
    FLOAT, 
    DOUBLE 
} element_type; 

void fmatrix(FILE *const  out, 
      void *const  origin, 
      const int   rows, 
      const int   cols, 
      const long   rowstride, 
      const long   colstride, 
      const element_type type, 
      const int   decimals) 
{ 
    int row, col; 

    for (row = 0; row < rows; row++) { 

     switch (type) { 
#define PRINTROW(type, argtype, spec) \ 
      do { \ 
       const unsigned char *const data = (unsigned char *)origin + row * rowstride; \ 
       for (col = 0; col < cols - 1; col++) \ 
        fprintf(out, spec ", ", decimals, (argtype)(*(const type *)(data + col * colstride))); \ 
       fprintf(out, spec "\n", decimals, (argtype)(*(const type *)(data + (cols - 1) * colstride))); \ 
      } while (0) 

     case UNSIGNED_CHAR: PRINTROW(unsigned char, unsigned int, "%*u"); break; 
     case UNSIGNED_SHORT: PRINTROW(unsigned short, unsigned int, "%*u"); break; 
     case UNSIGNED_INT: PRINTROW(unsigned int, unsigned int, "%*u"); break; 
     case SIGNED_CHAR: PRINTROW(signed char, int,   "%*d"); break; 
     case SIGNED_SHORT: PRINTROW(signed short, int,   "%*d"); break; 
     case SIGNED_INT:  PRINTROW(int,   int,   "%*d"); break; 
     case UNSIGNED_LONG: PRINTROW(unsigned long, unsigned long, "%*lu"); break; 
     case SIGNED_LONG: PRINTROW(long,   long,   "%*ld"); break; 
     case FLOAT:   PRINTROW(float,   double,  "%.*f"); break; 
     case DOUBLE:   PRINTROW(double,   double,  "%.*f"); break; 

#undef PRINTROW 
     } 
    } 
} 

origin указывает на элемент в верхнем левом углу матрицы. Матрица имеет rows строк и cols столбцов.

colstride и rowstride определяют количество байтов между последовательными членами в матрице. Для стандартных C-массивов rowstride = cols * colstride и colstride = sizeof (type) для матричных элементов типа type.

Если вы хотите выход транспонирования, просто поменять местами rows и cols и rowstride и colstride.

eltype определяет тип элемента, и decimals число десятичных цифр для FLOAT и DOUBLE типов (по умолчанию 6), или минимальную ширину для целочисленных типов. Используйте -1, если вы не хотите указывать (поскольку отрицательная точность обрабатывается так, как если бы она была опущена).

В качестве практического примера,

float m[2][3] = { { 1, 2, 3 }, { 4, 5, 6 }}; 

fmatrix(stdout, m, 2, 3, 3 * sizeof m[0][0], sizeof m[0][0], FLOAT, 2); 

выходы

1.00, 2.00, 3.00 
4.00, 5.00, 6.00 

и

fmatrix(stdout, m, 3, 2, sizeof m[0][0], 3 * sizeof m[0][0], FLOAT, 1); 

выходы

1.0, 4.0 
2.0, 5.0 
3.0, 6.0 

Макрос PRINTROW Я использовал немного уродливый и сложный (его можно было бы написать лучше), но это должен быть полностью переносимый код, тщательно написанный, чтобы явно делать правильные броски/рекламные акции, поэтому, надеюсь, это достаточно ясно, чтобы понять.

+0

Спасибо. У меня есть аналогичная процедура для последовательных 1D-массивов в памяти, которая позволяет представить все допустимые комбинации строк x cols для каждого допустимого шага (rowstrde), учитывая общее количество элементов. То, что я не делал там, включает в себя colstride, чтобы сделать его общим для типа. То, что вызвало этот вопрос, было подпрограммой ввода для гибкого считывания заданного файла, содержащего значения с любым разделителем, открывшего отверстие для кроликов для гибкой обработки любого типа ввода данных. Спасибо за ваши предложения. –

+0

@ DavidC.Rankin: Я обнаружил, что накладные расходы на наличие множителя для строки и номера столбца уравновешиваются гибкостью, которую он предоставляет. Для хранения фактических данных я использую отдельную структуру «владелец» с подсчетом ссылок, с матрицами, имеющими указатель как на владельца, так и на указатель на исходный элемент. Это позволяет просматривать в реальном времени (транспозиции, подматрицы, векторы строк, векторы столбцов, даже диагональные векторы, зеркальные векторы и т. Д.) С очень простым управлением динамической памятью. Дайте мне знать, если вы заинтересованы в деталях. –