作者:刘巍
Tuesday
,July920023
:15PM
在上一篇文章中,我演示了几个常用的
宏定义和预处理指令,但可以说这些都是相当常规的技巧。下面要介绍的
宏定义与预处理指令的用法也是ATL,MFC以及
LINUX中使用得比较多的非常重要的技巧。
##连接符与#符
## 连接符号由两个井号组成,其功能是在带参数的
宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。具体的定义在编译原理里有详尽的解释,但不知道也无所谓。同时值得注意的是#符是把传递过来的参数当成字符串进行替代。下面来看看它们是怎样工作的。这是MSDN上的一个例子。
假设程序中已经定义了这样一个带参数的宏:
#definepaster(n)printf("token"#n"=%d",token##n)
同时又定义了一个整形变量:
inttoken9
=9
;现在在主程序中以下面的方式调用这个宏:
paster
(9
);那么在编译时,上面的这句话被扩展为:
printf
("token""9""=%d"
,token9
);注意到在这个例子中,paster
(9
);中的这个”9”被原封不动的当成了一个字符串,与”token”连接在了一起,从而成为了token9。而#n也被”9”所替代。
可想而知,上面程序运行的结果就是在屏幕上打印出token9
=9
在ATL的编程中,我们查看它的源代码就会经常看见这样的一段:
#defineIMPLEMENTS_INTERFACE(Itf)/
{&IID_##Itf,ENTRY_IS_OFFSET,BASE_OFFSET(_ITCls,Itf)},
我们经常不假思索的这样使用它:
……
IMPLEMENTS_INTERFACE
(ICat
)……
实际上IID_ICat已经在别的地方由ATL向导定义了。当没有向导的时候,你只要遵循把IID_加在你的接口名前面来定义GUID的规则就也可以使用这个宏。在实际的开发过程中可能很少用到这种技巧,但是ATL使用得如此广泛,而其中又出现了不少这样的源代码,所以明白它是怎么一回事也是相当重要的。我的一个朋友就是因为不知道IMPLEMENTS_INTERFACE宏是怎么定义的,而又不小心改动了IID_ICat的定义而忙活了一整天。
LINUX的怪圈
在刚开始阅读
LINUX的时候有一个小小的宏让我百思不得其解:
#definewait_event(wq,condition)/
do{/
if(condition)/
break;/
__wait_event(wq,condition);/
}while(0)
这是一个奇怪的循环,它根本就只会运行一次,为什么不去掉外面的do
{..}while结构呢?我曾一度在心里把它叫做“怪圈”。原来这也是非常巧妙的技巧。在工程中可能经常会引起麻烦,而上面的定义能够保证这些麻烦不会出现。下面是解释:
假设有这样一个
宏定义#definemacro(condition)/
if
(condition
)dosomething
();现在在程序中这样使用这个宏:
if
(temp
)macro
(i
);else
doanotherthing
();一切看起来很正常,但是仔细想想。这个宏会展开成:
if
(temp
)if
(condition
)dosomething
();else
doanotherthing
();这时的else不是与第一个if语句匹配,而是错误的与第二个if语句进行了匹配,编译通过了,但是运行的结果一定是错误的。
为了避免这个错误,我们使用do
{…
.}while
(0
)把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do
{…
}while
(0
)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低。
几小小的警告
正如微软声称的一样,
宏定义与预编译器指令是强大的,但是它又使得程序难以调试。所以在定义宏的时候不要节省你的字符串,一定要力争完整的描述这个宏的功能。同时在定义宏的时候如有必要(比方使用了if语句)就要使用do
{…
}while
(0
)将它封闭起来。在
宏定义的时候一定要注意各个宏之间的相互依赖关系,尽量避免这种依赖关系的存在。下面就有这样一个例子。
设有一个静态数组组成的整型队列,在定义中使用了这样的方法:
intarray
[]={5
,6
,7
,8
};我们还需要在程序中遍历这个数组。通常的做法是使用一个
宏定义#defineELE_NUM4
…………………………
..……………………………
..for
(intI
=0
;I
<ELE_NUM
;I
++)
{cout
<<array
[I
];
}由于某种偶然的原因,我们删除了定义中的一个元素,使它变成:
array
[]={5
,6
,7
}而却忘了修改ELE_NUM的值。那么在上面的代码中马上就会发生访问异常,程序崩溃。然后是彻夜不眠的调试,最后发现问题出在这个
宏定义上。解决这个问题的方法是不使用
array
[]={…
.}这样的定义,而显式的申明数组的大小:
array
[ELE_NUM
]={…
.}这样在改动数组定义的时候,我们就不会不记得去改
宏定义了。总之,就是在使用
宏定义的时候能够用
宏定义的地方统统都用上。
我发现的另一个有趣的现象是这样的:
假设现在有一个课程管理系统,学生的人数用
宏定义为
#defineSTU_NUM50
而老师的人数恰好也是50人,于是很多人把所有涉及到老师人数的地方通通用上STU_NUM这个宏。另一个学期过去,学生中的一个被开除了,系统需要改变。怎么办呢?简单的使用#defineSTU_NUM49么?如果是这样,一个老师也就被开除了,我们不得不手工在程序中去找那些STU_NUM宏然后判断它是否是表示学生的数目,如果是,就把它改成49。天哪,这个
宏定义制造的麻烦比使用它带来的方便还多。正确的方法应该是为老师的数目另外定义一个宏:
#defineTEA_NUM50
当学生的数目改变以后只要把STU_NUM定义为49就完成了系统的更改。所以,当程序中的两个量之间没有必然联系的时候一定不要用其中的一个宏去替代另一个,那只会让你的程序根本无法改动。
最后,建议C
/C
++语言的初学者尽可能多的在你的程序中使用
宏定义和预编译指令。多看看MFC,ATL或者
LINUX的源代码,你会发现C语言强大的原因所在。
--------------------------------------------------------------------------------刘巍-高级程序员、软件设计师,精通VC
,VB
,COM
,PLATFORMSDK
,DDK,TCP
/IP。独立开发过多种windows应用程序,WDMUSB驱动程序,网络应用程序。熟悉加密技术,熟悉
LINUX下的C
/C
++开发。
loading...
分享到:
相关推荐
预处理和宏定义是C语言的一个强大工具,使用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能。
VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量VC中如何定义全局变量...
一个很简单的代码 ...而且程序中如果有需要获得程序版本集信息的,只须使用Define.h中预先定义好的宏就可以了,而不用去查MSDN去调用一些复杂的API函数来获得版本号等。 具体请看代码,VC2008的工程
预定义,通常用它来定义常量(包括无参量与带参量),以及用来实现那些“表面似和善、背后一长串”的宏,它本身并不在编译过程中进行,而是在这之前(预处理过程)就已经完成了 3、#typedef指令 常用来定义一个标识符...
MFC 宏定义的详解,从此那些莫名其妙的宏在你面前了无秘密! <br>有了这份文档,MFC的宏就像是被扒光了衣服的女人
运用VC对数字图像进行处理,包括用DIB类显示图像,还包括一些图像的预处理:图像的灰度变化,直方图变化,二值变化,图像滤波(中值滤波,均值滤波),边缘检测等。
VC三维重建中图像预处理技术的实现.doc
使用这个文件可以高亮C++中的宏与结构体以及所有大写的标识符,也适用于Asm语法高亮。
VC 使用MMX指令集的例子,通过点击MMX按钮来调用相关的MMX指令集,具体请下载测试。
利用VC_开发图像采集卡与图像预处理库,利用VC_开发图像采集卡与图像预处理库
VC定义光标热区 VC定义光标热区 VC定义光标热区
VC++中,SAMPLE.DSM宏文件的使用说明,快速给函数添加描述说明框架,快速给多行代码进行注释
VC图像处理 DIB文件读写 预处理算法
ipconfig指令的实例,改了一下,需要什么兄弟姐妹们可以改回来~~
vc调试宏,详细讲解vc调试代码。对于对vc有一定了解,希望继续提高的同学很有帮助!
VC宏Macros收集,内含多个宏文件,有时候VC宏是一个很用用工具或命令!
vc2008路径宏。对于初学者来说,工程属性里有些宏会比较生涩,这个文档对你可能会有帮助。
VC6.0 多个常见错误指令 及其解决方法
值得一看学习VC MFC开发必须了解的常用宏和指令