C语言宏定义技巧和指针.docx
《C语言宏定义技巧和指针.docx》由会员分享,可在线阅读,更多相关《C语言宏定义技巧和指针.docx(16页珍藏版)》请在冰豆网上搜索。
C语言宏定义技巧和指针
C语言宏定义技巧
1,防止一个头文件被重复包含
2,
3,#ifndefCOMDEF_H
4,
5,#defineCOMDEF_H
6,
7,//头文件内容
8,
9,#endif
10,
11,2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。
12,
13,typedefunsignedcharboolean;/*Booleanvaluetype.*/
14,
15,
16,
17,typedefunsignedlongintuint32;/*Unsigned32bitvalue*/
18,
19,typedefunsignedshortuint16;/*Unsigned16bitvalue*/
20,
21,typedefunsignedcharuint8;/*Unsigned8bitvalue*/
22,
23,
24,
25,typedefsignedlongintint32;/*Signed32bitvalue*/
26,
27,typedefsignedshortint16;/*Signed16bitvalue*/
28,
29,typedefsignedcharint8;/*Signed8bitvalue*/
30,
31,
32,
33,
34,
35,//下面的不建议使用
36,
37,typedefunsignedcharbyte;/*Unsigned8bitvaluetype.*/
38,
39,typedefunsignedshortword;/*Unsinged16bitvaluetype.*/
40,
41,typedefunsignedlongdword;/*Unsigned32bitvaluetype.*/
42,
43,
44,
45,typedefunsignedcharuint1;/*Unsigned8bitvaluetype.*/
46,
47,typedefunsignedshortuint2;/*Unsigned16bitvaluetype.*/
48,
49,typedefunsignedlonguint4;/*Unsigned32bitvaluetype.*/
50,
51,
52,
53,typedefsignedcharint1;/*Signed8bitvaluetype.*/
54,
55,typedefsignedshortint2;/*Signed16bitvaluetype.*/
56,
57,typedeflongintint4;/*Signed32bitvaluetype.*/
58,
59,
60,
61,typedefsignedlongsint31;/*Signed32bitvalue*/
62,
63,typedefsignedshortsint15;/*Signed16bitvalue*/
64,
65,typedefsignedcharsint7;/*Signed8bitvalue*/
66,
67,
68,
69,3,得到指定地址上的一个字节或字
70,
71,#defineMEM_B(x)(*((byte*)(x)))
72,
73,#defineMEM_W(x)(*((word*)(x)))
74,
75,4,求最大值和最小值
76,
77,#defineMAX(x,y)(((x)>(y))?
(x):
(y))
78,
79,#defineMIN(x,y)(((x)<(y))?
(x):
(y))
80,
81,5,得到一个field在结构体(struct)中的偏移量
82,
83,#defineFPOS(type,field)\
84,
85,/*lint-e545*/((dword)&((type*)0)->field)/*lint+e545*/
86,
87,6,得到一个结构体中field所占用的字节数
88,
89,#defineFSIZ(type,field)sizeof(((type*)0)->field)
90,
91,7,按照LSB格式把两个字节转化为一个Word
92,
93,#defineFLIPW(ray)((((word)(ray)[0])*256)+(ray)[1])
94,
95,8,按照LSB格式把一个Word转化为两个字节
96,
97,#defineFLOPW(ray,val)\
98,
99,(ray)[0]=((val)/256);\
100,
101,(ray)[1]=((val)&0xFF)
102,
103,9,得到一个变量的地址(word宽度)
104,
105,#defineB_PTR(var)((byte*)(void*)&(var))
106,
107,#defineW_PTR(var)((word*)(void*)&(var))
108,
109,10,得到一个字的高位和低位字节
110,
111,#defineWORD_LO(xxx)((byte)((word)(xxx)&255))
112,
113,#defineWORD_HI(xxx)((byte)((word)(xxx)>>8))
114,
115,11,返回一个比X大的最接近的8的倍数
116,
117,#defineRND8(x)((((x)+7)/8)*8)
118,
119,12,将一个字母转换为大写
120,
121,#defineUPCASE(c)(((c)>='a'&&(c)<='z')?
((c)-0x20):
(c))
122,
123,13,判断字符是不是10进值的数字
124,
125,#defineDECCHK(c)((c)>='0'&&(c)<='9')
126,
127,14,判断字符是不是16进值的数字
128,
129,#defineHEXCHK(c)(((c)>='0'&&(c)<='9')||\
130,
131,((c)>='A'&&(c)<='F')||\
132,
133,((c)>='a'&&(c)<='f'))
134,
135,15,防止溢出的一个方法
136,
137,#defineINC_SAT(val)(val=((val)+1>(val))?
(val)+1:
(val))
138,
139,16,返回数组元素的个数
140,
141,#defineARR_SIZE(a)(sizeof((a))/sizeof((a[0])))
142,
143,17,返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
144,
145,#defineMOD_BY_POWER_OF_TWO(val,mod_by)\
146,
147,((dword)(val)&(dword)((mod_by)-1))
148,
149,18,对于IO空间映射在存储空间的结构,输入输出处理
150,
151,#defineinp(port)(*((volatilebyte*)(port)))
152,
153,#defineinpw(port)(*((volatileword*)(port)))
154,
155,#defineinpdw(port)(*((volatiledword*)(port)))
156,
157,
158,
159,#defineoutp(port,val)(*((volatilebyte*)(port))=((byte)(val)))
160,
161,#defineoutpw(port,val)(*((volatileword*)(port))=((word)(val)))
162,
163,#defineoutpdw(port,val)(*((volatiledword*)(port))=((dword)(val)))
164,
165,19,使用一些宏跟踪调试
166,
167,ANSI标准说明了五个预定义的宏名。
它们是:
168,
169,_LINE_
170,
171,_FILE_
172,
173,_DATE_
174,
175,_TIME_
176,
177,_STDC_
178,
179,如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。
记住编译程序
180,
181,也许还提供其它预定义的宏名。
182,
183,_LINE_及_FILE_宏指令在有关#line的部分中已讨论,这里讨论其余的宏名。
184,
185,_DATE_宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。
186,
187,源代码翻译到目标代码的时间作为串包含在_TIME_中。
串形式为时:
分:
秒。
188,
189,如果实现是标准的,则宏_STDC_含有十进制常量1。
如果它含有任何其它数,则实现是
190,
191,非标准的。
192,
193,可以定义宏,例如:
194,
195,当定义了_DEBUG,输出数据信息和所在文件所在行
196,
197,#ifdef_DEBUG
198,
199,#defineDEBUGMSG(msg,date)printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
200,
201,#else
202,
203,#defineDEBUGMSG(msg,date)
204,
205,#endif
206,
207,
208,
209,20,宏定义防止使用是错误
210,
211,用小括号包含。
212,
213,例如:
#defineADD(a,b)(a+b)
214,
215,用do{}while(0)语句包含多语句防止错误
216,
217,例如:
#difneDO(a,b)a+b;\
218,
219,a++;
220,
221,应用时:
if(….)
222,
223,DO(a,b);//产生错误
224,
225,else
226,
227,
228,
229,解决方法:
#difneDO(a,b)do{a+b;\
230,
231,a++;}while(0)
232,
233,
234,宏中"#"和"##"的用法
235,一、一般用法
236,我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.
237,用法:
238,#include
239,#include
240,usingnamespacestd;
241,
242,#defineSTR(s)#s
243,#defineCONS(a,b)int(a##e##b)
244,
245,intmain()
246,{
247,printf(STR(vck));//输出字符串"vck"
248,printf("%d\n",CONS(2,3));//2e3输出:
2000
249,return0;
250,}
251,
252,二、当宏参数是另一个宏的时候
253,需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开.
254,
255,1,非'#'和'##'的情况
256,#defineTOW
(2)
257,#defineMUL(a,b)(a*b)
258,
259,printf("%d*%d=%d\n",TOW,TOW,MUL(TOW,TOW));
260,这行的宏会被展开为:
261,printf("%d*%d=%d\n",
(2),
(2),(
(2)*
(2)));
262,MUL里的参数TOW会被展开为
(2).
263,
264,2,当有'#'或'##'的时候
265,#defineA
(2)
266,#defineSTR(s)#s
267,#defineCONS(a,b)int(a##e##b)
268,
269,printf("intmax:
%s\n",STR(INT_MAX));//INT_MAX#include
270,这行会被展开为:
271,printf("intmax:
%s\n","INT_MAX");
272,
273,printf("%s\n",CONS(A,A));//compileerror
274,这一行则是:
275,printf("%s\n",int(AeA));
276,
277,INT_MAX和A都不会再被展开,然而解决这个问题的方法很简单.加多一层中间转换宏.
278,加这层宏的用意是把所有宏的参数在这层里全部展开,那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.
279,
280,#defineA
(2)
281,#define_STR(s)#s
282,#defineSTR(s)_STR(s)//转换宏
283,#define_CONS(a,b)int(a##e##b)
284,#defineCONS(a,b)_CONS(a,b)//转换宏
285,
286,printf("intmax:
%s\n",STR(INT_MAX));//INT_MAX,int型的最大值,为一个变量#include
287,输出为:
intmax:
0x7fffffff
288,STR(INT_MAX)-->_STR(0x7fffffff)然后再转换成字符串;
289,
290,printf("%d\n",CONS(A,A));
291,输出为:
200
292,CONS(A,A)-->_CONS(
(2),
(2))-->int(
(2)e
(2))
293,
294,三、'#'和'##'的一些应用特例
295,1、合并匿名变量名
296,#define___ANONYMOUS1(type,var,line)typevar##line
297,#define__ANONYMOUS0(type,line)___ANONYMOUS1(type,_anonymous,line)
298,#defineANONYMOUS(type)__ANONYMOUS0(type,__LINE__)
299,例:
ANONYMOUS(staticint);即:
staticint_anonymous70;70表示该行行号;
300,第一层:
ANONYMOUS(staticint);-->__ANONYMOUS0(staticint,__LINE__);
301,第二层:
-->___ANONYMOUS1(staticint,_anonymous,70);
302,第三层:
-->staticint_anonymous70;
303,即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;
304,
305,2、填充结构
306,#defineFILL(a){a,#a}
307,
308,enumIDD{OPEN,CLOSE};
309,typedefstructMSG{
310,IDDid;
311,constchar*msg;
312,}MSG;
313,
314,MSG_msg[]={FILL(OPEN),FILL(CLOSE)};
315,相当于:
316,MSG_msg[]={{OPEN,"OPEN"},
317,{CLOSE,"CLOSE"}};
318,
319,3、记录文件名
320,#define_GET_FILE_NAME(f)#f
321,#defineGET_FILE_NAME(f)_GET_FILE_NAME(f)
322,staticcharFILE_NAME[]=GET_FILE_NAME(__FILE__);
323,
324,4、得到一个数值类型所对应的字符串缓冲大小
325,#define_TYPE_BUF_SIZE(type)sizeof#type
326,#defineTYPE_BUF_SIZE(type)_TYPE_BUF_SIZE(type)
327,charbuf[TYPE_BUF_SIZE(INT_MAX)];
328,-->charbuf[_TYPE_BUF_SIZE(0x7fffffff)];
329,-->charbuf[sizeof"0x7fffffff"];
330,这里相当于:
331,charbuf[11];
右左法则----复杂指针解析
因为C语言所有复杂的指针声明,都是由各种声明嵌套构成的。
如何解读复杂指针声明呢?
右左法则是一个既著名又常用的方法。
不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。
C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来
解决如何辩识一个声明的,两者可以说是相反的。
右左法则的英文原文是这样说的:
Theright-leftrule:
Startreadingthedeclarationfromtheinnermostparentheses,goright,andthengoleft.Whenyou
encounterparentheses,thedirectionshouldbereversed.Onceeverythingintheparentheseshasbeen
parsed,jumpoutofit.Continuetillthewholedeclarationhasbeenparsed.
这段英文的翻译如下:
右左法则:
首先从最里面的圆括号看起,然后往右看,再往左看。
每当遇到圆括号时,就应该掉转阅读方向。
一旦解析完圆括号里面所有的东西,就跳出圆括号。
重复这个过程直到整个声明解析完毕。
笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。
现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:
int(*func)(int*p);
首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返
回值类型是int。
int(*func)(int*p,int(*f)(int*));
func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int*和int(*)(int*)这样的形参,返回值为int类型。
再来看一看func的形参int(*f)(int*),类似前面的解释,f也是一个函数指针,指向的函
数具有int*类型的形参,返回值为int。
int(*func[5])(int*p);
func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。
跳出这个括号,看右边,也是一对圆括号,说
明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。
int(*(*func)[5])(int*p);
func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。
总结一下,就是
:
func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。
int(*(*func)(int*p))[5];
func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
要注意有些复杂指针声明是非法的,例如:
intfunc(void)[5];
func是一个返回值为具有5个int元素的数组的函数。
但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C语言的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。
intfunc[5](void);
func是一个具有5个元素的数组,这个数组的元素都是函数。
这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。
作为练习,下面列几个复杂指针声明给读者自己来解析。
int(*(*func)[5][6])[7][8];
int(*(*(*func)(int*))[5])(int*);
int(