版权声明
---------------------------------------------------------------------------------------------------------------------
该文章原创于Qter开源社区(www.qter.org)
作者: 女儿叫老白 (白振勇)
转载请注明出处!
---------------------------------------------------------------------------------------------------------------------
课程目录:《C++老鸟日记》目录
本套课程属于:《C++跨平台开发干货》系列课程。
----------------------------------------------------------------------------------------------------------------------
引言:
----------------------------------------------------------------------------
在前面的讨论中,我们使用内联函数代替了预处理宏来减少函数压栈;使用const常量或者constexpr来代替宏常量定义,如此看来,预处理宏好像没啥卵用呢。其实不然,预处理红并非一无是处。我们来看一下预处理宏的几个特征:字符串定义、字符串拼接和标志黏贴。
正文:
----------------------------------------------------------------------------
字符串定义和字符串拼接实际是相互关联的。字符串定义是利用#把标识符替换为把字符串。比如:
#define TRACESS(s) (cerr << __FILE__ << ", line: "<< __LINE__ << ", "<< #s << endl;)
然后在代码中调用它:
TRACESS(f(3));
我们来看一下这个预处理宏:
1, s是标识符
2, cerr是std的标准错误输出(一般指终端)
3, __FILE__宏和__LINE__宏是标准宏,前者输出当前执行的代码的文件名,后者输出当前代码行数。
4, #s用来把标识符转换为字符串输出。比如下面的输出中,f(3)被原封不动的输出到了终端。
它的运行输出如下:
d:\xingdianketang\project\gui\src\chapter00\syncdir\main.cpp, line: 33, f(3)
那么标志黏贴是咋回事呢?标志黏贴实际是将字符串标志替换为代码中的标志,它的语法为:##标志。
比如:
#define COMSTRUCT(COMPO) TK_##COMPO
如果我们调用COMSTRUCT(Breaker),展开后,变成:
TK_Breaker
在项目中的应用如下列代码中的宏MACRO_FUNC_GETCOMPONENT:
----------------------------------------------------------------------------
// 开关
struct TK_Breaker {
qint32 nID; // 序号
Jchar szMingZi[NAMELEN+1]; //名字
qint32 nDZCount; //开关累计动作次数
TK_Breaker() {
wID = 0;
szMingZi[0] = '\0';
nDZCount = 0;
};
};
// 变压器
struct TK_Transformer {
int nID; // 序号
Jchar szMingZi[NAMELEN+1]; //名字
Jfloat fMVA; //额定容量(兆伏安)
TK_Transformer() {
wID = 0;
szMingZi[0] = '\0;'
fMVA = 0.f;
};
};
// 开关
static Juint16 Offset_TK_KaiGuan[] = {
offsetof(TK_KaiGuan, wID),
offsetof(TK_KaiGuan, szMingZi),
offsetof(TK_KaiGuan, nDZCount),
};
// 变压器
static Juint16 Offset_TK_Transformer[] = {
offsetof(TK_Transformer, wID),
offsetof(TK_Transformer, szMingZi),
offsetof(TK_Transformer, fMVA),
};
#define MACRO_FUNC_GETCOMPONENT(COMPNAME) \
Jint32 CData::Get##COMPNAME(int nID, TK_##COMPNAME* p##COMPNAME) \
{ \
filter.Format("ID=%d", nID); \
return GetDBValue(,filter, (Juint8*)p##COMPNAME, Offset_TK_##COMPNAME); \
};
// 获取数据库一条纪录
Jint32 CMSData::GetDBValue(const char* filter, Juint8* base, Juint16* offset)
{
......
return ret;
}
----------------------------------------------------------------------------
在上述代码中,为了避免针对Breaker和Transformer以及更多的部件编写类似代码,特地设计了宏MACRO_FUNC_GETCOMPONENT。
针对Breaker,我们调用MACRO_FUNC_GETCOMPONENT(Breaker)。那么,宏展开后变成:
Jint32 CData::GetBreaker(int nID, TK_Breaker* pBreaker) \
{ \
filter.Format("ID=%d", nID); \
return GetDBValue(filter, (Juint8*)pBreaker, Offset_TK_Breaker); \
};
如果我们调用MACRO_FUNC_GETCOMPONENT(Transformer)。那么,宏展开后变成:
Jint32 CData::GetTransformer (int nID, TK_Transformer * pTransformer) \
{ \
filter.Format("ID=%d", nID); \
return GetDBValue(filter, (Juint8*) pTransformer, Offset_TK_Transformer); \
};
这样,我们就不用针对不同的结构体来编写不同的代码了,而这些代码大部分都重复。
结语:
----------------------------------------------------------------------------
看来,预处理宏还是有它的用武之地的,前提是我们使用得当。
参考资料
----------------------------------------------------------------------------
《C++编程思想》两卷合订本中文版(9.6章节),(美) Bruce Eckel Chuck Allison著