代码改变世界

短小精悍的宏(整理篇)

2013-04-27 11:57  钱吉  阅读(3045)  评论(0编辑  收藏  举报

c/c++里面常常需要定义一些宏,以提高编程效率和调试,同时使得代码结构简练。这里从网上查找了一下,并集中整理,方便以后使用。个别地方加了一点自己的注释(红色字体表示)。其实都是些小儿科的编程技巧了,请勿见笑,只是想方便像我这样的新手共同学习。

一:#、##和__VA_ARGS__

(转自:http://www.cnblogs.com/zhujudah/admin/EditPosts.aspx?opt=1)

1.#
假如希望在字符串中包含宏参数,ANSI C允许这样做,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化成字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化(stringizing).

#incldue <stdio.h>
#define PSQR(x) printf("the square of" #x "is %d.\n",(x)*(x))
int main(void)
{
    int y =4;
    PSQR(y);
    PSQR(2+4);
    return 0;
}

输出结果:

the square of y is 16.
the square of 2+4 is 36.
第一次调用宏时使用"y"代替#x;第二次调用时用"2+4"代#x。


2.##
##运算符可以用于类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:#define XNAME(n) x##n
这样宏调用:
XNAME(4)
展开后:
x4
程序:

#include <stdio.h>
#define XNAME(n) x##n
#define PXN(n) printf("x"#n" = %d\n",x##n)
int main(void)
{
    int XNAME(1)=12;//int x1=12;
    PXN(1);//printf("x1 = %d\n", x1);
    return 0;
}

输出结果:x1=12

 

关于#和##补充一点,来自《C语言高级编程》

当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开.
1, 非'#'和'##'的情况
#define TOW(2)
#define MUL(a,b) (a*b)
printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
这行的宏会被展开为:
printf("%d*%d=%d\n", (2), (2), ((2)*(2)));
MUL里的参数TOW会被展开为(2).
2, 当有'#'或'##'的时候
#define A(2)
#define CONS(a,b)int(a##e##b)
这行会被展开为:
printf(“%s\n”, CONS(A, A));// compile error
这一行则是:printf("%s\n", int(AeA));
INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏. 加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.
#define A(2)
#define _CONS(a,b)int(a##e##b)
#define CONS(a,b)_CONS(a,b)// 转换宏
printf("%d\n", CONS(A, A));
输出为:200
CONS(A, A)-->_CONS((2), (2))--> int((2)e(2))

 

3.可变参数宏 ...和__VA_ARGS__
__VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)(我用的vc2008也可以)。
实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏__VA_ARGS__就可以被用在替换部分中,替换省略号所代表的字符串。比如:

#define PR(...) printf(__VA_ARGS__)
int main()
{
    int wt=1,sp=2;
    PR("hello\n");
    PR("weight = %d, shipping = %d",wt,sp);
    return 0;
}

(我的注释:__VA_ARGS__这个宏的头尾是两个下划线,包括后面提到的另外的一些编译器内置宏,不要搞错了哦。)

输出结果:
hello
weight = 1, shipping = 2
省略号只能代替最后面的宏参数。
#define W(x,...,y)错误!

(可变宏的另外一个在调试过程中很给力的用途,这篇文章里有个例子:http://blog.csdn.net/hinyunsin/article/details/6546670,现在整理如下,感谢原作者!)

编译器内置宏:

先介绍几个编译器内置的宏定义,这些宏定义不仅可以帮助我们完成跨平台的源码编写,灵活使用也可以巧妙地帮我们输出非常有用的调试信息。

ANSI C标准中有几个标准预定义宏(也是常用的):

__LINE__:在源代码中插入当前源代码行号;

__FILE__:在源文件中插入当前源文件名;

__DATE__:在源文件中插入当前的编译日期

__TIME__:在源文件中插入当前编译时间;

__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;

__cplusplus:当编写C++程序时该标识符被定义。

编译器在进行源码编译的时候,会自动将这些宏替换为相应内容。

看到这里,你的眼睛应该一亮了吧,嗯,是的,__FILE__和__LINE__正是我们前面想要的输出的,于是,我们的每一条语句都变成了:

DEBUG("FILE: %s, LINE: %d…",__FILE__,__LINE__,…)

其实没有必要,__FILE__本身就会被编译器置换为字符常量,于是乎我们的语句又变成了这样:

DEBUG("FILE:"__FILE__", LINE: %d…",__LINE__,…)

但是,我们还是不满足,依然发现,还是很讨厌,为什么每条语句都要写"FILE:"__FILE__", LINE: %d 以及,__LINE,这两个部分呢?这不是浪费我们时间么?

哈哈,是的,这就是本次大结局,把DEBUG写成这样:

DEBUG(format,...) printf("FILE: "__FILE__", LINE: %d: "format"/n", __LINE__, ##__VA_ARGS__)

没错,就是这样!下面,所有的DEBUG信息都会按照这样的方式输出:

FILE: xxx, LINE: xxx, …….

最后:

最后,老规矩,coding测试。
//============================================================================
// Name : debug.cpp
// Author : boyce
// Version : 1.0
// Copyright : pku
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <stdio.h>

#define __DEBUG__

#ifdef __DEBUG__
#define DEBUG(format,...) printf("File: "__FILE__", Line: %05d: "format"\n", __LINE__, ##__VA_ARGS__)
#else
#define DEBUG(format,...)
#endif

int main(int argc, char **argv) {
    char str[]="Hello World";
    DEBUG("A ha, check me: %s",str);
    return 0;
}

输出结果:

(关于上面的DEBUG宏里面的__FILE__,__LINE__,我本以为可以这样改:

#define DEBUG(format,...) printf("File: "__FILE__", Line: "__LINE__", "format"\n", ##__VA_ARGS__)
即让编译器自动置换__LINE__宏,但是编译错误,我想可能是编译器不会像__FILE__宏一样,自己主动置换这个宏,而是需要你去格式化输出,所以这样也许会报错:
printf("File: "__FILE__", Line: "__LINE__"\n");
果然报错:error C2064: 项不会计算为接受 1 个参数的函数。所以用的时候还是老老实实按照格式化输出的形式写吧,呵呵,这个小问题在这里备注一下。

另外代码中的这种形式:#define CCLOG(format, ...)      cocos2d::CCLog(format, ##__VA_ARGS__),' ## '的意思是,如果可变参数被忽略或为空,将使预处理器( preprocessor )去除掉它前面的那个逗号

关于#和##,还可以看看这篇博客:http://www.cnblogs.com/morewindows/archive/2011/08/18/2144112.html,讲的比较详细)


4 计算结构体中成员变量的偏移
#define offset(s,m) (size_t)&(((s*)0)->m)

  


二:C++中有用的一些宏定义
1,命名空间使用的宏定义
最近在看cocos2d-x的代码,有些地方写的很有意思(我自己以前代码写的很少,可能少见多怪了),所以记录下来。其实这也不算技巧了,对于做事讲究省布料的人,可以这么做:
#define NS_CC_BEGIN                     namespace cocos2d {
#define NS_CC_END                       }
#define USING_NS_CC                     using namespace cocos2d

用的时候,可以这样写:

/****custom head file***/
//**.h

NS_CC_BEGIN
//add your code here
NS_CC_END

//**.cpp
#include "**.h"
USING_NS_CC;
//add your code here

2,成员变量和函数的定义

#define CC_PROPERTY_READONLY(varType, varName, funName)\
protected: varType varName;\
public: virtual varType get##funName(void);

#define CC_PROPERTY_READONLY_PASS_BY_REF(varType, varName, funName)\
protected: varType varName;\
public: virtual const varType& get##funName(void);

再看一个:

#define CC_PROPERTY(varType, varName, funName)\
protected: varType varName;\
public: virtual varType get##funName(void);\
public: virtual void set##funName(varType var);

#define CC_PROPERTY_PASS_BY_REF(varType, varName, funName)\
protected: varType varName;\
public: virtual const varType& get##funName(void);\
public: virtual void set##funName(const varType& var);

这样的宏定义是不是很奇葩?但很好懂,而且比较节省体力,方便为类声明一些成员,同时提供外部读或写接口。写在这里借鉴下。

三:常用宏

这个也是来自cocos2d-x的代码,在头文件"CCPlatformMacros.h"中。

 

#define CC_SAFE_DELETE(p)            if(p) { delete (p); (p) = 0; }
#define CC_SAFE_DELETE_ARRAY(p)    if(p) { delete[] (p); (p) = 0; }
#define CC_SAFE_FREE(p)            if(p) { free(p); (p) = 0; }
#define CC_SAFE_RELEASE(p)            if(p) { (p)->release(); }
#define CC_SAFE_RELEASE_NULL(p)    if(p) { (p)->release(); (p) = 0; }
#define CC_SAFE_RETAIN(p)            if(p) { (p)->retain(); }
#define CC_BREAK_IF(cond)            if(cond) break;

 

 

 

总结:宏这个玩意是把双刃剑,因为编译器不会提供类型安全检查,只是进行替换,所以很容易将一些错误隐藏起来,总之,要在能把控的情况下玩弄,否则磨刀出鞘,会嗜血的!

暂时整理这几个,以后有其它的再添加进来。错误之处请大家使劲指点,我会像海绵一样吸纳!!