C/C++从编辑到生成目标文件的过程中,经历了预处理(#include、#if、#define)、编译、汇编和链接这几个过程,宏替换就是出现在预处理中,宏替换时不做任何的语法检查。由于宏替换时上下文可能存在不同的情况,因此要求对宏替换过程中对存在的变量使用括号包起来,切为了避免歧义,尽量不要在宏中使用自增自减运算符。
一、宏中使用#以及##
说明:“#”将宏中的参数字符串化,“##”将2个token连接为1个
// 输出class的大小
#define OUT_CLASS_SIZE(_Class) do{\
cout << #_Class << " size is:" << sizeof(_Class) << endl;\
}while (0);
// 获取基类的类型
#define GetClassBase(type) C##type##Bass
class CHouseBass{};
class CHouse:public CHouseBass{};
// 以上定义了2个类,CHouse继承于CHouseBass,这里使用GetClassBase宏定义了一个基类的对象
GetClassBase(House) housebassObj;
二、宏在字符串中的使用
在使用C++调用SQLITE的demon中,将宏替换写入了一个字符串中,但运行的时候与预期不一致。
先看代码:
#define _VAR_ARG_20 (20)
#define _VAR_ARG_40 (40)
#define _VAR_ARG_SPEC (20-30)
char *str = "Target Pin[_VAR_ARG_20]:Value[_VAR_ARG_40]:Spec[_VAR_ARG_SPEC]";
编译无问题,运行的时候,str中的各个宏并没有被替换。第一反应是,宏是在预编译的时候进行替换的,不应该存在问题才对。后来仔细想想,char *实际上是一个存放在静态存储区里面的字符串,其实质是一个右值,不允许修改,且其创建是在预编译之前完成的,字符串中的宏被视为普通的字符串不会被替换。
如果要实现字符串的替换,可以使用 # 号连接一个字符串与宏。也可以先使用占位符替换宏,然后以格式化字符的方法使用宏替换占位符达到目的。
三、宏在语言国际化中的使用
//第一版本的语言国际化
// 中文、英文
enum _EN_LANGUAGE{eLanguage_Chinese = 0,eLanguage_English};
_EN_LANGUAGE g_enLanguage = eLanguage_English;
#define defString(no,en,cn) string string_language_no(GetLanguage()?en:cn);
//等价于: string string_language_1101("Welcome To Luran's Home \r\n");
defString(1101,"Welcome To Luran's Home \r\n","欢迎来到卢然之家\r\n")
#define GETSTRING(no) string_language_no
//第二版本的语言国际化:对比第一版本是定义一个全局变量,第二版本定义的是一个string的数组,
//且当不知道上次语言设置时,可以使用 eLanguage_Last 来设置继承上次语言设置
enum _EN_LANGUAGE{eLanguage_Chinese = 0,eLanguage_English,eLanguage_Last};
_EN_LANGUAGE g_enLanguage = eLanguage_English;
#define SetLanguage(language) do {\
if(eLanguage_Last != language)\
g_enLanguage = language ;\
}while(0)
#define GetLanguage() g_enLanguage
#define defString(no,en,cn) static string string_id_##no[eLanguage_Last] = {en,cn};
#define GETSTRING(no) string_id_##no[GetLanguage()]
defString(1101,"Welcome To Luran's Home \r\n","欢迎来到卢然之家\r\n")
int main()
{
SetLanguage(eLanguage_Chinese);
cout << GETSTRING(1101).c_str() << endl;
SetLanguage(eLanguage_English);
cout << GETSTRING(1101).c_str() << endl;
getchar();
return 0;
}
四、获类取成员变量的偏移量
#define OFFSET(structure, member) ((int)&((structure*)0)->member);
典型例子是通过某一类的成员变量的地址来获取该类对象的地址,如linux内核中链表的实现。
五、使用do{….}while(0)
使用do{….}while(0) 将需要的代码包裹起来,成为一个独立的语法单元,可以避免多条语句的情况下报错
#define dosomething(x) x++;x*=2;
if(bok)
dosomething(x); // dosomething扩展后,由于if和else之间存在多条语句,且无{}包裹,导致便宜错误
else
dootherthing();
使用do{}while(0)可以很好的消除以上问题
#define dosome(x) do{\
x++;\
x*=2;\
}while(0)
六、进行类型强转
以下代码将指定地址转换为class_ex类型的对象或该对象的指针
#define addr_to_class_ex( p_addr ) ( *( (class_ex*) (p_addr) ) )
#define addr_to_class_ex( p_addr ) ( ( (class_ex*) (p_addr) ) )
七、防止溢出
#define INC_VAL( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val))
八、返回数组个数
#define ARRAY_NO(arr) ((sizeof(arr)/sizeof(arr[0])))
九、宏调试
_LINE_ /*(两个下划线),对应%d*/
_FILE_ /*对应%s*/
_DATE_ /*对应%s*/
_TIME_ /*对应%s*/
十、宏陷阱
宏使用时由于不能进行类型检查,不能生成调试时的标识符,切存在一些其他副作用,如:
// 当a == b 时,结果将与预期不符
#define swap(a,b) do \
{\
a = a + b;\
b = a - b;\
a = a - b;\
} while (0);
// 当调用max时,如果使用了a++或者b++进行调用,则结果与预期不符
#define max(a,b) ((a)>(b)?(a):(b))
本文探讨了C++中的宏使用,包括宏的字符串化操作`#`和连接操作`##`,宏在字符串和国际化中的应用,如何获取类成员变量的偏移量,使用`do{…}while(0)`避免语法错误,类型强转,防止溢出,计算数组长度的技巧,以及宏在调试中的作用和潜在陷阱。建议在使用宏时谨慎处理,以避免可能的问题。
5739

被折叠的 条评论
为什么被折叠?



