一乐电子

一乐电子百科

 找回密码
 请使用微信账号登录和注册会员

QQ登录

只需一步,快速开始

快捷登录

手机号码,快捷登录

搜索
查看: 2437|回复: 6
收起左侧

UNICODE、_UNICODE、__TEXT、__T、_T、_TEXT、TEXT宏

[复制链接]
发表于 2014-1-3 17:09 | 显示全部楼层 |阅读模式
本帖最后由 kenson 于 2014-1-3 17:10 编辑 % E0 |5 R5 U! v  V" R
6 A- X4 T7 P" F; {+ `

% u: {) f# z; a  P; @最近在看一些关于VC++和MFC的书时,书上对字符串的处理一般都会使用TEXT("a string")的形式或者_T("a string")的形式,自己写程序时MFC自动生成的代码中也有类似的宏。作为菜鸟,不加思考地照搬书上的TEXT()或者_T()不是我的风格,喜欢追根究底的性格促使我决定弄懂这些宏。但如果我按照以往写文章的习惯,跟着我思考的顺序来写这篇随笔的话,那是倒叙,会很不好写,所以我就按弄懂之后的正常顺序来写吧,但这也让这篇随笔看起来有点说教,各位看客且请忍受一下,谢谢。% o: `$ ~6 e- C1 c3 l, e
! f. H4 m8 W* f1 S! d
C语言发明时尚没有UNICODE这一说,那时候米国人只有ASCII,但随着计算机的进化,程序中需要出现其它国家语言的字符,如中文,这远不是ASCII所能表示的,所以就出现了UNICODE(想深入了解UNICODE的童鞋,请猛击这里),于是C语言也新加了一个类型:wchar_t,用于表示UNICODE字符,其定义为:#define unsigned short wchar_t,说白了其实就是用16位双字节表示一个字符(ASCII用单字节表示一个字符)。
. K8 e, z6 l+ @$ i
5 D- b% }: ~% Q9 {* O0 `6 I- J这样编写程序就出现了一个问题,我先定义了一个变量:char *str = "china";这当然没问题,但如果后来要把该字符串换成中文的,那就得换成wchar_t *str = L"中国";(顺便说一下,字符串前的L是告诉编译器,后面紧跟的字符串按UNICODE宽字符处理,即每个字符占两个字节)。如果字符串只有一个,改一下没问题,但很难想象一个程序中只出现一个字符串,所以,这样修改起来的工作量是很大的。
  W$ c0 ?5 G9 Q9 _8 t0 S. K3 f8 P# l5 |, g& ]) H+ E# `
M$永远不会缺乏牛人,所以他们自然为VC++想好了解决办法,那就是__TEXT(),__T()等一系列宏,先来看看WinNT.h头文件,这个文件有几千行,但我们只需要抽取关键的几行出来:) ]: K' a  L2 a: n+ y1 j
7 D- B8 @. Y2 H4 z& C2 n/ F1 Q
#ifdef  UNICODE                     // r_winnt' v& I% t  x! z  }. U( \. G, W
#define __TEXT(quote) L##quote      // r_winnt
. I9 P, v: t# {% K" F* F#else   /* UNICODE */               // r_winnt
3 Y  l6 q" a' r- \6 _#define __TEXT(quote) quote         // r_winnt
1 }& ]' B! S, v#endif /* UNICODE */                // r_winnt4 \, k. ~2 c+ ~" `
#define TEXT(quote) __TEXT(quote)   // r_winnt7 V; L# z! N7 R/ D! _/ v
这几行代码应该很简单:如果定义了UNICODE宏,那么就将__TEXT(quote)宏定义为L##quote,如果没定义UNICODE宏,则__TEXT(quote)宏就是普通的quote,最后,再将TEXT(quote)宏定义为__TEXT(quote)宏。(如果有朋友不懂其中##符号的意思的话,这里解释一下,##起连接作用,例如__TEXT("china")在经过宏替换后就会被解释为L"china")4 p4 v0 y. n  |, H
# t, g5 Z) C7 f. |
这种解决方法很巧妙,编程人员可以根据需要自由定义UNICODE宏来决定是使用ASCII字符串还是UNCODE字符串,而程序中的字符、字符串只需加个宏处理即可。与此类似的还有__T(),_TEXT()等宏,这些宏在tchar.h头文件里定义,这个文件同样有几千行,仍然只需要抽出关键的几行:
! o/ O; E2 d# d4 i- f3 r5 H* f: _* z$ c7 t' ~$ m' O1 D" L$ ~  m
复制代码9 C# i% M7 r. }. Y! O) Z
#ifdef  _UNICODE
( u9 S3 v" ]0 M- w2 x" v& A4 i- o$ O#define __T(x)      L ## x
# S2 c5 l6 ]" V- H& z  G# G% k#else   /* ndef _UNICODE */
5 H( R0 j6 [3 x6 x8 l; D0 D#define __T(x)      x
% Z, Q+ I. S+ [8 I( K% o#endif  /* _UNICODE */! _4 P' ?: |7 ^( Z
#define _T(x)       __T(x)
1 [5 P* v  W9 R8 c: k0 `#define _TEXT(x)    __T(x)
7 w  U. z. f$ i/ [5 r复制代码
; u! E. o2 u7 H, ?/ ?) T  M其原理同上,但这里是根据_UNICODE宏(注意前面的下划线)来决定是使用ASCII字符还是UNICODE字符的。同样最后又附加了_T()和_TEXT()宏的定义。
' k& y6 L3 Z9 x  D/ [4 Z$ s* Y0 k+ [" Z6 U% a/ r" p' ^+ C
到此,疑问又来了,为什么要定义_UNICODE和UNICODE两个宏?M$的牛人吃多了嫌撑?要是一个宏定义了,另一个宏没定义引起冲突怎么办?于是再来查找一番,在atldef.h和afxv_w32.h两个头文件中,我找到了一模一样的内容,如下:. G! y1 Q  _& y( c/ i9 f9 J$ t

7 M; m8 F& n+ F7 @, m4 P/ }0 N, C  g复制代码
% l6 \; a0 m5 m7 M4 x4 u#ifdef _UNICODE
  l' |( y$ D, d' ]% G' z3 r8 i' a#ifndef UNICODE2 U8 m/ U* V4 o0 {5 f! B
#define UNICODE         // UNICODE is used by Windows headers
" d. G) u. O  G( C( _#endif
4 U0 f$ w9 N/ Z5 W3 }5 i7 g! t( |#endif
' {9 k8 s# V/ g& e
% g9 R3 b% `5 N4 S, ^#ifdef UNICODE5 P) G5 ?  U! X' M7 y+ t/ g
#ifndef _UNICODE
0 {* ]; q: N' h; Z) l, y2 K' J  I#define _UNICODE        // _UNICODE is used by C-runtime/MFC headers- O6 @, \5 w1 f8 ^
#endif
+ V& K6 J1 \) p+ }#endif
% {' N: L* Y1 @2 }9 r, N复制代码7 S6 ^7 n+ ]' o! P! G5 m
这段代码有点绕,但目的很清晰,就是要保证UNICODE和_UNICODE这两个宏要么都定义了,要么都未定义,不能只定义一个。所以,在上面的分析中,不论是利用UNICODE宏来定义的__TEXT()和TEXT(),还是利用_UNICODE宏来定义的__T()、_T()和_TEXT(),都是可以正常使用的,不会出现一部分是ASCII字符串,另一部分是UNICODE字符串的低等错误。因此,__TEXT、__T、_T、_TEXT、TEXT这几个宏的具体作用其实是一样的,没有区别,至于M$为什么对相同的功能要搞这么多奇形怪状的符号来表示,那我就不得而知了。
, Q% s. O/ a, K* j: s9 s! Y4 @( a* ]3 V
同时,上面的注释还解释了,UNICODE宏用于Windows头文件,而_UNICODE宏用于C运行时和MFC的头文件,当然我这个菜鸟还不太懂具体区别,只能大概猜到,在Windows的头文件中,需要根据是否使用UNICODE来定义不同版本宏的地方就使用UNICODE宏,而在MFC和标准C中,需要根据是否使用UNICODE来定义不同版本宏的地方就使用_UNICODE宏。
, k+ n: K* H  d/ H2 P7 A3 T
- G: q5 i' W( M. ^1 R另外,小弟真的很菜,尚不知为何关于UNICODE和_UNICODE这两个宏的纠缠在atldef.h和afxv_w32.h两个文件中都出现了,而且还一模一样,园子里高手众多,如果哪位知道这其中的奥秘,还望能给小弟指点一二,先行谢过。
 楼主| 发表于 2014-1-3 17:18 | 显示全部楼层
1 S. A& y. Z! v% c( j& I" R
C语言之#define用法(终极盘点篇)2008-08-27 20:56一.$ f% t$ ?( A& N
#define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质,总是在此处产生一些困惑,在编程时误用该命令,使得程序的运行与预期的目的不一致,或者在读别人写的程序时,把运行结果理解错误,这对 C语言的学习很不利。
% Y) n% b7 o1 X% ]1 #define命令剖析
: o, H3 p, b/ F. [4 a1.1   #define的概念
4 L8 o; @$ t6 y#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。# V* B. Y7 }3 f7 W' {
该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。
* J. w* T8 O5 }3 {4 x$ Z(1)   简单的宏定义:2 P$ d: r* K- Q4 B3 x6 k
#define   <宏名>  <字符串>$ K- e3 H) P$ c, X, n, T
   例:   #define PI 3.1415926
( R9 @2 X4 Y  _, W$ C(2) 带参数的宏定义; u" M% K4 N! @4 w. S4 u
   #define   <宏名> (<参数表>)   <宏体>
/ o2 {' [( i( d" ~   例: #define   A(x) x
4 x( j. x  E1 [6 p" w+ v4 B2 w( h一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。! ^/ D- e1 v+ l3 @0 B: Q4 p
1.2 宏替换发生的时机
4 Q4 z2 @4 b' D- }. F为了能够真正理解#define的作用,让我们来了解一下对C语言源程序的处理过程。当我们在一个集成的开发环境如Turbo C中将编写好的源程序进行编译时,实际经过了预处理、编译、汇编和连接几个过程,见图1。
/ d9 c/ z, D7 O& X5 ?源程序7 F: d0 a8 g) q4 B2 g  [' ]5 W  X

. ^+ R  G4 P/ }0 }8 e8 x预处理器5 N# ^% G. f  r9 ?
/ h6 [4 S) D' @; P- Q
修改后的源程序: H! `' d7 Z( |! ?, f0 j& x( d
3 y5 c2 o3 B' s% K- g9 d+ r/ z1 k
编译器
3 x# }* r) t# R! F- W8 c
: x2 t- _- t. |' a汇编程序2 d% [4 Y9 S% H
) L" o+ K) t8 r0 v+ D( X9 j# j
汇编器1 f/ i% p# B# t/ |4 o3 D: Y$ f: A

1 r: P- }0 x0 F8 t( U( v  `可重定位的目标程序3 K, U, Y1 W, Z- i) Z8 U
+ o, Y( s$ ?8 x/ J2 N. R9 b* L* j, e
连接器
& }+ f# [1 L3 [' z & I: G6 F2 z& ?, i6 M6 P7 V
可执行的目标程序% f( u3 w. C8 X$ @, C. a+ o2 T
: H' I/ k, z$ B4 s
图1 C语言的编译过程# z9 l! _) x2 q
! f' I. ^: G" u' [
其中预处理器产生编译器的输出,它实现以下的功能:
0 |8 g) X5 r1 D, g  T& @3 V(1)    文件包含
+ X; U8 `& \0 x  v* c" k8 ~可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。: o. n. U/ u1 _' ~$ E0 h
(2)    条件编译
* u/ \+ x3 L$ R预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。, \; `( ~$ l# y% f) B
(3)    宏展开8 ?8 j7 G. q3 r$ h
预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。
1 S2 u! S/ m$ D3 k经过预处理器处理的源程序与之前的源程序有所有不同,在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能,所以在学习#define命令时只要能真正理解这一点,这样才不会对此命令引起误解并误用。, F  ]0 b" E- @
7 x3 }- v; C1 g+ |/ s
2 #define使用中的常见问题解析" w6 B8 q0 o) U1 Z* @0 j; `8 V
2.1 简单宏定义使用中出现的问题
9 g' C+ ?/ [3 @5 E在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引起误解和误用。如下例:
1 W& Q% o3 m/ c1 I6 N5 [例1   #define   N   2+2
# Z  s5 I5 M! d& r( Kvoid main(). [; e: G/ _& [
{9 T" U8 E: j; q. m; J
   int   a=N*N;. z7 G/ M; g4 q* d9 L
   printf(“%d”,a);9 o, v) e; A0 v) z$ D* E
}5 m1 ]- g: o& p1 _2 e: l
(1) 出现问题:在此程序中存在着宏定义命令,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为2+2=4,然后在程序中计算a时使用乘法,即N*N=4*4=16,其实该题的结果为8,为什么结果有这么大的偏差?
6 {1 L$ N' Q5 r9 H$ {(2)问题解析:如1节所述,宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8,这就是宏替换的实质,如何写程序才 能完成结果为16的运算呢?
! Y% ~2 ]! u# ^: A7 r1 f+ h(3)解决办法:将宏定义写成如下形式
- P, y; ?9 q7 k7 ?! O& I/ s#define   N   (2+2)
. `+ |1 n, F/ r$ ~5 _这样就可替换成(2+2)*(2+2)=16
: b1 b7 a, Y  Z; k# D2.2 带参数的宏定义出现的问题% k, i6 G5 Y- R1 u7 o
在带参数的宏定义的使用中,极易引起误解。例如我们需要做个宏替换能求任何数的平方,这就需要使用参数,以便在程序中用实际参数来替换宏定义中的参数。一般学生容易写成如下形式:( j. v8 _2 ^, S+ L
#define   area(x)   x*x
9 g/ s5 w! f4 p) P这在使用中是很容易出现问题的,看如下的程序5 }. s2 c* |; v$ @) ^1 G
void main()$ R1 s1 n) q; ?
{( z/ o; [+ z5 Y1 Q; l7 K
int   y=area(2+2);
+ [2 b, ~" L! j; S8 n% O- x* Sprintf(“%d”,y);$ F( Z" A! U1 t0 D$ v
    }
+ Y- h5 t- ]' ^9 u按理说给的参数是2+2,所得的结果应该为4*4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换 了,在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?#define   area(x) (x)*(x),对于area(2+2),替换为(2+2)*(2+2)=16,可以解决,但是对于area(2+2)/area(2+2)又会怎么样 呢,有的学生一看到这道题马上给出结果,因为分子分母一样,又错了,还是忘了遵循先替换再计算的规则了,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除运算规则,结果为16/4*4=4*4=16,那应该怎么呢?解决方法是在整个 宏体上再加一个括号,即#define   area(x) ((x)*(x)),不要觉得这没必要,没有它,是不行的。$ A, D6 \  z5 F. O
要想能够真正使用好宏定义,那么在读别人的程序时,一定要记住先将程序中对宏的使用全部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展 开后再进行相应的计算,就不会写错运行结果。如果是自己编程使用宏替换,则在使用简单宏定义时,当字符串中不只一个符号时,加上括号表现出优先级,如果是 带参数的宏定义,则要给宏体中的每个参数加上括号,并在整个宏体上再加一个括号。看到这里,不禁要问,用宏定义这么麻烦,这么容易出错,可不可以摒弃它, 那让我们来看一下在C语言中用宏定义的好处吧。
/ U) _2 ]: ]" Y- I9 v+ P% W4 I+ G" e: Q4 o  W+ m
3   宏定义的优点
3 n4 ~% h8 {* e/ Y: Q$ N" [(1)   方便程序的修改
9 [7 {0 p( d& y. c使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。我们所说的常量改变不是在程序运行期间改变,而是在编程期间的修改,举一个大家比较熟悉的例 子,圆周率π是在数学上常用的一个值,有时我们会用3.14来表示,有时也会用3.1415926等,这要看计算所需要的精度,如果我们编制的一个程序中 要多次使用它,那么需要确定一个数值,在本次运行中不改变,但也许后来发现程序所表现的精度有变化,需要改变它的值, 这就需要修改程序中所有的相关数值,这会给我们带来一定的不便,但如果使用宏定义,使用一个标识符来代替,则在修改时只修改宏定义即可,还可以减少输入 3.1415926这样长的数值多次的情况,我们可以如此定义 #define   pi   3.1415926,既减少了输入又便于修改,何乐而不为呢?# x+ M+ n& D3 I2 W) I& U
(2) 提高程序的运行效率4 K3 K" F; u" U8 ~/ q! I
使用带参数的宏定义可完成函数调用的功能,又能减少系统开 销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子 函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽 略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义 所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。
6 u7 R5 [  S' X  F9 D' w6 K- [
$ J( M2 U, Y, i/ ?7 O$ y" P4 结语
& S6 R& `- m& v+ C, A* p. M/ D本文对C语言中宏定义#define在使用时容易出现的问题进行了解析,并从C源程序处理过程的角度对#define的处理进行了分析,也对它的优点进行 了阐述。只要能够理解宏展开的规则,掌握使用宏定义时,是在预处理阶段对源程序进行替换,只是用对应的字符串替换程序中出现的宏名,这样就可在正确使用的 基础上充分享受使用宏定义带来的方便和效率了, U: s8 M7 a" L  q; |

+ b/ d$ w$ z( C7 s+ q二.
9 a$ r* f5 ~8 h. C; d& ]& I最近看com相关的资料,看到CCmdTarget实现com接口的时候,去读了一些宏的定义,在afxdisp.h头文件中
4 m. d! G; |8 g! ?! x% D
4 r# n* ^% F+ B/ `+ ]7 `- J#define BEGIN_INTERFACE_PART(localClass, baseClass) \
4 {% J, R& @. g4 Uclass X##localClass : public baseClass \+ X5 C0 R( A5 a6 Q2 L% i

$ H+ ^( e, M. J& h本来这个宏定义很容易理解的,但是这里多出个X##,我真没见过这种用法,不晓得它是什么用意。$ v  R. j4 b( w) T
后来问了几个朋友也都不知道。
2 h3 ~% |( J$ o! B, n5 I" W
/ p8 ?5 o# g4 }3 H你知道么?
. `6 u" L5 s- N) S2 J+ `& C8 a0 S+ c2 v  L; I/ I
也许你也不知道~呵呵,最后我还是找到了相关的资料,解读了这个define,还顺便认识了另外两个不常用的define
! b. x! N  Y: \+ R# C& e3 R) P3 {+ ~" s1 F$ u( _2 H* q) L1 T" j! I
#define Conn(x,y) x##y
2 h2 a3 @2 G* Q/ `#define ToChar(x) #@x
6 W% G; n  s! P6 c8 y+ z#define ToString(x) #x% g& T: H" q+ \, j( x* L! k  o9 J
' A4 q, l- X' n5 ]+ w
x##y表示什么?表示x连接y,举例说:
# ]5 i9 m! ]2 D8 Zint n = Conn(123,456);   结果就是n=123456;5 n5 x. y* V7 v9 n2 I0 q5 b' @7 H
char* str = Conn("asdf", "adf")结果就是 str = "asdfadf";9 Z) r" q) `. {3 r* i& M, ~8 c
怎么样,很神奇吧7 V4 k9 r& H' U! J9 R

4 {" G+ s8 u! x/ d( b再来看#@x,其实就是给x加上单引号,结果返回是一个const char。举例说:9 g* A$ c/ T( M! f& \5 ~* I
char a = ToChar(1);结果就是a='1';" t; L) Z# d2 w
做个越界试验char a = ToChar(123);结果是a='3';, E0 V# J% I+ w$ B) G- Q5 e
但是如果你的参数超过四个字符,编译器就给给你报错了!error C2015: too many characters in constant   :P& f) l% S& T3 x* l

1 ^# R* p! }; o1 m; f, O3 C最后看看#x,估计你也明白了,他是给x加双引号: K  a) D* H$ y( B' w
char* str = ToString(123132);就成了str="123132";, D! V/ q+ L/ L) P9 |
2 g- ^0 o. Y  Z8 z' y4 q( _$ ~; C
最后留几个小试验给大家去测测:7 `5 ^8 ^$ \& w7 |, K( D
#define Dec(x,y) (x-y)
8 R/ N+ r2 H4 aint n = Dec( A(123,1), 1230);. b8 x4 o! w6 i, U
n = Conn(123, Conn(123,332) );6 a0 s, u- J4 a0 U6 u6 L- Q; @1 G
char* str = A("12", ToString( Dec(3,1));
0 F! f* E6 @( }" F" ]' [  C: ]结果会如何呢? 嘿嘿嘿嘿~6 y( j2 e9 U3 r+ Q

' D3 x0 m- a) E( o" ]6 h三.
( z: F- }) v" {5 Y$ |#define xxx() {} 1 N  M) M& N. N" t
标准C支持的# L9 A8 o8 @$ K' b- x4 c
#define xxx() ({})
% B1 S" O3 N5 G& U/ [5 N2 pGCC新增的功能,主要为了防止宏展开出现问题,默认展开时是要加上一个;的,容易出问题。# S2 i5 _2 J, ^- c  q
9 ?1 d1 s# B. K1 R' h4 N8 a. h
/ F1 O" _0 B$ A5 V
CODE:#define A(a,b,c) ({a=1;b+=1;c=3;a+b+c;})) C( a9 d  ]8 k, G  g  e4 ]) o
#include <stdio.h>: R" H! g, |# k+ b
int main()
/ t- m' ]- ?' \/ C" o! {1 c{( G% U. c/ d( D. N- w4 h: @
       int a;/ {, @4 O6 s+ B6 t- B
       int b=1;2 o' `% a! p( Y2 y/ V8 U+ o9 S
       int c;  C& S6 @, f4 ~* k; T: u
       int d;$ ^/ M$ E& {! h
       d=A(a,b,c);
( c2 J' j& P4 y& M( g       printf("%d,%d,%d,%d\n",a,b,c,d);
! e6 J$ s% u/ X. T( R, Y7 S+ W       return 0;* o! b+ E* h0 ^) ^' L; ?: F: F8 ]
}
4 M' H3 k) b0 r& u6 _表示该宏函数还有返回值,最后一个式子的返回值作为宏函数的返回值。
/ B4 \7 A7 n# I( U& F' _运行结果:( r* Q' ?& o. T  i# i& z
1,2,3,6
 楼主| 发表于 2014-1-3 17:23 | 显示全部楼层
 楼主| 发表于 2014-1-3 17:25 | 显示全部楼层
本帖最后由 kenson 于 2014-1-3 17:28 编辑
6 g1 ?0 o+ s2 S" @
% I; ^8 [& b7 y* E2 ?

指针数组和指向数组的指针8 g9 K, |6 M2 l) O1 Q& L7 O
       int * pi[3];      //pi是指向int类型的指针数组

       int a[][3] ={{1,2,3}, {1,2,3}, {1,2,3}};      //两维数组即数组的数组,a[0]是一个一维的3个int型元素的数组7 G2 B$ }( ]# e' s
       int (*pa)[3] = a;      //pa是一个指向3个int元素的数组的指针

. N+ Z5 Q4 D8 \
       const5 g3 l5 g2 O% V( y6 A
       const char* p1;      //指针本身不是常量,可以p1++,但指向的东西不能修改,*p1='a'不行8 O* n$ w7 z& E% r! A1 O" [
       char* const p2;      //指针本身是常量,p2++不行,但可以修改指向的内容,*p2='b'可以
3 y: N& ^( `; d4 J6 ^+ h* c       const char* const p3;      //指向常量的指针常量

       函数指针
) r7 p3 ?8 b3 Q! k+ X! `/ r* k       int (*fp)(const char*, …) = fprintf;      //调用可以fp("hello") 或者 (*fp)("hello")

       extern void insert(void);- i4 n( N$ q9 g7 o+ W8 a
       extern void update(void);  S( S- R6 G5 X+ I* }5 A5 H
       extern void delete(void);5 \' p  u8 ]2 a: q/ G
       void (*farray[])(void)={insert, update, delete};      //farray是一个函数指针数组,调用farray[0]()

; c8 I! l( j/ q. d
 楼主| 发表于 2014-1-3 17:29 | 显示全部楼层
本帖最后由 kenson 于 2014-1-6 15:50 编辑 ! b" @, M3 I  S3 y( D
) J5 O1 f9 `( U
C/C++ 宏详解% ]; l- x7 [4 z" d4 V$ x

众多C++书籍都忠告我们C语言宏是万恶之首,但事情总不如我们想象的那么坏,就如同goto一样。宏有$ i* T/ @' C5 }2 F
一个很大的作用,就是自动为我们产生代码。如果说模板可以为我们产生各种型别的代码(型别替换),. b+ S+ b3 I, j
那么宏其实可以为我们在符号上产生新的代码(即符号替换、增加)。

关于宏的一些语法问题,可以在google上找到。相信我,你对于宏的了解绝对没你想象的那么多。如果你
: y( e& F: n5 K/ f还不知道#和##,也不知道prescan,那么你肯定对宏的了解不够。

我稍微讲解下宏的一些语法问题(说语法问题似乎不妥,macro只与preprocessor有关,跟语义分析又无关):

1. 宏可以像函数一样被定义,例如:
. d6 X3 a# B% w0 T9 J#define min(x,y) (x 但是在实际使用时,只有当写上min(),必须加括号,min才会被作为宏展开,否则不做任何处理。7 i% ?% c7 F/ _+ ]
6 \0 @$ l. }) a7 g
2. 如果宏需要参数,你可以不传,编译器会给你警告(宏参数不够),但是这会导致错误。如C++书籍中所描
+ @: F3 {* v' V& y2 W1 O述的,编译器(预处理器)对宏的语法检查不够,所以更多的检查性工作得你自己来做。

3. 很多程序员不知道的#和##
  y0 k# Y! a# y$ i$ M#符号把一个符号直接转换为字符串,例如:
! ~9 v9 U& I, s0 |6 M#define STRING(x) #x/ J3 k4 o' A- a! t! [
const char *str = STRING( test_string ); str的内容就是"test_string",也就是说#会把其后的符号
& `' J) u8 T$ d% U' g9 y2 ?直接加上双引号。
* ?3 b- Q( C! `% t4 i8 n0 }# g5 G##符号会连接两个符号,从而产生新的符号(词法层次),例如:
7 s3 f# a% O' B2 E3 I: s3 s" d#define SIGN( x ) INT_##x
$ I" l, U, Z+ h  pint SIGN( 1 ); 宏被展开后将成为:int INT_1;

4. 变参宏,这个比较酷,它使得你可以定义类似的宏:4 D- z. g; T" S/ e1 _
#define LOG( format, ... ) printf( format, __VA_ARGS__ )
  a5 P. G# M1 [' D8 r5 r  xLOG( "%s %d", str, count );
% f7 S' f  z& C0 q( T/ ^__VA_ARGS__是系统预定义宏,被自动替换为参数列表。

5. 当一个宏自己调用自己时,会发生什么?例如:' u' @- [1 t4 o6 d4 ~8 L
#define TEST( x ) ( x + TEST( x ) )
4 k, _7 \9 ^) ^( r( e7 j6 i: }TEST( 1 ); 会发生什么?为了防止无限制递归展开,语法规定,当一个宏遇到自己时,就停止展开,也就是
: u; J3 ^2 G# \; o: e7 X. L说,当对TEST( 1 )进行展开时,展开过程中又发现了一个TEST,那么就将这个TEST当作一般的符号。TEST(1)
7 O) J$ q& J2 T- T+ l( Z最终被展开为:1 + TEST( 1) 。

6. 宏参数的prescan,
6 O9 X( L6 i. M/ P当一个宏参数被放进宏体时,这个宏参数会首先被全部展开(有例外,见下文)。当展开后的宏参数被放进宏体时,
- v% U  Q- Y' K+ e预处理器对新展开的宏体进行第二次扫描,并继续展开。例如:( n8 E1 ]* W4 w: H
#define PARAM( x ) x: @' S2 P& U, o; t7 A
#define ADDPARAM( x ) INT_##x$ ~) e) ^0 _0 K- ^0 X' \
PARAM( ADDPARAM( 1 ) );6 H$ \, g  D9 N* \) o
因为ADDPARAM( 1 ) 是作为PARAM的宏参数,所以先将ADDPARAM( 1 )展开为INT_1,然后再将INT_1放进PARAM。
4 m$ k% j) j% @6 u& T' h9 L. E9 a/ d: k. S- Z% K
例外情况是,如果PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开:
  k  r- p2 H) D6 F/ D7 [0 E#define PARAM( x ) #x6 F+ ~: T! Y* f1 }$ j2 `: g
#define ADDPARAM( x ) INT_##x4 W) k' \4 p  P& C- k
PARAM( ADDPARAM( 1 ) ); 将被展开为"ADDPARAM( 1 )"。

使用这么一个规则,可以创建一个很有趣的技术:打印出一个宏被展开后的样子,这样可以方便你分析代码:+ u3 a- b; x. Y* U, |* J, \; S7 S
#define TO_STRING( x ) TO_STRING1( x )
1 U5 ^1 R. u( \7 M* w: q( m& P#define TO_STRING1( x ) #x4 o* K* s1 C) G6 b
TO_STRING首先会将x全部展开(如果x也是一个宏的话),然后再传给TO_STRING1转换为字符串,现在你可以这样:
$ M( }- F! F- [# T( {" \const char *str = TO_STRING( PARAM( ADDPARAM( 1 ) ) );去一探PARAM展开后的样子。

7. 一个很重要的补充:就像我在第一点说的那样,如果一个像函数的宏在使用时没有出现括号,那么预处理器只是
9 n, T0 @8 X4 h. c将这个宏作为一般的符号处理(那就是不处理)。


  C! X0 e" o0 t$ Z; ^+ H- Y我们来见识一下宏是如何帮助我们自动产生代码的。如我所说,宏是在符号层次产生代码。我在分析Boost.Function1 W. ~" m) g# X; D! r! ?! U
模块时,因为它使用了大量的宏(宏嵌套,再嵌套),导致我压根没看明白代码。后来发现了一个小型的模板库ttl,说的/ C' q/ D; a% z, v1 Q  m
是开发一些小型组件去取代部分Boost(这是一个好理由,因为Boost确实太大)。同样,这个库也包含了一个function库。
) B, |+ |0 g' |* G1 Q# n/ F* T. |这里的function也就是我之前提到的functor。ttl.function库里为了自动产生很多类似的代码,使用了一个宏:

#define TTL_FUNC_BUILD_FUNCTOR_CALLER(n) /
0 b1 E( a  y  rtemplate< typename R, TTL_TPARAMS(n) > /' ^8 R! \8 \2 F( `  D, u
struct functor_caller_base##n /
# }8 y/ Y$ K' d& u/ Y8 c///...  ~8 k' u; N5 b. p0 @* P1 @
该宏的最终目的是:通过类似于TTL_FUNC_BUILD_FUNCTOR_CALLER(1)的调用方式,自动产生很多functor_caller_base模板:$ S& A& C7 K! M" e
template struct functor_caller_base15 E" v* ?; M+ M4 J( ~: ]+ t3 E
template struct functor_caller_base2+ n/ B9 @7 h$ A* F3 a
template struct functor_caller_base3/ v& b, [+ ^+ i4 Y5 Q5 j* b
///...' V) @4 H+ u' x
那么,核心部分在于TTL_TPARAMS(n)这个宏,可以看出这个宏最终产生的是:
0 z7 I  ~) a: Q, G& itypename T1
; ?) P! s1 p, xtypename T1, typename T2
, c" X) w& D' n* V) ?- Gtypename T1, typename T2, typename T3. t! j4 {7 c& p6 N" o9 r5 }$ y
///...2 S6 S4 h6 W$ J
我们不妨分析TTL_TPARAMS(n)的整个过程。分析宏主要把握我以上提到的一些要点即可。以下过程我建议你翻着ttl的代码,
* R, I5 L) Q+ ]2 |& [( B1 S相关代码文件:function.hpp, macro_params.hpp, macro_repeat.hpp, macro_misc.hpp, macro_counter.hpp。

so, here we go

分析过程,逐层分析,逐层展开,例如TTL_TPARAMS(1):

#define TTL_TPARAMS(n) TTL_TPARAMSX(n,T)
" e, T! A1 l+ C, R8 s: L# `- {( s; B; [1 h=> TTL_TPARAMSX( 1, T )
! v* S5 t: L! B( s6 C#define TTL_TPARAMSX(n,t) TTL_REPEAT(n, TTL_TPARAM, TTL_TPARAM_END, t)
9 N+ f5 \7 |& G: N=> TTL_REPEAT( 1, TTL_TPARAM, TTL_TPARAM_END, T )
% {4 e; }0 r5 b9 T; U* ~#define TTL_TPARAM(n,t) typename t##n,4 W  E- D- i0 V( M+ d# t
#define TTL_TPARAM_END(n,t) typename t##n
0 g4 j. G4 k' }( R7 u5 o#define TTL_REPEAT(n, m, l, p) TTL_APPEND(TTL_REPEAT_, TTL_DEC(n))(m,l,p) TTL_APPEND(TTL_LAST_REPEAT_,n)(l,p)- |( ~# P/ B5 ?" r  p: I5 o
注意,TTL_TPARAM, TTL_TPARAM_END虽然也是两个宏,他们被作为TTL_REPEAT宏的参数,按照prescan规则,似乎应该先将
5 E8 M/ d* X! W  r5 W这两个宏展开再传给TTL_REPEAT。但是,如同我在前面重点提到的,这两个宏是function-like macro,使用时需要加括号,2 @6 A( Y% I  }( A% m
如果没加括号,则不当作宏处理。因此,展开TTL_REPEAT时,应该为:" {* e1 ^$ D  _  u/ ^2 S
=> TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T) TTL_APPEND( TTL_LAST_REPEAT_,1)(
/ N" Z: g. W' I! `% ]: ~0 j5 ITTL_TPARAM_END,T)5 p7 ~: m* |7 j4 e8 d& K( O% h" H
这个宏体看起来很复杂,仔细分析下,可以分为两部分:  \, V! d8 ]% l. Y" J% w8 \. X$ {' J
TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T)以及
0 ^* T1 A5 W  yTTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T)+ }. y9 h) }9 Z" }
先分析第一部分:
# z- r' D8 @& H$ D6 e#define TTL_APPEND( x, y ) TTL_APPEND1(x,y) //先展开x,y再将x,y连接起来, u' {8 b" K# w3 @% t
#define TTL_APPEND1( x, y ) x ## y
; w1 o4 h* L. C+ ~#define TTL_DEC(n) TTL_APPEND(TTL_CNTDEC_, n)6 k) _$ r6 W& ~0 |; s4 z
根据先展开参数的原则,会先展开TTL_DEC(1)1 u9 L. u" ?7 ]
=> TTL_APPEND(TTL_CNTDEC_,1) => TTL_CNTDEC_1
# S  b) u. B, \) b8 G$ z0 k#define TTL_CNTDEC_1 0 注意,TTL_CNTDEC_不是宏,TTL_CNTDEC_1是一个宏。- ?7 _3 T0 A% u/ }8 |& P$ x: L  R
=> 0 , 也就是说,TTL_DEC(1)最终被展开为0。回到TTL_APPEND部分:
' |/ Z# ?6 n; t" B) v+ d; |=> TTL_REPEAT_0 (TTL_TPARAM,TTL_TPARAM_END,T)
; J: S3 N' N0 u/ b5 w#define TTL_REPEAT_0(m,l,p)
. Z$ K' E9 z. m- z' `0 ETTL_REPEAT_0这个宏为空,那么,上面说的第一部分被忽略,现在只剩下第二部分:
9 U/ S0 @" E2 \! N1 iTTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T)2 ~- e+ }) X6 a! @: N5 v
=> TTL_LAST_REPEAT_1 (TTL_TPARAM_END,T) // TTL_APPEND将TTL_LAST_REPEAT_和1合并起来
  p( i% t2 B% u( H9 h3 U; y#define TTL_LAST_REPEAT_1(m,p) m(1,p)! o) {# i! s8 ^  i2 S2 e
=> TTL_TPARAM_END( 1, T )( h1 k) ?8 O/ w1 N# e
#define TTL_TPARAM_END(n,t) typename t##n( A8 r% y/ J3 ^3 A" ]/ @
=> typename T1 展开完毕。

虽然我们分析出来了,但是这其实并不是我们想要的。我们应该从那些宏里去获取作者关于宏的编程思想。很好地使用宏
5 B7 f0 s$ O3 C+ p2 D看上去似乎是一些偏门的奇技淫巧,但是他确实可以让我们编码更自动化。

" t+ H' `  c5 g3 |0 I1 Y$ f
 楼主| 发表于 2014-1-3 17:29 | 显示全部楼层
本帖最后由 kenson 于 2014-1-6 16:51 编辑
0 I) D5 O3 |$ \: T) l( l3 P( h' w; [3 O
有关VA_LIST的用法--变参函数的实现' G! v$ V2 |& C5 S& N5 ]2 r

" P1 @2 ?; z" N/ pVA_LIST 是在C语言中解决变参问题的一组宏 VA_LIST的用法:      " e; c3 I9 [( S9 W* C
       (1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针
  e0 S; C1 u7 L4 O0 b6 h& X       (2)然后用VA_START宏初始化变量刚定义的VA_LIST变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。
) g% a  t7 q+ d       (3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型。
0 L$ k5 c3 P! U5 b/ j' {4 N" v       (4)最后用VA_END宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。如果函数有多个可变参数的,依次调用VA_ARG获取各个参数。 VA_LIST在编译器中的处理: (1)在运行VA_START(ap,v)以后,ap指向第一个可变参数在堆栈的地址。
  V. x- @6 |) [(2)VA_ARG()取得类型t的可变参数值,在这步操作中首先apt = sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型*指针,这正是第一个可变参数在堆栈里的地址。然后用*取得这个地址的内容。
1 W- Y. ~5 H1 U3 d1 R! t6 l(3)VA_END(),X86平台定义为ap = ((char*)0),使ap不再指向堆栈,而是跟NULL一样,有些直接定义为((void*)0),这样编译器不会为VA_END产生代码,例如gcc在Linux的X86平台就是这样定义的。 要注意的是:由于参数的地址用于VA_START宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。 使用VA_LIST应该注意的问题:. P. d4 T: E2 O( Y$ [
   (1)因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.# U  X4 b9 C. l2 P. e$ }! W$ H
   (2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
  W3 \, S& Y" _' E! }" p小结:可变参数的函数原理其实很简单,而VA系列是以宏定义来定义的,实现跟堆栈相关。我们写一个可变参数的C函数时,有利也有弊,所 以在不必要的场合,我们无需用到可变参数,如果在C++里,我们应该利用C++多态性来实现可变参数的功能,尽量避免用C语言的方式来实现。 示例代码:
, V' x5 W" n" |#include<stdio.h>" P- X! m+ q; B8 j( T
#include<stdarg.h>. i, j# V0 H5 z* @/ Y
void simple_va_fun(int start,...), p7 c+ b# l4 H7 H/ ]  \. ~
{
7 z3 Q) E5 d8 w7 z     va_list arg_ptr;- ~1 Q! j+ s" _* |7 s$ c! ?& o$ I
     int nArgValue = start;" J6 z8 {! ~1 Y5 t3 y! ?. @
     int nArgCount = 0;% N, y; y8 k7 C/ r  x& z' P% g, N
     va_start(arg_ptr,start);3 c2 [/ S0 c2 I5 |% @. e* \
     do& p: h) _* m  F3 O: Z; H/ P
     {
$ Z4 p( F, t( o3 k( v- v; H# Q     ++nArgCount;0 z" B" s: {7 g, K' J- Z% ]
      printf("the %d the arg:%d\n",nArgCount,nArgValue);0 _0 p! u# O3 q# t( S/ @1 A$ ~7 a
       nArgValue=va_arg(arg_ptr,int);
6 G* H7 t* F* u7 [' ]2 V$ H% L. P" J  ?    }while(nArgValue != -1);& b/ R! @& \! s# @1 V
    return;
9 ]( e  @3 p9 @0 L     }
1 A" E) a% R5 G; Umain()7 J* G) n9 d  p* H& k
{
3 ^6 t# A( [: ?simple_va_fun(100,-1);5 a+ t8 ~& i+ w$ p
simple_va_fun(100,200,-1);, E1 U# r* X3 D' M0 I! G# N5 b
getchar();
! Y* R- ]' e- [. y" ^      }
7 H, F+ K0 x9 ~, i6 ?///////////////////
, t; Y% A! c! F& D0 a: R3 E& pint writeMultiString(void *c, ...)
# n& J3 d& q( H7 f& G{0 m3 n2 h' `$ ?# D7 u- k
    va_list ap;( U+ R+ D6 q1 Q* H" H
    const char *str;
# P3 r! W' y- i% m7 v2 Y9 I- G
! _) V7 f+ T& X% l! w, U/ Y6 \    va_start(ap, c);
& _/ `* N8 g; b" b1 L$ E- ~- R    str = va_arg(ap, const char *);
1 \" m: c* m9 k6 W, D" a/ `/ o, @8 m( E1 Q, f% Q
    while (str != NULL)
" `5 Q' L# Y8 }. h- H" q) X3 p    {; }1 p2 [' d7 j$ I
        fputs(str, (FILE *)c);5 Z+ ~  t9 S* q; L6 X0 d
        str = va_arg(ap, const char *);
" G! C- Y$ ]  j: f) P, c1 Z    }
& l8 I  ], E/ ~* O- f" c
/ v8 K: V/ p, U+ D: c, Z" R2 q8 k9 r    va_end(ap);2 U8 @$ z& S) y% N9 s3 {* g; E
    return 0;
; r( S& F; o1 q6 y# ~2 o}
 楼主| 发表于 2014-1-3 17:29 | 显示全部楼层
本帖最后由 kenson 于 2014-1-6 17:14 编辑 1 G( t, K! g1 U0 ~4 G1 S

$ q& t" [; Z8 I4 Rva_list 详解8 v* |1 V  H( x

. Z/ G  O+ n% p, ?' A* iVA_LIST 是在C语言中解决变参问题的一组宏
6 y1 N3 }5 k, e/ k4 Z
9 k5 z, m6 D# }, q5 s他有这么几个成员:
# e4 D- t# X' q* {# Y$ e4 U7 F/ M8 T. C" \
1) va_list型变量:
! l' V6 W7 o+ |" B# y/ W& K: P0 m# H& }- G' i
#ifdef  _M_ALPHA4 f# Y' x$ n. U$ @; G
typedef struct {
2 q( I% |0 Z! {5 W8 \% F& C6 Z        char *a0;       /* pointer to first homed integer argument */
+ _$ @+ Q  s0 r+ o0 U4 ^2 _( [        int offset;     /* byte offset of next parameter */* O2 p! p! z$ g8 T# u; B7 P
} va_list;
! W, Y/ E) T$ I! ]1 q#else
4 r% q; x& k3 i- g6 i" H- Qtypedef char *  va_list;  v1 M" ]% {9 [5 P2 E0 b: I6 c9 e
#endif8 e6 U& j0 M" [% y+ J( t
6 a5 B. I( ?5 _
2)_INTSIZEOF 宏,获取类型占用的空间长度,最小占用长度为int的整数倍:
7 L7 E5 \# A- ?$ y# }. m
. R) n& ?' p! ^7 j+ N+ P( c) ]#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )' X2 Q# g4 c" }/ O) Y5 N+ K
7 f) [, ~0 }& y8 V8 {* v5 h
3)VA_START宏,获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数):
' d! U3 L" [8 c) K7 s8 D$ r1 j! v) a4 d8 i
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )7 P' Y$ j6 g, N  V" X
' k$ r/ @4 r# ?' |/ H# u
4)VA_ARG宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(t参数描述了当前参数的类型):$ u) q+ h$ ?2 c$ n! O& W
, u& S5 Z2 r6 c# S8 ?( q
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
' V9 Q1 M2 ^% u: q# y  D0 y. F2 \7 L/ S+ y- e" A6 g
5)VA_END宏,清空va_list可变参数列表:" a/ g2 U0 O, U6 C
3 s0 d% y& Z( {3 E2 v0 x0 ~
#define va_end(ap)      ( ap = (va_list)0 )
! H1 Q( R4 ?: R  I- \* V
' C) t! l+ O% XVA_LIST的用法:      9 a8 |! l7 ?* F" m
       (1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
5 L- h% R. z" a8 }+ O% t       (2)然后用VA_START宏初始化变量刚定义的VA_LIST变量;
& L* T# s# N- d' d       (3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);3 m2 {) Z3 j( q% u, ]" @7 M$ r
       (4)最后用VA_END宏结束可变参数的获取。
5 [  o$ z# f' K8 W8 {) A使用VA_LIST应该注意的问题:
9 x# E1 }, a; G4 p4 P/ l   (1)可变参数的类型和个数完全由程序代码控制,它并不能智能地识别不同参数的个数和类型;
( ?5 Z# s* N2 n2 x7 P7 n8 J   (2)如果我们不需要一一详解每个参数,只需要将可变列表拷贝至某个缓冲,可用vsprintf函数;
7 o' B! p, Q( h* e0 j" b- w; y$ N) g   (3)因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码;5 @1 ]" s0 O5 X* G* e# C! {

& |8 G" ?1 N8 l小结:可变参数的函数原理其实很简单,而VA系列是以宏定义来定义的,实现跟堆栈相关。我们写一个可变参数的C函数时,有利也有弊,所 以在不必要的场合,我们无需用到可变参数,如果在C++里,我们应该利用C++多态性来实现可变参数的功能,尽量避免用C语言的方式来实现。

本版积分规则

QQ|一淘宝店|手机版|商店|电子DIY套件|一乐电子 ( 粤ICP备09076165号 ) 公安备案粤公网安备 44522102000183号

GMT+8, 2025-4-28 20:09 , Processed in 0.061705 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表