宏(Macro),在C/C++中,是一个颇有争议的话题。在以前的老代码中,我们总是可以看到很多使用得很巧妙的宏,而在大多数的C++教材中,宏的使用都是不被推荐的,因为宏的使用比较容易产生BUG,这些BUG是由一些宏的边界效应(Side effect)所导致,而且这些BUG在调试的过程中都是很难发现的。到底该不该用宏?我的观点是,并不是因为它容易产生BUG就不用它,一定要学会如何用宏、何时用宏。很多时候,巧妙的使用宏,一方面可以减少代码量,一方面还可以提高代码效率。所以,我们现在要做的就是深入、全面了解宏相关的内容,只有充分的掌握了它,才会用好它!
1. 预处理命令(Preprocessor commands)
(1) #define 用来定义一个预处理宏,编译时直接替换
#define PI 3.1415926
在代码中所有出现PI 的地方,都被替换为3.1415926,只是简单的替换,不做任何类型检查,因此,使用者必须确保类型的正确性。
(2)#undef 用来取消已经定义过的一个宏
#undef PI
如果在之前定义过PI,那么,在当前文件中从上面这行代码开始,以及包含上面这行代码所在文件的所有文件中,PI都不再代表3.1415926
(3)#include 用来引入一个要包含的文件
#include
在当前文件中引入stdio.h这个文件。
(4)#if… #endif , #if…#else…#endif, #if…#elif…#else…#endif, 几种条件宏定义的方式,只有在指定的条件成立时才引入该条件块中的预编译语句。这些条件宏定义语句,常和defined搭配在一起使用,但没有必要一定要使用defined。
#if defined(__DEBUG__)
# define Msg(msg) printf("%s\n", msg)
#else
# define Msg(msg)
#endif
上面的语句,只有在__DEBUG__宏定义的时候,Msg才会输出指定的字串信息。
另一种比较常的用法是,用条件语句来注释代码:
#if 0
printf("This is commented\n");
#endif
这样,在该条件语句块中的语句都将不会被执行。
(5)#ifdef…#endif, #ifdef…#else…#endif, #ifndef…#endif, #ifndef…#else…#endif. 这些条件宏定义的用法,和(4)中提到的条件宏与defined搭配在一起的用法差不多。
#ifdef _WIN32
# define STRNCASECMP _strnicmp
#else
# define STRNCASECMP strncasecmp
#endif
上面的宏,定义了一个可跨平台的字符串比较函数,在windows平台上用_strnicmp()实现,在unix上用strncasecmp()实现。
(6)#,字符串替换,把跟在其后面的内容按字符串进行替换。
#define PUTS(s) printf("%s\n", #s)
使用上面的宏,PUTS(a)的替换结果就是
printf("%s\n", "a")
(7)#@,字符替换,把跟在其后面的内容按字符进行替换。
#define PUT(c) printf("%c\n", #@c)
使用上面的宏,PUT(a)的替换结果就是
printf("%c\n", 'c')
(8)##,将两侧的两个token, 连接成为一个。
#define DEFINE_SETTER(name, type, member) \
void Set##name(const type & arg) \
{ \
member = arg; \
}
DEFINE_SETTER(Age, int, m_nAge),相当于定义了一个这样的函数:
void SetAge(const int & arg)
{
m_nAge = arg;
}
在成员变量很多的类中,为了保证比较好封装性,我们假定每个成员变量都是private的。这样我们就需要为每个成员实现对应的Setter和Getter,如果一个个去写,会有看上去很相似的函数,有很大的重复性的工作。此时,便可采用上面的宏的方式,一行搞定一个,看上去就代码比较简洁了。
(10)#error用来输出编译时的一些出错信息。
下面是一个名为Test.cpp的文件:
#include
#define SIZE 129
#if (SIZE % 128) != 0
#error "SIZE must be a multiple of 128!"
#endif
int main()
{
}
对Test.cpp用 g++ -c Test.cpp命令编译,会得到如下错误信息:
Test.cpp:6:2: error: #error “SIZE must be a multiple of 128!”
(11)#line 用来暗示编译器,当前代码的这一行是由用户写的代码中某个文件中的某一行生成的。
(12)#pragma 用来给编译器指定与实现相关的一些信息,在所有的预处理命令中,#pragma是最为复杂的,下面对其进行比较详细的说明:
#pragma的使得语法为: #pragma para, 其中para为其参数,下面介绍一些常用的参数。
A. #pragma message(“text”) 在编译信息输出窗口中输出text信息
B. #pragma once 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,
但是考虑到兼容性并没有太多的使用它。
C. #pragma hdrstop 表示预编译头文件到此为止,后面的头文件不进行预编译。
D. #pragma resource “*.dfm” 表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体外观的定义。
E. #pragma warning( disable: 4507 34; once: 4385; error: 164 ),
等价于:
#pragma warning( disable: 4507 34 ) // 不显示4507和34号警告信息
#pragma warning( once: 4385 ) // 4385号警告信息仅报告一次
#pragma warning( error: 164 ) // 把164号警告信息作为一个错误。
F. #pragma comment(…) 该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。如: #pragma comment(lib, “comctl32.lib”)
G. #pragma pack( [ n ] ) 该指令指定结构和联合成员的紧凑对齐。
当使用#pragma pack ( n ) 时, 这里n 为1、2、4、8 或16。当使用#pragma pack时,按缺省设置进行对齐。
还有一种加强型语法:#pragma pack( [ [ { push | pop } , ] [ identifier, ] ] [ n] )
2.预定义的宏(Predefined Macros)
(1)__LINE__ 当前代码行的行号(整数)
(2)__FILE__ 当前代码所在的文件的文件名(字符串)
(3)__TIME__ 当前时间(hh:mm:ss)
(4)__DATE__ 当前日期(Mmm dd yyyy)
(5)__STDC__ 如果编译器的实现兼容ISO C,那么,该宏被定义为常量1
(6)__STDC_VERSION__ C89:199409L; C99:199901L, otherwise, not defined
(7)__cplusplus C++编译器会预定义这个宏,在标准C++中,它的值是版本号