C++中的宏

本文深入探讨C++中的宏定义(#define)及取消定义(#undef),包括定义常量、条件编译、宏函数等应用场景,并介绍如何利用宏提高代码效率及维护性。
Technorati 标签: C++, , #define

#define <macro_name> [MACRO_VALUE] <br>#undef <macro_name></macro_name></macro_name>

#define可以用于定义常量、条件编译、宏函数等用法,无论如何使用宏,必须记住其本质,宏只是简单的文本替换,只是将MACRO_NAME直接替换为MACRO_VALUE包含的文本,而#undef用于取消定义。

在工程上,为了便于区分宏与变量,宏一般用大写字母表示,单词之间使用下划线“_”分隔,以便于阅读。

1.1.1. 定义常量

#define PI 3.14

#define ERROR -1

#define UINT16_MAX 65535

因为宏的本质是简单的文本替换,所以本身不包含类型信息,即不保证类型安全,所以很多书上都建议使用常量代替宏[??]。如,上面的UINT16_MAX使用常量表示为:

const unsigned short UNINT16_MAX = 65535;

如果定义一组相关的常量,就可以考虑使用枚举或枚举类(参见??)。

使用宏定义常量,而不是硬编码便于代码维护,例如我们曾经使用一个整数-99表示数据未出始化,后来我们重构代码,想换为-1,只需要改一个宏就可以了,如[??],如果是硬编码,则需要将上百个-99改为-1,虽然现在的多数编辑器都具有搜索替换功能,但谁也不能保证把所有-99替换为-1就可以了,很可能出错。

#define INT_NOT_SUPPORT -1 // from -99

定义常量的宏用法常用于定义一些代码的配置项,一般定义一个config.h文件,然后在该文件中放置一些配置,而在其它代码文件中引用该头件,可以随时根据需要修改配置,重新编译。例如,在实现一个线程池时,可以通过配置文件配置编程池的大小(当然也可以动态配置)。

// config.h

#define THREAD_POOL_SIZE 10

#define THREAD_DEFAULT_STACK_SIZE (2*1024) // 2KB

1.1.2. 条件编译

#define常与#ifdef/#ifndef/defined指令配合使用,用于条件编译,例如我们曾经有个项目使用了boost::asio网络库,其它几个平台都很好,而在HP-UX 11.23上使用HP aC++却不能编译通过,后来使用了Socket++代替了boost::asio,通过条件编译可以方便地选择使用哪个库。

// config.h

#define USING_BOOST_ASIO // 也可以在Makfile或Visual Studio的工程文件中定义

// xxx.cpp

#include “config.h”

int SendTo(const string& ip, const int port, const string& msg)

{

#if defined(USING_BOOST_ASIO

// code using boost::asio

#elif defined(USING_SOCKET_PLUS_PLUS)

// code using socket++

#endif

}

在条件编译时建议使用#if defined和#if !defined来代替使用#ifdef/#ifndef,因为前者更方便处理多分支的情况与较复杂条件表达式的情况,如一段使用#if defined的代码,通过定义操作系统的相关宏OS_HPUX、OS_AIX或操作系统版本HPUX_11_11、HPUX_11_23、HPUX_11_31编译不同的程序代码,以封装底层实现代码,使程序达到可移植的效果。

#if defined(OS_HPUX)&&(defined(HPUX_11_11)|| defined(HPUX_11_23)

// for HP-UX 11.11 and 11.23

#elif defined(OS_HPUX) && defined(HPUX_11_31

// for HP-UX 11.31

#elif defined(OS_AIX)

// for AIX

#else

#endif

如果使用#ifdef将使代码得很难读,如[??],如果再加入新的条件,嵌套的层数越多,代码越难维护,所以在条件编译时尽可能使用#if defined而不是#ifdef。

#ifdef OS_HPUX

# ifdef HPUX_11_11

// for HPUX 11.11

# endif

# ifdef HPUX_11_23

// for HPUX 11.23

# endif

# ifdef HPUX_11_31

// for HPUX 11.31

# endif

#else

# ifdef OS_AIX

// for AIX

# else

# endif

#endif

条件编译时,如果一个文件中太多条件编译的代码,有些编辑器的智能感知可能都不能很好地解析,还是保持代码越简单越好。对于函数级别的条件编译主要有两种实现方式:

(1) 同一个函数声明,同一个函数定义,函数体内使用条件编译代码。这种方式有个问题,如果条件编译代码太多,会导致这个函数体很长,不利于阅读与维护;有一个优点是,有利于编辑器的智能感知,因为这样解析函数名比较方便,但随着编辑器功能的完善,这方面的差别就不明显了。例如[??]的SendTo函数。

(2) 根据编译条件,将编译条件相同的代码放到单独的文件中,这些文件在顶层文件中使用条件编译指令来引用。如[??],在Google的浏览器Chrome源代码中普遍采用了这种方式。这种方式最大的优点就是不同平台的程序由不同的源文件来实现,很便于多人分工合作,对于某一部分代码由一个人实现并测试完成后直接把源文件复制过来就可以了,进行低层次的单元测试非常方便;它的缺点就是增加了目录中的文件数量。

// func.cpp

#if defined(OS_WINDOWS)

#include “func_win.cpp”

#elif defined(OS_LINUX)

#include “func_linux.cpp”

#elif defined(OS_SOLARIS)

#include “func_solaris.cpp”

#else

#error Unknown OS

#endif

// func_win.cpp

void func( void ) { /* implementation for Windows */ }

// func_solaris.cpp

void func( void ) { /* implementation for Solaris */ }

// func_linux.cpp

void func( void ) { /* implementation for Linux */ }

1.1.3. 宏函数

宏参数其实不是函数,而是带参数的宏,通过参数使某些代码可以重用,使代码便于维护,或达到特定目的。

1.1.3.1. 避免函数调用,提高程序效率

常用的就是最大值与最小值的判断函数,由于函数内容并不多,如果定义为函数在调用比较频繁的场合会明显降低程序的效率,其实宏是用空间效率换取了时间效率。如取两个值的最大值:

#define MAX(a,b) ((a)

定义为函数:

inline int Max(int a, int b) { return a

定义为模板:

template <typename t></typename>

inline T TMax(T a, T b) { return a

使用宏函数的优点有两个:(1)适用于任何实现了operator

需要注意的是,由于宏的本质是直接的文本替换,所以在宏函数的“函数体”内都要把参数使用括号括起来,防止参数是表达式时造成语法错误或结果错误,如[??]。

#define MIN( a, b) b

#define SUM( a, b) a + b

cout

int c = SUM(a,b)*2; // c的期望值:16,实际值:13

1.1.3.2. 引用编译期数据

上述的这些作用虽然使用宏函数可以取得更好的性能,但如果从功能上讲完全可以不使用宏函数,而使用模板函数或普通函数实现,但还有些时候只能通过宏实现。例如,程序中在执行某些操作时可能会失败,此时要打印出失败的代码位置,只能使用宏实现。

#define SHOW_CODE_LOCATION() cout

if( 0 != rename(“oldFileName”, “newFileName”) ){

cout

SHOW_CODE_LOCATION();

}

虽然宏是简单的替换,所以在调用宏函数SHOW_CODE_LOCATION时,分号可以直接写到定义里,也可以写到调用处,但最好还是写到调用处,看起来更像是调用了函数,否则看着代码不伦不类,如[??]。

#define SHOW_CODE_LOCATION() cout

if( 0 != rename(“oldFileName”, “newFileName”) ){

cout

SHOW_CODE_LOCATION()

}

1.1.3.3. do-while的秒用

do-while循环控制语句的特点就是循环体内的语句至少会被执行一次,如果while(…)内的条件始终为0时,循环体内的语句就会被执行且只被执行一次,这样的执行效果与直接使用循环体内的代码相同,但这们会得到更多的益处。

#define SWAP_INT(a, b) do{ /

int tmp = a; /

a = b; /

b = tmp; /

}while(0)

int main( void )

{

int x = 3, y = 4;

if( x > y )

SWAP_INT(x, y);

return 0;

}

代码[??],通过do-while代码块的宏定义我们不仅可以把SWAP_INT像函数一样用,而且还有优点:

l 在宏定义中可以使用局部变量;

l 在宏定义中可以包含多个语句,但可以当作一条语句使用,如代码中的if分支语句,如果没有do-while把多条语句组织成一个代码块,则程序的运行结果就不正确,甚至不能编译。

其实我们定义的SWAP_INT(a, b)相当于定义了引用参数或指针参数的函数,因为它可以改变实参的值。在C++0X中有了decltype关键词,这种优势就更显示了,因为在宏中使用了局部变量必须确定变量的类型,所以这个宏只能用于交换int型的变量值,如果换作其它类型则还必须定义新的宏,如SWAP_FLOAT、SWAP_CHAR等,而通过decltype,我们就可以定义一个万能的宏。

#include <iostream></iostream>

using namespace std;

#define SWAP(a, b) do{ /

decltype(a) tmp = a; /

a = b; /

b = tmp; /

}while(0)

int main( void )

{

int a = 1, b = 2;

float f1 = 1.1f, f2 = 2.2f;

SWAP(a, b);

SWAP(f1,f2);

return 0;

}

通过宏实现的SWAP“函数”要比使用指针参数效率还要高,因为它连指针参数都不用传递而是使用直接代码,对于一些效率要求比较明显的场合,宏还是首选。

1.1.4. 取消宏定义(#undef)

#undef指令用于取消前面用#define定义的宏,取消后就可以重新定义宏。该指令用的并不多,因为过多的#undef会使代码维护起来非常困难,一般也只用于配置文件中,用来清除一些#define的开关,保证宏定义的唯一性,如[??]。

// config.h

#undef HAS_OPEN_SSL

#undef HAS_ZLIB

#if defined(HAS_OPEN_SSL)

#endif

#if defined(HAS_ZLIB)

#endif

将对该头文件的引用放到所有代码文件的第一行,就可以保证HAS_OPEN_SSL没有被定义,即使是在编译选项里定义过一宏,也会被#undef指令取消,这样使得config.h就是唯一一处放置条件编译开关的地方,更有利于维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值