|
1、函数宏介绍函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。函数宏本质上为宏,可以直接进行定义,例如:#define INT_SWAP(a,b) \' Z9 ~# ^6 L% w int tmp = a; \ a = b; \$ h- Z" v7 @1 H5 q5 y( U/ z b = tmp 但上述的宏具有一个明显的缺点:当遇到 if、while 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = b 和 b = tmp 均不受控制语句所作用。因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为{}、do{...}while(0) 和 ({})。下文将一一对三种方式进行分析,比较各自的优劣点。 3 ^. D9 o; T; B( A5 ?2 ]: f! F 2、{} 方式 INT_SWAP 宏使用 {} 封装后形态如下: #define INT_SWAP(a,b)\{ \ int tmp = a; \+ _: o7 L& j8 |$ d a = b; \ b = tmp; \* |( d; `/ H' t j& j1 p# V } 此时,直接调用与在无花括号的控制语句(如 if、while)中调用均能正常运行,例如: #define INT_SWAP(a,b) \2 r; [9 |7 _9 Z* }6 \* m# U; {{ \+ q7 s" o8 q8 d: Q) z( R* U int tmp = a; \ a = b; \ b = tmp; \' s: ~. S7 q" k' f7 C) g0 T } " g F3 l1 l7 ~ int main() _2 h+ l% s7 A9 Y8 K# C8 Q2 ^ {5 s3 U: z$ B. K* _ l9 A int var_a = 1;$ y2 P+ Z4 O! h, {0 t int var_b = 2;/ t" k8 L( h# r9 A3 l 0 H" c* c9 D, x% ^$ m" a INT_SWAP(var_a, var_b);( G; P" _$ \( w printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1 . l$ y( k2 b0 v. k3 P if (1) INT_SWAP(var_a, var_b);+ h8 q# `* ~$ R( _" M8 B S% z' }$ [6 R printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 1, var_b = 2( e5 K+ k$ W9 p1 x2 t }- z; g2 h. o, G9 O9 P7 Y 但当无花括号的 if 语句存在其他分支(else if、else 等)如: if (1)INT_SWAP(var_a, var_b); else printf("hello world!\n");5 \/ D, S5 Y& {) W 会发现编译出错: .../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’ else 这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的else 当然没有找到与之匹配的 if 了。因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的if(port.2),例如:/* port.1 */0 w3 o0 V* e$ R9 \- L( V if (1) INT_SWAP(var_a, var_b) else {* i. o( k; f5 J printf("hello world!\n");. p) y+ t n' P6 O2 E4 H0 q V }) D1 @) [2 z& c* F /* port.2 */+ I \; l) d5 o' Z5 T if (1)( S. y' d3 \1 z {# i% X* e- P2 R' y7 ]' P INT_SWAP(var_a, var_b); } else( Y% Y! B5 ^7 a" {2 \- B! B7 { {2 g. `, {0 W& d l8 W printf("hello world!\n"); }4 C3 F* b+ ?8 s& j, R 可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。 优缺点总结: 3、do{…}while(0) 方式 INT_SWAP 宏使用 do{...}while(0) 封装后形态如下: #define INT_SWAP(a,b) \8 W, |6 l' h8 _& Ado{ \ int tmp = a; \ ~. H M1 y" x a = b; \ b = tmp; \ }while(0)9 b: T( D1 ]0 h0 D do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;。 由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如: #define INT_SWAP(a,b) \4 {6 J) g( A% V% U3 W+ V5 i- }do{ \0 a! ?2 r R2 \/ E: x' ]1 Q if (a < 0 || b < 0) \ break; \ int tmp = a; \ a = b; \$ i" D2 q5 s8 P8 s1 ]# ~7 H4 U/ @ b = tmp; \8 r2 t- H) z6 `- r% @- R- Y8 } }while(0) 由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。 因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如: #define INT_SWAP(a,b) \; r* ~) M: l3 \. \do{ \, s4 X! G+ F& v if (a < 0 || b < 0) \+ |' V' u( |" x2 q- f3 f' }! G break; \- b* G. I% a) o int tmp = a; \ a = b; \ b = tmp; \- y4 B* p( Y( r5 e# O. [- v }while(0) int main()4 K5 K5 N; @+ F' h+ W4 \ { int var_a = 1;# |, G, ~( p0 |( c int var_b = 2;* @6 X1 k( m3 a* R, ? - l7 H+ j& V; V( I if (1)* s, y: f, w; C INT_SWAP(var_a, var_b); S" ~$ w* j! V; }* ~1 {! r$ V6 { else printf("hello world!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1 return 0;" s+ H0 ?+ h3 T2 l- o% j, U0 q$ B+ @ } C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现: if (1): X0 `2 q3 Q' ~0 SINT_SWAP(var_a, var_b)! a# A' d% }2 I) v& k else# g6 F6 z! J% E9 Z5 \ { printf("hello world!\n"); " o9 q2 q8 o1 P* O# R } $ w+ y9 b8 V& m R2 [ 优缺点总结: 4、 ({}) 方式 ({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。 INT_SWAP 宏使用 ({}) 封装后形态如下: #define INT_SWAP(a,b) \. m5 S3 B% v. k% c# O) t k8 i({ \ int tmp = a; \ a = b; \* [( b3 n0 Z0 x b = tmp; \ }) 3 _7 @4 F/ i0 R: v% k4 ^ 与 do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如: #define INT_SWAP(a,b) \' J3 C8 w4 b& f/ ^, Z" @# u- u({ \ int tmp = a; \4 \5 r5 D x; Z1 S' C a = b; \- b+ Z) a3 O8 O; G5 \7 z5 H$ | b = tmp; \' r$ k6 ?" r% k' c6 Q }) int main() {- k4 D, j# I. L! f int var_a = 1;7 @4 n9 U/ g9 `2 ?3 { int var_b = 2;8 n* Q: S" [2 u5 W; V . U0 ?+ z" w% U; @" p* \7 K if (1) INT_SWAP(var_a, var_b); else printf("hello world!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1 9 ?) Z" t7 @/ M9 c/ @ return 0; } - S9 \% }3 [! S! L$ Z+ p4 `. t5 \" } 与 do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢? 答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如: int main()" T( n$ i8 m% G! d1 o+ r7 Q6 l{% a2 H/ }2 f0 q int a = ({( W8 P. k- M9 h6 e- n5 T; z- F 10; 1000; });6 J& g8 x6 \/ o4 Z printf("a = %d\n", a); // a = 1000 } + ]* g; S" U- g0 l5 y0 B! m 因此,({}) 可以为函数宏提供返回值。例如: #define INT_SWAP(a,b) \({ \ int ret = 0; \ if (a < 0 || b < 0) \4 b- E) [: i% H4 K4 K! N( r) v2 { { \/ R1 J4 O; ^ `; j% L7 _& q0 Z ret = -1; \ } \ else \" R9 P. i8 [( j% j. _6 D { \% ^$ H' H/ A) N0 K. Q/ K5 A' S; e! g; ? int tmp = a; \ a = b; \+ t. b+ i3 I7 O! q6 Y b = tmp; \ } \ ret; \ }): N" V' L. c3 r3 X! K* t" ?$ I ) q7 `$ `* `, B7 h# Y. g int main() { int var_a = 1; int var_b = 2;# W* q" `8 o2 Q5 s* b' l' z) s if (INT_SWAP(var_a, var_b) != -1) printf("swap success !!\n"); // swap success !!! G2 L) D; o8 w+ C- y else printf("swap fail !!\n"); . N9 j* s1 F3 y: l- u printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1 ; l6 {, W/ @0 U) p. w O- n2 g% r return 0;5 N6 q6 c! u! D& B) y2 w9 { } 可见,此时的 INT_SWAP 宏已与函数十分接近。 优缺点总结: 5、 总结 综上,在 {}、do{...}while(0) 和 ({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。 o8 {) F9 ]* ~6 M |
微信公众号
手机版