[英]How to Pass (void**) to Function and Efficiently Dereference/Use for Any type?
我希望通过一个简单的函数来打印相似的2D矩阵,我只是希望能够传递一个指向该函数的指针数组作为void**
,以及所需的尺寸mxn
, sizeof type
, printf
的格式字符串以及一个用于将浮点数与integer区别的简单标志。 我遇到的问题是处理范围以取消对指针数组的引用,以便可以将每个元素正确地打印为原始类型。 下面是基本方案:
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类型以及需要其他任何标志来正确取消引用类型而无需太多代码重复的类型? 如果这是必须的方法,那很好,但是我想确保我没有错过一个简单的窍门。 到目前为止,我的搜索没有产生任何有用的信息(搜索返回的绝大多数信息是关于如何取回单个类型,而不是将所有类型分开)。 如何编写接受任何类型的(一个)参数的C函数都不是问题。
完整功能(不包含用于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);
宏提供的解决方案
建议了几种出色的解决方案,主要是通过为冗余文本创建宏来消除逐字重复,其次是使用回调来打印元素的值。 宏提供了几乎下降的解决方案:
#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);
}
这闻起来有点像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
的示例回调:
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)
加法
如果您需要保留mtrx_prnv
的签名, 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{
...
}
}
}
但是,您将失去矩阵中其他数据类型的潜力。
当然,为矩阵中的每个单元调用回调都有一些函数调用开销,但是在这种情况下,它很可能被打印的I / O开销所掩盖。
考虑到您要逐字重复每个块的大部分内容,一个显而易见的解决方案是定义并使用一个表示该代码的宏(与另一个用于生成每个类型函数的宏相反)。 这将限制物理代码的重复,并且不需要您更改程序的结构:
#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);
}
...
}
}
尽管问题已经回答,但我想显示一个替代方法。 这也使用了宏来简化代码,但是它在行循环中使用(因为打印任何内容,即使是单个数字,也要比条件打印花费更长的时间)。 此外,功能参数略有不同。
#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数组,对于type type
矩阵元素, rowstride = cols * colstride
,而colstride = sizeof (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
宏有点丑陋和复杂(可以写得更好),但这应该是完全可移植的代码,经过精心编写以明确执行正确的强制转换/促销,因此希望它足够清晰易懂。
最简单的解决方案可能是定义一个宏,该宏在被调用时为指定的类型定义一个函数(但是我个人仍然倾向于为每种类型编写单独的函数;对我而言,宏总是感觉很便宜)。 就像是:
#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]);
}
...以此类推。 但是,这种方式只节省了一点打字/复制操作。 当然,您可能会选择结合两种技术并定义一个宏,您可以使用该宏来定义各种功能,以从各种类型的矩阵中打印元素。
下面的实现通过使用缓冲区变量来避免重复。
只要它是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
而不是直接强制转换是为了避免出现极端情况,例如较大的值出现在两个页面之间的边界中,这可能会导致段错误。 可以对其进行优化以有条件地使用,并且仅在每个页面的最后一个元素中使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.