细说C/C++中的宏(Macro)和宏替换

宏(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++中,它的值是版本号

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值