替换嵌套 for 循环的宏
Macro to replace nested for loops
我发现这个宏#define TIMES(x) for(int i1=0;i1<x;i1++)
对于缩短代码文本非常实用。但是当我有嵌套循环时,我不知道如何编写这样的宏,甚至不知道是否可能。这个想法如下。是否可以编写此代码
for(int i1=0;i1<5;i1++)
for(int i2=0;i2<3;i2++)
for (int i3=0;i3<7;i3++)
/* many nested `for` loops */
{
/* some code, for example to print an array printf("%d \n",a[i1][i2][i3]) */
}
作为
TIMES(5) TIMES(3) TIMES(7) ....
{
/* some code, for example to print an array printf("%d \n",a[i1][i2][i3]) */
}
使用一种 "recursive" 宏来检测所有 TIMES
并将它们替换为具有 i1、i2、i3、... i'n' 循环计数器的 for
循环?
这是非常糟糕的做法,请不要这样做。其他 C 程序员非常了解 for 循环,但他们完全没有注意到您私有的、秘密的宏语言。此外,像这样的函数式宏的类型安全性很差,只能作为最后的手段使用。
正确的解决方法不是使用宏,而是使用函数。如果你想使用适当的泛型编程,你可以这样写:
typedef void callback_t (int data);
void traverse (size_t n, int data[n], callback_t* callback)
{
for(size_t i=0; i<n; i++)
{
callback(data[i]);
}
}
其中callback
是调用者提供的函数指针,包含了实际的功能。类似于宏中的循环体。
完整示例:
#include <stdio.h>
typedef void callback_t (int data);
void traverse (size_t n, int data[n], callback_t* callback)
{
for(size_t i=0; i<n; i++)
{
callback(data[i]);
}
}
void print (int i)
{
printf("%d ", i);
}
int main (void)
{
int array [5] = {1, 2, 3, 4, 5};
traverse(5, array, print);
}
编辑:
在上面的例子中,数据类型是int
。但由于它是泛型编程,您可以进行一些调整并将其替换为任何其他数据类型,例如数组或结构。然后要注意的是,您必须通过指针将参数传递给回调,而不是按值传递。示例:
#include <stdio.h>
/* Generally it is bad practice to hide arrays behind typedefs like this.
Here it just done for illustration of generic programming in C. */
typedef int data_t[3];
typedef void callback_t (data_t* data);
void traverse (size_t n, data_t data[n], callback_t* callback)
{
for(size_t i=0; i<n; i++)
{
callback(&data[i]);
}
}
void print_array (int(*array)[3])
{
int* ptr = *array;
printf("{%d %d %d}\n", ptr[0], ptr[1], ptr[2]);
}
int main (void)
{
int array [2][3] = { {1, 2, 3}, {4, 5, 6} };
traverse(2, array, print_array);
}
这与 非常相似,但被重构为更通用的内容。
要一般地遍历元素,您可以将参数保留为 void *
,类似于 qsort
和 bsearch
。
typedef void cb_type (void *base, size_t sz);
void
traverse (void *base, size_t n, size_t sz, cb_type *cb) {
char *p = base;
for (size_t i = 0; i < n; ++i) {
cb(p + i*sz, sz);
}
}
回调传递元素的大小。回调函数应该知道对象的底层类型,因此它可以正确地推断出正在遍历哪个维度。例如,如果遍历一个int[4][5][6]
:
int array[4][5][6];
traverse(array, 4, sizeof(*array), print_456);
打印函数可能如下所示:
void
print_456 (void *base, size_t sz) {
if (sz == 5 * 6 * sizeof(int)) {
traverse(base, 5, 6*sizeof(int), print_456);
puts("");
} else if (sz == 6 * sizeof(int)) {
traverse(base, 6, sizeof(int), print_456);
puts("");
} else
printf("%d ", *(int *)base);
}
这个宏终于写成功了。我在这篇非常好的文章 (http://jhnet.co.uk/articles/cpp_magic). The following posts (Can we have recursive macros?, Is there a way to use C++ preprocessor stringification on variadic macro arguments?, C++ preprocessor __VA_ARGS__ number of arguments, Variadic macro trick, ...) 中找到了大部分信息,这对我也有很大帮助。
本回答旨在回答问题。它没有解决宏和良好编程习惯的问题。这是另一个主题。
这是代码
#define SECOND(a, b, ...) b
#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()
#define BOOL(x) NOT(NOT(x))
#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)
#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...) _IF_0_ELSE
#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__
#define EMPTY()
#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__
#define DEFER1(m) m EMPTY()
#define DEFER2(m) m EMPTY EMPTY()()
#define FIRST(a, ...) a
#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0
#define MAP(m, first, ...) \
m(first,__VA_ARGS__) \
IF_ELSE(HAS_ARGS(__VA_ARGS__))( \
DEFER2(_MAP)()(m, __VA_ARGS__) \
)( \
/* Do nothing, just terminate */ \
)
#define _MAP() MAP
#define PP_NARG(...) \
PP_NARG_(,##__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
z,_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0
#define TIMES(...) EVAL(MAP(TIME2FOR,__VA_ARGS__))
#define TIME2FOR(x,...) \
for(int CAT(i,PP_NARG(__VA_ARGS__))=0; \
CAT(i,PP_NARG(__VA_ARGS__))<x; \
CAT (i,PP_NARG(__VA_ARGS__))++)
main() {
int a[3][2][4];
TIMES(3,2,4) a[i2][i1][i0]=i2*100+i1*10+i0;
TIMES (3,2,4) printf("a[%d][%d][%d] : %d\n",i2,i1,i0,a[i2][i1][i0]);
TIMES (3,2,4) {/* whatever you want : loop indexes are ...,i2,i1,i0 */}
}
事实证明比我想象的要难。
我发现这个宏#define TIMES(x) for(int i1=0;i1<x;i1++)
对于缩短代码文本非常实用。但是当我有嵌套循环时,我不知道如何编写这样的宏,甚至不知道是否可能。这个想法如下。是否可以编写此代码
for(int i1=0;i1<5;i1++)
for(int i2=0;i2<3;i2++)
for (int i3=0;i3<7;i3++)
/* many nested `for` loops */
{
/* some code, for example to print an array printf("%d \n",a[i1][i2][i3]) */
}
作为
TIMES(5) TIMES(3) TIMES(7) ....
{
/* some code, for example to print an array printf("%d \n",a[i1][i2][i3]) */
}
使用一种 "recursive" 宏来检测所有 TIMES
并将它们替换为具有 i1、i2、i3、... i'n' 循环计数器的 for
循环?
这是非常糟糕的做法,请不要这样做。其他 C 程序员非常了解 for 循环,但他们完全没有注意到您私有的、秘密的宏语言。此外,像这样的函数式宏的类型安全性很差,只能作为最后的手段使用。
正确的解决方法不是使用宏,而是使用函数。如果你想使用适当的泛型编程,你可以这样写:
typedef void callback_t (int data);
void traverse (size_t n, int data[n], callback_t* callback)
{
for(size_t i=0; i<n; i++)
{
callback(data[i]);
}
}
其中callback
是调用者提供的函数指针,包含了实际的功能。类似于宏中的循环体。
完整示例:
#include <stdio.h>
typedef void callback_t (int data);
void traverse (size_t n, int data[n], callback_t* callback)
{
for(size_t i=0; i<n; i++)
{
callback(data[i]);
}
}
void print (int i)
{
printf("%d ", i);
}
int main (void)
{
int array [5] = {1, 2, 3, 4, 5};
traverse(5, array, print);
}
编辑:
在上面的例子中,数据类型是int
。但由于它是泛型编程,您可以进行一些调整并将其替换为任何其他数据类型,例如数组或结构。然后要注意的是,您必须通过指针将参数传递给回调,而不是按值传递。示例:
#include <stdio.h>
/* Generally it is bad practice to hide arrays behind typedefs like this.
Here it just done for illustration of generic programming in C. */
typedef int data_t[3];
typedef void callback_t (data_t* data);
void traverse (size_t n, data_t data[n], callback_t* callback)
{
for(size_t i=0; i<n; i++)
{
callback(&data[i]);
}
}
void print_array (int(*array)[3])
{
int* ptr = *array;
printf("{%d %d %d}\n", ptr[0], ptr[1], ptr[2]);
}
int main (void)
{
int array [2][3] = { {1, 2, 3}, {4, 5, 6} };
traverse(2, array, print_array);
}
这与
要一般地遍历元素,您可以将参数保留为 void *
,类似于 qsort
和 bsearch
。
typedef void cb_type (void *base, size_t sz);
void
traverse (void *base, size_t n, size_t sz, cb_type *cb) {
char *p = base;
for (size_t i = 0; i < n; ++i) {
cb(p + i*sz, sz);
}
}
回调传递元素的大小。回调函数应该知道对象的底层类型,因此它可以正确地推断出正在遍历哪个维度。例如,如果遍历一个int[4][5][6]
:
int array[4][5][6];
traverse(array, 4, sizeof(*array), print_456);
打印函数可能如下所示:
void
print_456 (void *base, size_t sz) {
if (sz == 5 * 6 * sizeof(int)) {
traverse(base, 5, 6*sizeof(int), print_456);
puts("");
} else if (sz == 6 * sizeof(int)) {
traverse(base, 6, sizeof(int), print_456);
puts("");
} else
printf("%d ", *(int *)base);
}
这个宏终于写成功了。我在这篇非常好的文章 (http://jhnet.co.uk/articles/cpp_magic). The following posts (Can we have recursive macros?, Is there a way to use C++ preprocessor stringification on variadic macro arguments?, C++ preprocessor __VA_ARGS__ number of arguments, Variadic macro trick, ...) 中找到了大部分信息,这对我也有很大帮助。 本回答旨在回答问题。它没有解决宏和良好编程习惯的问题。这是另一个主题。
这是代码
#define SECOND(a, b, ...) b
#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()
#define BOOL(x) NOT(NOT(x))
#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)
#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...) _IF_0_ELSE
#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__
#define EMPTY()
#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__
#define DEFER1(m) m EMPTY()
#define DEFER2(m) m EMPTY EMPTY()()
#define FIRST(a, ...) a
#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0
#define MAP(m, first, ...) \
m(first,__VA_ARGS__) \
IF_ELSE(HAS_ARGS(__VA_ARGS__))( \
DEFER2(_MAP)()(m, __VA_ARGS__) \
)( \
/* Do nothing, just terminate */ \
)
#define _MAP() MAP
#define PP_NARG(...) \
PP_NARG_(,##__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
z,_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0
#define TIMES(...) EVAL(MAP(TIME2FOR,__VA_ARGS__))
#define TIME2FOR(x,...) \
for(int CAT(i,PP_NARG(__VA_ARGS__))=0; \
CAT(i,PP_NARG(__VA_ARGS__))<x; \
CAT (i,PP_NARG(__VA_ARGS__))++)
main() {
int a[3][2][4];
TIMES(3,2,4) a[i2][i1][i0]=i2*100+i1*10+i0;
TIMES (3,2,4) printf("a[%d][%d][%d] : %d\n",i2,i1,i0,a[i2][i1][i0]);
TIMES (3,2,4) {/* whatever you want : loop indexes are ...,i2,i1,i0 */}
}
事实证明比我想象的要难。