printf'ing a matrix
I'm trying to implement an all-purpose function for printing 2D data. What I've come up with is:开发者_C百科
int mprintf(FILE* f, char* fmt, void** data, size_t cols, size_t rows)
The challenge is determining how many bits to read at once from data
, based on fmt
.
The format fmt
is going to be the stdlib's-specific format for printf()
and alike.
Do you have any knowledge of already-existing features from stdlibc (GNU GCC C's) I could use to ease this up?
I try avoiding having to do it all manually, because I know "I am stupid" (I don't want to introduce stupid bugs). Thus, reusing code would be the bug-freest way.
Thanks
Addendum
I see there's a /usr/include/printf.h
. Can't I use any of those functions to do it right and ease my job at the same time?
Design proposed in question:
int mprintf(FILE *f, char *fmt, void **data, size_t cols, size_t rows);
High-level design points
- If you want to print a 4x4 section of an 8x8 matrix, you need to know the row length of the matrix as well as the size to print. Or you may prefer to have that as a separate function.
- Presumably, the format will define the separation between matrix entries, or will you force a space between them, or what? (If the user specifies "%d", will the numbers all be joined together?)
- You're implicitly assuming that the matrix will be printed by itself, left-justified on the page. How would you adapt the interface to print the matrix elsewhere? Leading spaces on the line? Text before each line of the matrix? Text after line of the matrix?
Low-level design points
The format string should be a
const char *
.Clearly, your code can do what
printf()
does, more or less. It looks at the format conversion specifier, and then determines what type to collect. Your code will be slightly more complex, in some respects. You'll need to treat an array ofunsigned char
differently from an array ofshort
, etc. C99 provides for modifierhh
forsigned char
orunsigned char
(before the format specifiersd
,i
,o
,u
,x
, orX
), and the modifierh
forshort
orunsigned short
. You should probably recognize these too. Similarly, the modifiersL
forlong double
andl
forlong
andll
forlong long
should be handled. Interestingly,printf()
does not have to deal withfloat
(because any singlefloat
value is automatically promoted todouble
), but your code will have to do that. By analogy withh
andL
, you should probably useH
as the modifier to indicate afloat
array. Note that this case means you will need to pass to theprintf()
function a different format from the one specified by the user. You can make a copy of the user-provided format, dropping the 'H' (or use exactly the user-provided format except when it contains the 'H'; you will not modify the user's format string - not least because the revised interface says it is a constant string).Ultimately, your code will have to determine the size of the elements in the array. It might be that you modify the interface to include that information - by analogy with functions such as
bsearch()
andqsort()
, orfread()
andfwrite()
. Or you can determine it from the format specifier.Note that although GCC allows pointer arithmetic on
void *
, Standard C does not.Are you sure you want a
void **
in the interface? I think it would be easier to understand if you pass the address of the starting element of the array - a single level of pointer.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);
This changes the
data
parameter to avoid *
. Maybe I'm too decaffeinated, but I can't see how to make a double pointer work sanely.
Outline
- Determine size of elements and correct format string.
- For each row
- For each column
- Find the data for the element
- Call an appropriate function to print it
- Print a separator if you need to
- Print a newline
Illustration
This code assumes a '0 is success' convention. It assumes you are dealing with numbers, not matrices of pointers or strings.
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...
}
Working code
Left as an exercise:
- Pointers
- Strings
size_t
intmax_t
ptrdiff_t
Note that I would not normally use the +=
or *=
operators on the same line as other assignments; it was convenient for generating test numbers, though.
#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 */
Assuming I understood your requirements and assuming fmt specifies how to format only one element (it could be extended so that it is not something you can pass to (f)printf directly, but a description of how to print the whole matrix - which I think is more useful), you simply have to do some easy string manipulation to find which type the data is, thus deciding how to cast your void** pointer (look here).
The cases won't be infinite but only restricted on how many data types you wish to support.
Once you casted the pointer, a simple for loop based on cols and rows will do the trick.
There is no means to do this that is universally applicable, it all depends on HOW you want it displayed. However, the following is pretty general and you can adapt it if you need it changed slightly:
int mprintf(FILE* file, char* fmt, int** data, size_t cols, size_t rows) {
for(int r=0; r<rows; r++) {
for(int c=0; c<cols; c++) {
fprintf(file, fmt, data[r][c]); // fmt can be e.g. "%9i"
}
printf("\n");
}
}
You can't determine the argument type from the printf format string. OP's problem is that he wants to print like printf, but consume arguments by pointer like scanf. Unfortunately, the printf conversion specifiers are insufficient for the 'scanf' side of the task. Consider "%f", which printf uses for floats or doubles. The argument conversion rules for variadic functions mean that printf always sees doubles and doesn't care, but in this case scanf needs "%f" and "%lf" to distinguish them. Nor can the OP try to use scanf versions of the specifiers, as this will break printf. Neither is a subset of the other.
Your generic function needs to be told at least both the format conversion specifier and how big the elements are to do the right pointer arithmetic. (In fact, if you are going to delegate to fprintf and friends, the size is insufficient - you will need to know the type at the fprintf call site). I would probably pass in a function rather than two parameters to avoid the risk of mismatch between the two, thus:
#include <stdio.h>
typedef const void *(*Printfunc)(FILE *f, const void *datum);
/* print an integer and advance the pointer */
static const void* print_int(FILE *f, const void *datum)
{
const int* p = datum;
fprintf(f, "%d", *p);
return p + 1;
}
/* print a char and advance the pointer */
static const void* print_char(FILE *f, const void *datum)
{
const char* p = datum;
fprintf(f, "%c", *p);
return p + 1;
}
static void mprint(FILE *f, Printfunc p, const void *data, size_t cols, size_t rows)
{
const void *next = data;
int i;
for (i = 0; i < rows; ++i) {
int j;
for (j = 0; j < cols; ++j) {
next = p(f, next);
putc(' ', f);
}
putc('\n', f);
}
}
int main()
{
int imatrix[3][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 } };
char cmatrix[2][2] = { { 'a', 'b' }, { 'c', 'd' } };
mprint(stdout, print_int, imatrix, 2, 3);
mprint(stdout, print_char, cmatrix, 2, 2);
return 0;
}
I think by typecasting the void **data based upon the *fmt would do the trick. I am not sure if I understand your question correctly. Because you can you can put a switch-case statement based upon *fmt for typcasting, and then use **data as 2-D array to print it. Use rows/columns as index of the 2-D array.
精彩评论