|
1. 函数宏介绍 函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。 函数宏本质上为宏,可以直接进行定义,例如: #define INT_SWAP(a,b) \! L6 u; n, h9 V9 U2 v1 G: uint tmp = a; \ a = b; \ b = tmp+ X5 K4 o7 V# R3 u 但上述的宏具有一个明显的缺点:当遇到 if、while 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = b 和 b = tmp 均不受控制语句所作用。 因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}、do{...}while(0) 和 ({})。下文将一一对三种方式进行分析,比较各自的优劣点。 2. {} 方式INT_SWAP 宏使用 {} 封装后形态如下: #define INT_SWAP(a,b)\{ \ int tmp = a; \+ b2 F( Y- s% }: a, t. ] a = b; \ b = tmp; \ }) l. z8 h- Q" i* @5 X 此时,直接调用与在无花括号的控制语句(如 if、while)中调用均能正常运行,例如: #define INT_SWAP(a,b) \{ \! @( a& L( H$ S9 f' J( @ int tmp = a; \ a = b; \9 ?: d4 ^% a# ] b = tmp; \$ k0 `* J5 b/ v* Q T } ( i; G, V1 ~, J# {8 z int main() { int var_a = 1; int var_b = 2; INT_SWAP(var_a, var_b); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 13 E* z5 @+ e6 @& w% P) s# w if (1)) v2 i d9 |: ]5 T5 Z+ | INT_SWAP(var_a, var_b);8 a' ?5 E9 M' a6 f printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 1, var_b = 21 ^* M7 m! ~' H9 a i }+ {/ v+ ]2 h; N' o- g( ~' j7 Z3 s0 C 但当无花括号的 if 语句存在其他分支(else if、else 等)如: if (1)8 f( e% V% T* {3 B% }9 x: M) g; qINT_SWAP(var_a, var_b); else printf("hello world!\n");) A" e8 V3 | J) @ 会发现编译出错: .../mnt/hgfs/share/pr_c/src/main.c: In function ‘main’: /mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’ else6 E; U5 T8 M1 \3 U' ^7 s 这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。 因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的 if(port.2),例如: /* port.1 */' B& n4 R- q# k3 Iif (1)* I V/ W* u# q7 \7 r; k INT_SWAP(var_a, var_b)9 T1 S3 ]* p3 z' k8 r" \8 |! y else& U2 _. p+ V; W {. ~6 k/ s) d3 ^$ M6 S5 \" j% V printf("hello world!\n"); \0 D; |6 B; e( p& A }# Q7 X b/ b5 w/ \ /* port.2 */ if (1)# T2 {0 t$ r. {( G {, Q# \% U% V4 R3 o& Q INT_SWAP(var_a, var_b); } else { printf("hello world!\n"); } 可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。 优缺点总结: 3. do{...}while(0) 方式 INT_SWAP 宏使用 do{...}while(0) 封装后形态如下: #define INT_SWAP(a,b) \do{ \ int tmp = a; \ a = b; \ b = tmp; \ @5 b: @$ }- d' T* ^0 W; Y! U# b9 P }while(0)0 H8 J* L w. n- S' k2 U do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;。 由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如: #define INT_SWAP(a,b) \do{ \ if (a < 0 || b < 0) \ break; \: z0 w( W1 t- y1 _3 d! _6 X2 W int tmp = a; \ a = b; \ b = tmp; \ }while(0)- X8 |7 c/ s c- W 由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。 因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如: #define INT_SWAP(a,b) \ J }" O" J$ ndo{ \- H; Y% s% w: F5 O if (a < 0 || b < 0) \! T5 q/ X5 C! {2 R0 t4 z8 o break; \ int tmp = a; \ a = b; \ b = tmp; \ }while(0) j1 k9 q- o% x/ t* C1 f! K) @! q 4 z3 W+ Y# i: L% l, T" C int main() { int var_a = 1;6 N. y7 x# {) d0 y/ `1 s int var_b = 2; if (1) INT_SWAP(var_a, var_b);/ u. x$ q( o: x7 H/ D) M else printf("hello world!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1/ H5 d3 K1 m- R ' I$ H5 }" @' [8 e2 F; B return 0;6 {1 h# {/ b6 R3 u' s }( x A8 e" r2 B C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现: if (1)8 k n: H$ Y/ w& V4 vINT_SWAP(var_a, var_b)) f2 {7 U4 ~8 [- u0 T' d1 @* ] else {' W, e& x; \. g) q printf("hello world!\n"); 0 S5 e5 Z9 B& K }( r5 Y- I5 D5 T- {9 s: b5 q- R 优缺点总结: 4. ({}) 方式 ({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。 INT_SWAP 宏使用 ({}) 封装后形态如下: #define INT_SWAP(a,b) \({ \ int tmp = a; \ a = b; \9 U6 N7 t0 z0 c) M0 h6 t3 C2 ? b = tmp; \ }) 与 do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如: #define INT_SWAP(a,b) \7 H! n$ Z4 {% V9 I4 a0 K" {({ \ int tmp = a; \4 l" U, U) G/ S. v a = b; \ b = tmp; \* o5 f" h/ O! U# ?0 I, M+ I }) int main()- b+ M7 j3 {, O& U" _( e {6 {- p- O$ D9 q5 w: E! [8 m2 m int var_a = 1;& z( e H: s- \! S. J int var_b = 2; if (1)$ Z" F% t) F3 Y/ L& r INT_SWAP(var_a, var_b); else% c |1 L0 a+ \% w% t: G printf("hello world!\n");" l9 ~( n% m; d) j+ U' } printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 15 R. l$ a7 g4 F/ C: g! Y : W; f0 @# b2 Y% d+ K; H0 @+ G return 0; } # [% n) v8 U, i+ m) z+ R; y9 ^$ L 与 do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢? 答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如: int main()8 ?. C4 L+ o- N5 K$ o{+ y. | Q& L% ] int a = ({& j# Q) ?6 u& t: v 10; 1000; });! `! [& @7 [2 v, i; V printf("a = %d\n", a); // a = 1000 } 因此,({}) 可以为函数宏提供返回值。例如: #define INT_SWAP(a,b) \({ \4 T7 O* S; x$ x( K- Q/ ~: ?: r" j int ret = 0; \; K- z, L1 u. I! h- l if (a < 0 || b < 0) \ { \( l9 c) N! ~( Y! T: t; h5 t( {8 O ret = -1; \" A6 m) S, q$ y7 C/ W/ s } \3 K0 ]& F2 @% \/ I0 | a# ^2 b else \) U, i& ~3 G7 M4 n { \ int tmp = a; \( \# |6 J* c# } a = b; \ b = tmp; \& i% V) n: U, ? } \ ret; \ })0 t+ p/ `$ L7 i& D+ j5 c. K - Y1 V# h5 r7 ~ int main(): p& H( V) z, j0 B6 i: X {7 k2 C7 Z- P3 i2 S) ?# U int var_a = 1; int var_b = 2;! }/ j0 ^* H" ?6 C, P3 K4 [. f3 i % M( K2 s4 m G: T) x; X* x if (INT_SWAP(var_a, var_b) != -1) printf("swap success !!\n"); // swap success !!+ t: J# @7 U5 x else printf("swap fail !!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1) D4 m3 ^, |7 o* |0 g return 0;* g. m: D- c2 s0 t4 a" O* C; Q } 可见,此时的 INT_SWAP 宏已与函数十分接近。 优缺点总结: 5. 总结 综上,在 {}、do{...}while(0) 和 ({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。 ! l* i$ {) Q7 ~" D |
微信公众号
手机版