本帖最后由 baizy77 于 2018-10-1 21:00 编辑
版权声明 --------------------------------------------------------------------------------------------------------------------- 作者: 女儿叫老白 (白振勇) 转载请注明出处! --------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------
引言: ---------------------------------------------------------------------------- 在前面的讨论中,我们使用内联函数代替了预处理宏来减少函数压栈;使用const常量或者constexpr来代替宏常量定义,如此看来,预处理宏好像没啥卵用呢。其实不然,预处理红并非一无是处。我们来看一下预处理宏的几个特征:字符串定义、字符串拼接和标志黏贴。
正文: ---------------------------------------------------------------------------- 字符串定义和字符串拼接实际是相互关联的。字符串定义是利用#把标识符替换为把字符串。比如: #defineTRACESS(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: ---------------------------------------------------------------------------- // 开关 structTK_Breaker { qint32 nID; // 序号 Jchar szMingZi[NAMELEN+1]; //名字 qint32 nDZCount; //开关累计动作次数
TK_Breaker() { wID = 0; szMingZi[0] = '\0'; nDZCount = 0; }; };
// 变压器 structTK_Transformer { int nID; // 序号 Jchar szMingZi[NAMELEN+1]; //名字 Jfloat fMVA; //额定容量(兆伏安)
TK_Transformer() { wID = 0; szMingZi[0] = '\0;' fMVA = 0.f; }; };
// 开关 static Juint16Offset_TK_KaiGuan[] = { offsetof(TK_KaiGuan, wID), offsetof(TK_KaiGuan, szMingZi), offsetof(TK_KaiGuan, nDZCount), };
// 变压器 static Juint16Offset_TK_Transformer[] = { offsetof(TK_Transformer, wID), offsetof(TK_Transformer, szMingZi), offsetof(TK_Transformer, fMVA), };
#define MACRO_FUNC_GETCOMPONENT(COMPNAME) \ Jint32 CData::Get##COMPNAME(intnID, TK_##COMPNAME* p##COMPNAME) \ { \ filter.Format("ID=%d", nID); \ return GetDBValue(,filter,(Juint8*)p##COMPNAME, Offset_TK_##COMPNAME); \ }; // 获取数据库一条纪录 Jint32CMSData::GetDBValue(const char* filter, Juint8* base, Juint16* offset) { ...... return ret; } ---------------------------------------------------------------------------- 在上述代码中,为了避免针对Breaker和Transformer以及更多的部件编写类似代码,特地设计了宏MACRO_FUNC_GETCOMPONENT。 针对Breaker,我们调用MACRO_FUNC_GETCOMPONENT(Breaker)。那么,宏展开后变成: Jint32 CData::GetBreaker(intnID, 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章节),(美) BruceEckel Chuck Allison著
|