EDA365电子论坛网
标题: 函数宏的三种封装方式 [打印本页]
作者: monsterskyy 时间: 2020-12-18 13:36
标题: 函数宏的三种封装方式
1. 函数宏介绍函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。
函数宏本质上为宏,可以直接进行定义,例如:
#define INT_SWAP(a,b) \! m% C, B% Q; t+ ^0 S
int tmp = a; \
9 L! \! _- P& |7 U a = b; \
+ e- Y$ V: K5 l/ ] b = tmp9 n4 P% C% s7 E! P! Y. u
但上述的宏具有一个明显的缺点:当遇到 if、while 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = b 和 b = tmp 均不受控制语句所作用。
因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}、do{...}while(0) 和 ({})。下文将一一对三种方式进行分析,比较各自的优劣点。
2. {} 方式INT_SWAP 宏使用 {} 封装后形态如下:
#define INT_SWAP(a,b)\
( r. O# E/ [( l$ v7 R0 M% ~( D{ \
$ B' W9 d8 N7 L' x2 z' [1 b int tmp = a; \
. F8 A: n2 Q5 b% X4 l7 L% r' e( p9 R4 @ a = b; \
* D: d* M+ L1 c8 G b = tmp; \$ J9 {$ u; T( q* e3 V8 O' E% R
}
" J7 I( |1 @1 D6 b4 R, m此时,直接调用与在无花括号的控制语句(如 if、while)中调用均能正常运行,例如:
#define INT_SWAP(a,b) \
' y0 P* A" G2 _; C{ \" `% U* [+ ?! r0 H% A& ^
int tmp = a; \ O' Z' Y# b; g. t. d4 c8 D# b
a = b; \9 a) U" v8 a2 l
b = tmp; \
6 N) m( I5 d/ B}) E* p" ?6 ^+ p( t
3 `6 y1 D" z Z! j5 y: Oint main()
7 P' h. [/ \* X f" ]" R2 D{
3 J0 a+ X2 E3 N8 I6 c int var_a = 1;; A; T/ d% z; Y
int var_b = 2;
6 Q0 v$ h, S$ s; U7 v! S* |% {
- m' U; k% E- r9 w INT_SWAP(var_a, var_b);2 r0 @* `: u3 F* w7 q: P+ a% k
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
0 U8 j6 g- @" \, ~ C2 ]; n 3 I- g0 o9 o& r% Y2 V! c! F
if (1)
T1 u% O* Y& u# g8 [" a INT_SWAP(var_a, var_b);6 m5 ?0 V8 e3 j3 L
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 1, var_b = 2
, j3 E$ T! \+ Q5 P; Y}9 y5 g$ l I g6 Z" z) M. Q1 [* M7 L
但当无花括号的 if 语句存在其他分支(else if、else 等)如:
if (1)
2 B" I6 T j1 z4 l }/ f INT_SWAP(var_a, var_b);# ?/ [, B E( S6 D, ?1 P' H
else
; Q; N1 R) L$ Q$ k8 a0 w+ I printf("hello world!\n");
9 {6 r9 N- C. ^' W会发现编译出错:
...
8 x( f7 [/ I. y0 Y9 a/mnt/hgfs/share/pr_c/src/main.c: In function ‘main’:
$ y4 j. ]% v# l. u- Q) Q2 W/mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’" T0 d3 U2 r3 c: K$ F/ E
else9 B# F) R5 y q9 l
这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。
因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的 if(port.2),例如:
/* port.1 */
. b- ^/ [* b/ k7 s; aif (1)
8 f6 v, N( \7 z2 t+ z INT_SWAP(var_a, var_b)$ g9 u/ h3 R' t
else8 h. t" e8 E: k" g2 a9 H& y
{. p8 `, b. b5 a' H
printf("hello world!\n");
- T/ f2 U% ?( @$ _" ?, O3 Y7 i}
; o' ]3 Z }, q- u
9 Q# @2 f. ^# R( [/* port.2 */
5 ]* k3 o% s' C8 Qif (1)
$ d$ ] s* U5 O. l2 }0 U3 _{ o( Y+ k8 a1 U. f
INT_SWAP(var_a, var_b);
r. W N$ n# B/ l$ u}! K& ]9 m( j. ^1 n3 @3 g: D# F
else
( y) x: \3 c. ~$ u B e, j{ {; r5 }7 e- A9 n
printf("hello world!\n");+ J# w5 `; S" I3 X2 K r: ]' \; T
}
/ U' l4 r# G9 P# b: y& k可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。
优缺点总结:
- 优点:简单粗暴。
- 缺点:不能在无花括号且有分支的 if 语句中直接调用;能够不带 ; 直接调用。* F1 C0 w7 r9 K. p' h3 b& G
3. do{...}while(0) 方式INT_SWAP 宏使用 do{...}while(0) 封装后形态如下:
#define INT_SWAP(a,b) \6 a" P5 t" d8 \' s9 s V5 [' j& E8 Z
do{ \3 ]- c0 l$ e$ M: n, J! f
int tmp = a; \( Y% G3 b3 [" q
a = b; \( `) y0 g& m6 y
b = tmp; \. j: j- T+ X- ^
}while(0)# c3 S# u! F" ^
do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;。
由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如:
#define INT_SWAP(a,b) \
( n2 |4 I2 @' C$ s$ }do{ \2 ?: K+ ~7 e9 G8 u# v7 Q: N
if (a < 0 || b < 0) \ {7 K5 T( V8 d( w c$ w$ Z, O
break; \, c/ Z9 F! A' A- k' }$ T7 f) _+ q
int tmp = a; \5 |. s$ F( K+ K% X# k; b
a = b; \
: {, b% j& V( E ? b = tmp; \2 v4 Z/ x/ |9 a. T2 l! B, F6 c" @+ I
}while(0)
& x) K3 D" k! c8 v. p% P4 t由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。
因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b) \0 p6 R8 S6 ~4 l6 i8 v9 I4 |8 @
do{ \* h) U( p k6 g6 S4 a
if (a < 0 || b < 0) \' w' s) a( {; f' u9 s) a
break; \% a; W, F3 [7 O4 d$ T; v3 A. s
int tmp = a; \2 u: x4 p* Q1 Q/ B
a = b; \( K* N2 S. p3 T! T1 a- X
b = tmp; \
* Y N4 c$ G* o9 m1 G) ]}while(0)
5 D% ~7 C' C j K3 I9 A3 s# @# V( `3 Q- M) h4 e$ @# q1 T
int main()
, G. M; w0 j' v1 K! F+ T+ i+ q4 ~{
9 p0 J1 a4 _! m4 s' T# i' b int var_a = 1;, B5 j, b9 q r L8 E# c
int var_b = 2;
, Z# E6 x+ R/ z: ^) W1 }; G9 R. j I% m: O) L, N$ R
if (1)
]; D& Q' e1 h INT_SWAP(var_a, var_b);# f G/ R* u4 p# D
else+ D6 M$ I0 k% U3 Q) u6 a
printf("hello world!\n");
3 m- V1 i+ B2 P. r; w printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
% M7 r: E2 B( ^, y+ W* k4 k( O7 J" `, x- {- k
return 0;
6 L& L% W+ n5 y$ z}
0 c1 J& d2 _. C @ H. i% X+ O7 H" T* z+ k
C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现:
if (1)3 S- f }' k8 g% w
INT_SWAP(var_a, var_b)5 F) c6 B$ x% X7 W2 _' ` t1 |1 l
else
( C( b* c5 S h{& |) \5 s4 ^ _$ j+ j- h; I
printf("hello world!\n");
3 w' V( V9 \/ b! f) [$ F}+ P7 ~* x3 U* j Y
( `8 B; P( s2 H* s0 l2 _5 p优缺点总结:
- 优点:支持在无花括号且有分支的 if 语句中直接调用;支持提前退出函数宏;强制调用时必须使用 ;。
- 缺点:无返回值,不能作为表达式的右值使用。
4 G" I+ Y' ? g2 ?9 i, I
4. ({}) 方式({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。
INT_SWAP 宏使用 ({}) 封装后形态如下:
#define INT_SWAP(a,b) \
" e6 L w, g% T, {$ `( y({ \
a4 x ?# |. @ int tmp = a; \
# E u7 Y) \+ A3 P a = b; \6 [% q/ P9 T' L& M8 t8 S# K
b = tmp; \6 i& Y% h; A$ Z/ X
})* I5 w O1 w# }! u+ e1 n
, S+ y6 }8 L9 L; s# m5 P8 ?& Y2 Y与 do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b) \( k8 B2 y1 r% I1 k; [2 C; k( B
({ \7 g( F N, w( e) d, L3 T
int tmp = a; \- ]0 {/ r/ x% w+ Z k: O
a = b; \
, m# ?2 j6 ], |, X' O& X2 d# d$ v b = tmp; \% J! c u0 x6 T
})
4 N5 ]+ G2 k( f- M& k& @' \$ X9 |4 V D: z d9 y' M* _2 q; I. }6 s4 ]* p
int main()
+ o! p' x6 B( ]- E0 p{( N- E0 V! T7 A$ E5 I5 D7 d
int var_a = 1;
" u5 A# c b& F0 G: H4 i int var_b = 2;
# D4 S' ~. [$ T3 O; B
6 J h9 k- d$ |- {/ b, v if (1)
! A* B, @3 g4 n" h1 F9 I. w INT_SWAP(var_a, var_b);
- f0 Z) e5 u1 n/ P8 ~* O1 k else: Y# G& _. W6 q2 @& j e! E9 L
printf("hello world!\n");. w" L b1 }8 `8 f2 W
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
$ j) m7 k+ \7 H
. K5 ?- E; ^( H" I return 0;; y& ]; |- e- d" ^: t* S
}
" W3 G( @3 |. {, h$ s! b# O i4 K
# X3 @! u0 U" N/ h9 }与 do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢?
答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如:
int main()
! k7 b, \- s: Q2 I{
, ?! q: m) [, K3 `& Y) N; l( z int a = ({, F& C5 G9 T; W3 g$ Y l8 C2 N
10;
9 C) a" |) O# ^" R1 Z! a* } 1000;
0 M0 n- H9 E7 ^0 x, {+ Q });2 T1 {$ S- a1 j8 Q
printf("a = %d\n", a); // a = 1000& t5 `1 v* y3 d7 T0 b8 d. y
}$ x& s9 Z5 g& d) {& @5 m& T) u
0 c" {% v0 {3 i1 t' V- F) V. K
因此,({}) 可以为函数宏提供返回值。例如:
#define INT_SWAP(a,b) \. {' j; V* }; a4 c( \) H
({ \: d! ^. o2 C4 x+ w6 A e
int ret = 0; \
- V% Z5 H9 X4 c" b/ @ if (a < 0 || b < 0) \/ _( a) f9 C# F
{ \
0 V, A$ J( o8 P0 r; i# |9 M ret = -1; \ @7 r5 n+ m2 O3 Q6 N w
} \' f5 d0 ^7 k9 w6 F1 _: Y
else \! q2 N4 H: A( H7 n( U: f
{ \ y! l' v& [0 J
int tmp = a; \
) _9 z. ]; c4 |) h' g9 o8 | a = b; \
; [+ f; V1 o/ G+ ?1 V" c* C) T b = tmp; \/ ]" A2 S8 j8 D! t* l! t
} \
3 z& Q2 n# o# O9 k2 y ret; \; i/ x4 Q' c' n
})
7 R! C3 [0 T% k1 ]7 m1 p+ `9 B8 Y7 ~# R
int main()
$ |1 {* B* b1 x: v( ~{/ t- Z, ^8 a- V! x/ @6 o7 o9 n
int var_a = 1;
: J9 ]1 c; {- f1 ]8 p- q! h int var_b = 2;4 X% R3 H9 ]% |- Q
4 {, [, Z1 \) z
if (INT_SWAP(var_a, var_b) != -1)
/ t4 W1 {3 E0 H3 a" _6 _ printf("swap success !!\n"); // swap success !!4 P5 _" c+ S. f. L7 }
else
) K/ c6 I9 P. e+ w printf("swap fail !!\n");
( a# F. D) o! _8 F7 X# y printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1; B9 h3 m* H6 P
( p7 z* c( ^. s6 ^" i/ O return 0;
) w/ Z; {' n/ Z( \- D}2 x- t0 Q+ B. B9 F4 \ c
可见,此时的 INT_SWAP 宏已与函数十分接近。
优缺点总结:
- 优点:支持在无花括号且有分支的 if 语句中直接调用;有返回值,支持作为表达式的右值。
- 缺点:不支持提前退出函数宏;非 C 的原生语法,编译器可能不支持。9 p1 K. b! D; Y% c0 f5 P* _7 y; ]
5. 总结综上,在 {}、do{...}while(0) 和 ({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。
) d( Y: X+ l" C5 m' o; N
作者: modengxian111 时间: 2020-12-18 14:44
函数宏,即包含多条语句的宏定义
| 欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) |
Powered by Discuz! X3.2 |