====== 参数宏(Macro with parameters) ====== ===== 括号规则 ===== 对于带参数的宏,定义时左括号(parenthesis)必须紧接着宏名,之间不能有空格,否则,从左括号开始的部分都被识别为宏的定义。当运行时,宏名和左括号之间可以有空白。 宏调用时,实参可以包含多层括号,括号内可以有逗号(commas)。大括号(braces)和中括号(brackets)也可以出现在实参里,但他们里面不能包含逗号。 例如定义: #define insert(stmt) stmt 如下调用是合法的: insert( { a = 1; b = 1; } ) 但这个就不行: insert( { a = 1, b = 1; } ) 必须写成这样才行: insert( { (a = 1, b = 1); } ) 有点诡异,但这是语法的要求。 ===== 宏的本质 ===== 编写带参数的宏需要特别小心,因为**宏扩展的本质仅仅是进行文本式的替换(textual substitution)**,它不是真正的函数。 例如: #define SQUARE(x) x * x 本意是求平方,但调用: SQUARE(z + 1) 的扩展结果却是: z + 1 * z + 1 一个安全的做法是在宏体内使用括号括住每个参数。如果整个宏的形式是一个表达式,那么宏也用括号括住。 上例改造后: #define SQUARE(x) ((x) * (x)) 如果没有用括号括住整个宏,那么下面的调用仍旧可能会出问题: (short)SQUARE(z + 1) 因为强制转换的优先级更高。 ===== 宏的副作用 ===== 括号不是万能的,还要注意宏参数可能存在的副作用,毕竟宏不是函数。 例如: a = 3; b = SQUARE(a++); 导致a++了两次,结果为5,而b的结果则依赖于实现。如果SQUARE是个函数,就不会有这个问题。 ===== do-while模式 ===== 对于多语句的情况,一般用do-while结构比较合适。 比如下面的实现: #define swap(x, y) {unsigned long temp = x; x = y; y = temp;} 实际调用时,很容易在末尾加个分号(semicolon),导致编译出错: if ( x > y) swap(x, y); else x = y; 改用do-while实现就好了: #define swap(x, y) \ do { unsigned long temp = x; x = y; y = temp; } while (0) ===== 变参宏 ===== C99允许在参数中使用%%...%%实现变参宏。所有额外的参数,包含其中的逗号,用标识符%%__VA_ARGS__%%代替。 比如: #define my_printf(...) fprintf(stderr, __VA_ARGS__) 调用: my_printf("x = %d\n", x); 的扩展结果为: fprintf(stderr, "x = %d\n", x); 另一个例子: #define make_em_a_string(...) #__VA_ARGS__ 调用: printf("%s\n", make_em_a_string(a, b, c, d)); 的扩展结果为: printf("%s\n", "a, b, c, d"); {{tag>C语言}}