Дизайн, предложенный в вопросе:
int mprintf(FILE *f, char *fmt, void **data, size_t cols, size_t rows);
Точки проектирования высокого уровня
- Если вы хотите напечатать секцию 4x4 матрицы 8x8, вам нужно знать строкудлина матрицы, а также размер для печати.Или вы можете предпочесть, чтобы это было отдельной функцией.
- Предположительно, формат будет определять разделение между элементами матрицы, или вы пробьете пробел между ними, или как?(Если пользователь указывает «% d», все ли числа будут объединены?)
- Вы неявно предполагаете, что матрица будет напечатана сама по себе, с выравниванием по левому краю на странице.Как бы вы адаптировали интерфейс для печати матрицы в другом месте?Ведущие места на линии?Текст перед каждой строкой матрицы?Текст после строки матрицы?
Точки проектирования низкого уровня
Строка формата должна быть const char *
.
Очевидно, ваш код может делать то, что делает printf()
, более или менее.Он смотрит на спецификатор преобразования формата, а затем определяет, какой тип собирать.Ваш код будет немного сложнее, в некоторых отношениях.Вам нужно будет трактовать массив unsigned char
иначе, чем массив short
и т. Д. C99 предусматривает модификатор hh
для signed char
или unsigned char
(перед спецификаторами формата d
, i
, o
, u
, x
или X
) и модификатор h
для short
или unsigned short
.Вы, вероятно, должны признать это тоже.Аналогично, модификаторы L
для long double
и l
для long
и ll
для long long
должны обрабатываться.Интересно, что printf()
не должен иметь дело с float
(потому что любое одиночное значение float
автоматически повышается до double
), но ваш код должен будет это сделать.По аналогии с h
и L
вы, вероятно, должны использовать H
в качестве модификатора для указания массива float
.Обратите внимание, что этот случай означает, что вам нужно будет передать функции printf()
формат, отличный от указанного пользователем.Вы можете сделать копию предоставленного пользователем формата, опуская 'H' (или использовать точно предоставленный пользователем формат, кроме случаев, когда он содержит 'H'; вы не будете изменять строку формата пользователя - не в последнюю очередь потому, что пересмотренный интерфейсговорит, что это постоянная строка).
В конечном счете, ваш код должен определить размер элементов в массиве.Возможно, вы измените интерфейс для включения этой информации - по аналогии с такими функциями, как bsearch()
и qsort()
или fread()
и fwrite()
.Или вы можете определить его по спецификатору формата.
Обратите внимание, что хотя GCC допускает арифметику указателей на void *
, стандарт C не позволяет.
Вы уверены, что хотите void **
в интерфейсе?Я думаю, что было бы легче понять, если вы передадите адрес начального элемента массива - один уровень указателя.
short s[3][4];
float f[2][5];
char c[20][30];
mprintf(fp, "%3hd", &s[0][0], 4, 3);
mprintf(fp, "%8.4Hf", &f[0][0], 5, 2);
mprintf(fp, "%hhu", &c[0][0], 30, 20);
Изменяет параметр data
на void *
.Может быть, у меня нет кофеина, но я не понимаю, как заставить работать двойной указатель разумно.
Outline
- Определить размер элементов и исправитьстрока формата.
- Для каждой строки
- Для каждого столбца
- Найти данные для элемента
- Вызвать соответствующую функцию, чтобы напечатать ее
- Напечатайте разделитель, если вам нужно
- Напечатайте новую строку
Иллюстрация
В этом коде предполагается, что соглашение «0 успешно»,Предполагается, что вы имеете дело с числами, а не с матрицами указателей или строк.
typedef int (*PrintItem)(FILE *fp, const char *format, void *element);
static int printChar(FILE *fp, const char *format, void *element)
{
char c = *(char *)element;
return (fprintf(fp, format, c) <= 0) ? -1 : 0;
}
...and a whole lot more like this...
static int printLongDouble(FILE *fp, const char *format, void *element)
{
long double ld = *(long double *)element;
return (fprintf(fp, format, ld) <= 0) ? -1 : 0;
}
int mprintf(FILE *fp, const char *fmt, void *data, size_t cols, size_t rows)
{
char *format = strdup(fmt);
int rc = 0;
size_t size;
PrintItem print;
if ((rc = print_info(format, &size, &print)) == 0)
{
for (size_t i = 0; i < rows; i++)
{
for (size_t j = 0; j < cols; j++)
{
void *element = (char *)data + (i * cols + j) * size;
if ((rc = print(fp, format, element)) < 0)
goto exit_loop;
}
fputc('\n', fp); // Possible error ignored
}
}
exit_loop:
free(fmt);
return rc;
}
static int print_info(char *fmt, size_t *size, PrintItem *print)
{
...analyze format string...
...set *size to the correct size...
...set *print to the correct printing function...
...modify format string if need so be...
...return 0 on success, -1 on failure...
}
Рабочий код
Оставлено как упражнение:
- Указатели
- Строки
size_t
intmax_t
ptrdiff_t
Обратите внимание, что я обычно не буду использоватьоператоры +=
или *=
в той же строке, что и другие назначения;это было удобно для генерации тестовых чисел.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
/* mprintf() - print a matrix of size cols x rows */
extern int mprintf(FILE *fp, const char *fmt, void *data, size_t cols, size_t rows);
typedef int (*PrintItem)(FILE *fp, const char *format, void *element);
static int printChar(FILE *fp, const char *format, void *element)
{
char value = *(char *)element;
return (fprintf(fp, format, value) <= 0) ? -1 : 0;
}
static int printShort(FILE *fp, const char *format, void *element)
{
short value = *(short *)element;
return (fprintf(fp, format, value) <= 0) ? -1 : 0;
}
static int printInt(FILE *fp, const char *format, void *element)
{
int value = *(int *)element;
return (fprintf(fp, format, value) <= 0) ? -1 : 0;
}
static int printLong(FILE *fp, const char *format, void *element)
{
long value = *(long *)element;
return (fprintf(fp, format, value) <= 0) ? -1 : 0;
}
static int printLongLong(FILE *fp, const char *format, void *element)
{
long long value = *(long long *)element;
return (fprintf(fp, format, value) <= 0) ? -1 : 0;
}
static int printFloat(FILE *fp, const char *format, void *element)
{
float value = *(float *)element;
return (fprintf(fp, format, value) <= 0) ? -1 : 0;
}
static int printDouble(FILE *fp, const char *format, void *element)
{
double value = *(double *)element;
return (fprintf(fp, format, value) <= 0) ? -1 : 0;
}
static int printLongDouble(FILE *fp, const char *format, void *element)
{
long double valued = *(long double *)element;
return (fprintf(fp, format, valued) <= 0) ? -1 : 0;
}
/* analyze format string - all arguments can be modified */
static int print_info(char *format, size_t *size, PrintItem *print)
{
char *fmt = format;
char c;
bool scanning_type = false;
int hcount = 0;
int lcount = 0;
int Hcount = 0;
int Lcount = 0;
char *Hptr = 0;
while ((c = *fmt++) != '\0')
{
switch (c)
{
case '%':
if (*fmt == '%')
fmt++;
else
scanning_type = true;
break;
/* Length modifiers */
case 'h':
if (scanning_type)
hcount++;
break;
case 'l':
if (scanning_type)
lcount++;
break;
case 'L':
if (scanning_type)
Lcount++;
break;
case 'H':
if (scanning_type)
{
Hptr = fmt - 1;
Hcount++;
}
break;
/* Integer format specifiers */
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
if (scanning_type)
{
/* No floating point modifiers */
if (Hcount > 0 || Lcount > 0)
return -1;
/* Can't be both longer and shorter than int at the same time */
if (hcount > 0 && lcount > 0)
return -1;
/* Valid modifiers are h, hh, l, ll */
if (hcount > 2 || lcount > 2)
return -1;
if (hcount == 2)
{
*size = sizeof(char);
*print = printChar;
}
else if (hcount == 1)
{
*size = sizeof(short);
*print = printShort;
}
else if (lcount == 2)
{
*size = sizeof(long long);
*print = printLongLong;
}
else if (lcount == 1)
{
*size = sizeof(long);
*print = printLong;
}
else
{
*size = sizeof(int);
*print = printInt;
}
return 0;
}
break;
/* Floating point format specifiers */
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
case 'a':
case 'A':
if (scanning_type)
{
/* No integer modifiers */
if (lcount > 0 || hcount > 0)
return -1;
/* Can't be both float and long double at once */
if (Lcount > 0 && Hcount > 0)
return -1;
/* Cannot repeat L or H modifiers */
if (Lcount > 1 || Hcount > 1)
return -1;
if (Lcount > 0)
{
*size = sizeof(long double);
*print = printLongDouble;
}
else if (Hcount > 0)
{
/* modify format string, dropping the H */
assert(Hptr != 0 && strlen(Hptr+1) > 0);
memmove(Hptr, Hptr+1, strlen(Hptr)); // Copy '\0' too!
*size = sizeof(float);
*print = printFloat;
}
else
{
*size = sizeof(double);
*print = printDouble;
}
return 0;
}
break;
default:
break;
}
}
return -1;
}
int mprintf(FILE *fp, const char *fmt, void *data, size_t cols, size_t rows)
{
char *format = strdup(fmt); // strdup() is not standard C99
int rc = 0;
size_t size;
PrintItem print;
if ((rc = print_info(format, &size, &print)) == 0)
{
for (size_t i = 0; i < rows; i++)
{
for (size_t j = 0; j < cols; j++)
{
void *element = (char *)data + (i * cols + j) * size;
if ((rc = print(fp, format, element)) < 0)
{
fputc('\n', fp); // Or fputs("<<error>>\n");
goto exit_loop;
}
}
fputc('\n', fp); // Possible error ignored
}
}
exit_loop:
free(format);
return rc;
}
#ifdef TEST
int main(void)
{
short s[3][4];
float f[2][5];
char c[8][9];
FILE *fp = stdout;
int v = 0;
for (size_t i = 0; i < 3; i++)
{
for (size_t j = 0; j < 4; j++)
{
s[i][j] = (v += 13) & 0x7FFF;
printf("s[%zu,%zu] = %hd\n", i, j, s[i][j]);
}
}
v = 0;
for (size_t i = 0; i < 8; i++)
{
for (size_t j = 0; j < 9; j++)
{
c[i][j] = (v += 13) & 0x7F;
printf("c[%zu,%zu] = %hhu\n", i, j, c[i][j]);
}
}
float x = 1.234;
for (size_t i = 0; i < 2; i++)
{
for (size_t j = 0; j < 5; j++)
{
f[i][j] = x *= 13.12;
printf("f[%zu,%zu] = %g\n", i, j, f[i][j]);
}
}
mprintf(fp, " %5hd", &s[0][0], 4, 3);
mprintf(fp, "%%(%3hhu) ", &c[0][0], 8, 9);
mprintf(fp, " %11.4He", &f[0][0], 5, 2);
mprintf(fp, " %11.4He", f, 5, 2);
return 0;
}
#endif /* TEST */