宏的妙用
副标题#e#
1、概述
C++中出了const要害字今后,宏界说常量的成果已经不在被推荐利用。这使 得宏好像没有了用武之地。实际上,宏还可以做许多工作,笔者也难以全部罗列 。这里,仅仅罗列几个典范的用法,但愿各人可以或许从中获益。
2、实现多情况兼容
常见的环境是,我们实现了一个函数,但愿它只在某种编译条件满意是被编译和利用。譬喻,我但愿在源码中插入调试语句,以便以Debug方法运行时可以或许 通过调试信息调查措施运行环境。可是,在产物发售给用户时,我又但愿这些调 试信息不要输出,以低落代码尺寸,提高运行机能。 这一问题的办理要领就是 利用宏。按照条件编译指令,对付差异的编译条件,提供差异的实现。譬喻:我们但愿在特定的位置向日志中写入当前行号和文件名,以判定对应代码是否被执 行到,可以利用下面的宏:
#ifdef _DEBUG
#define TRACE_FILE_LINE_INFO() do{\
CString str;\
str.Format(_T("file=%s,line=%u\r\n",__FILE__,__LINE__);\
CFile file("logfile.txt");\
file.Write(str,str.GetLength());\
}while(0)
#else
#define TRACE_FILE_LINE_INFO()
#endif
上面这段代码通过#ifdef #else #endif三个条件编译 指令,按照_DEBUG界说环境(该宏用于区分DEBUG版本和Release版本),抉择了 详细的TRACE_FILE_LINE_INFO宏函数的实现。利用者可以用如下要领利用
TRACE_FILE_LINE_INFO();//这里显示行号和文本信息
虽然 ,回收其他方法也可以实现这一成果,可是利用宏有以下非凡长处: 只有需要 的代码才会被编译,淘汰了标记表的尺寸,也淘汰了代码尺寸 宏在编译时被展 开,因此用于暗示代码位置的__FILE__,__LINE__宏可以起浸染,假如用函数实 现,这两个宏则不能起浸染。
#p#副标题#e#
3、用新函数替换原有函数
对付一个设计好的函数,假设它已经在一个很大的工程中处处利用,溘然发 现它的一个不敷,想修改它的成果。也许这个新增加的成果需要一个特另外参数 ,可是又不想修改利用这些函数的处所。 假设有两个函数必需成对利用,一个 占用资源并利用,别的一个则释放资源以供其他模块利用。典范的例子是,函数 一(假设为Lock)得到一个全局的锁,这个锁用于掩护在多线程环境下多个线程 对一个民众资源如一个全局变量的会见。问题是,这个Lock函数得到锁今后,其 他线程将不能再得到这个锁,直到当前线程释放这个锁。体例Lock函数的措施员 同时提供了一个 Unlock函数用于释放锁,并要求利用Lock的人必需对应的利用 Unlock。调试措施时,发明线程被死锁,猜疑有人利用完Lock后健忘挪用 Unlock,可是Lock和Unlock在这个大工程中都被遍及的利用,因此设计者但愿 Lock和Unlock都增加两个特另外参数file和line,以说明这两个函数在那边被调 用了,哪些处所被死锁以及哪些处所挪用了Lock可是没有挪用Unlock。 假设这 两个函数的原型为:
void Lock();
void Unlock();
新设计的函数的原型是:
void Lock(LPCTSTR szFileName,UINT uLineNo);
void Unlock(LPCTSTR szFileName,UINT uLineNo);
设计完新的函数后,项目司理但愿所有模块统一利用这两个函数并提供文件 名和行号信息作为参数。这样将是一个很是浩荡且啰嗦的事情,意味着反复性的 劳动、数小时无聊的加班和工期的耽搁,这是谁都不肯意碰着的。 利用宏可以 很是轻松的办理这一切。首先,应该把新设计的函数换个名字,不妨叫它们 NewLock和NewUnlock,也就是他们的原型为:
void NewLock(LPCTSTR szFileName,UINT uLineNo);
void NewUnlock(LPCTSTR szFileName,UINT uLineNo);
这个函数原型应该放在一个头文件中,制止在多个处所反复的声明。需要用 到这两个函数的cpp文件,只要包括他们原型地址的头文件即可。为了不窜改使 用Lock/Unlock函数的模块,在头文件中增加如下两行:
#define Lock() NewLock(__FILE__,__LINE__)
#define Unlock() NewUnlock(__FILE,__LINE__)
这样,当差异模块利用这个函数时,宏替换成果在编译时起浸染,自动利用 了__FILE__和__LINE__为参数,挪用了新设计的函数。调试的时候就可以按照日 志来判定什么处所漏掉了挪用Unlock。
4、给一个函数绑缚其他成果
上述要领修改了本来函数的设计。实际上,这两个函数自己没有问题,只是 利用者利用上出了问题。你大概只需要在调试版本中测试到底谁漏掉了这些重要 信息。对付一些严谨的公司,一旦软件被修改,推出销售前就需要举办严格的测 试。因此项目司理大概不会答允修改原有函数的设计,要求直接绑缚一个测试代 码。产物发售时,删除绑缚代码即可。 利用宏也可以绑缚代码,这需要首先了 解一个宏的特点:假如你的代码中呈现了一个字符串,编译器会首先匹配宏,并 试图用宏展开。这样,纵然你有同名的函数,它也不会被看成函数处理惩罚。可是, 假如一个宏展开时发明,展开式是一个嵌套的宏展开,展开式就试图在进入下一 次嵌套展开之前,试图用函数匹配来终止这种无限轮回。 为此,界说如下两个 宏:
#p#分页标题#e#
#define Lock() Lock();\
TRACE("Lock called in file = %s at line =%u\n",__FILE__,__LINE__)
#define Unlock() Unlock();\
TRACE("Unlock called in file = %s at line =%u\n",__FILE__,__LINE__)
编译器在编译进程中,发明如下代码
//here the Lock function is called
Lock();
它首先把这个Lock领略成宏函数,展开成:
//here the Lock function is called
Lock();
TRACE("Lock called in file = %s at line = %u\n",__FILE__,__LINE__);
上述代码中,__FILE__和__LINE__应该 同时被展开,由于与论题无关,所以照旧原样给出。展开今后,Lock照旧一个和 宏匹配的式子,可是编译器发明假如这样下去,它将是一个无休止的迭代,因此 它遏制展开进程,讯中同名的函数,因此上面的代码已经是最终展开式。 这样 ,我们乐成的不改变Lock函数的原型和设计,绑缚了一条调试信息上去。由于 TRACE语句在Release版本中不会呈现,这样也制止了不起不举办特另外测试进程 。
5、实现一些自动化进程
措施中需要输入一组参数,为此设计了一个对话框来输入。问题是:每次显 示对话框时,都但愿能凭据上次输入的值显示。设计虽然没有问题,在文档中保 存输入的参数,在显示对话框前在把生存的值赋值给对话框对应节制变量。下面 是常见的代码:
CMyDoc * pDoc = GetDocument();
ASSERT_VALID(pDoc);
CParameterDlg dlg;
//配置对话框初值
dlg.m_nValue1 = pDoc->m_nValue1;
dlg.m_szValue2 = pDoc->m_szValue2;
......
dlg.m_lValuen = pDoc->m_lValuen;
//显示对话框
if(dlg.DoModal() == IDOK)
{
//点击OK按钮后生存配置
pDoc->m_nValue1 = dlg.m_nValue1;
pDoc->m_szValue2 = dlg.m_szValue2;
......
pDoc->m_lValuen = dlg.m_lValuen;
}
假如整个措施只有一两个这样的代码段,而且每个代码段涉及 的变量个数都很少,虽然没有问题,可是当你措施中有成百上千个这样的参数对 话框,每个对话框又对应数十个这样的参数,事情量就很是可观了(并且是没有 任何成绩感的事情量)。我想,用VC做界面的伴侣们大多碰着过这样的问题。可 以留意到,上述代码在DoModal前后都是一组赋值进程,可是赋值的偏向不是很 一致,因此每个变量对都需要写两个赋值语句。那么是否可以做一个函数,前后 各挪用一次,按照一个参数抉择偏向。并且函数中也只需要对每个变量写一次?
下面这个函数就是一个实现:
void DataExchange(CMyDoc * pMyDoc,CParameterDlg * pDlg,BOOL flag )
{
BEGIN_EXCHANGE(pMyDoc,CMyDoc,pDlg,CParameterDlg,flag)
EXCHANGE(m_nValue1);
EXCHANGE(m_szValue2);
....
EXCHANGE(m_lValue2);
END_EXCHANGE()
}
为了使上述语义能起浸染,界说上面三个宏如下:
#define BEGIN_EXCHANGE(left,lefttype,right,righttype,flag)\
{\
CSmartPtr<lefttype> pLeft = left;\
CSmartPtr<righttype> pRight = right
#define END_EXCHANGE() }
#define EXCHANGE(varible)\
if(flag)\
{\
pLeft->varible = pRight->varible ;\
}else{\
pRight->varible = pLeft->varible;\
|
这里为了制止每次都输入varible所属工具的指针,利用了 一个智能指针来提供一个左指针pLeft和一个右指针pRight语义,这个智能指针 只需要实现取下标成果即可,因此可以简朴实现如下(为了通用,必需为模板类) :
#p#分页标题#e#
template <typename TYPE>
class CSmartPointer
{
protected:
TYPE * m_pPointer;
public:
CSmartPointer(TYPE * pPointer):m_pPointer(pPointer){};
TYPE* operator->() {return m_pPointer;}
};
这样,本来的代码就可以修改成这样:
CMyDoc * pDoc = GetDocument();
ASSERT_VALID(pDoc);
CParameterDlg dlg;
//配置对话框初值
DataExchange(pDoc,&dlg,FALSE);
//显示对话框
if(dlg.DoModal() == IDOK)
{
//点击OK按钮后生存配置
DataExchange(pDoc,&dlg,TRUE);
}
上述代码要求阁下指针对应变量名必需沟通,假如变量名差异 ,就不能这样利用,需要设计成这样的EXCHANGE2宏:
#define EXCHANGE2(leftvar,rightvar)\
if(flag)\
{\
pLeft->leftvar,pRight->rightvar;\
}else{\
pRight->rightvar = pLeft->leftvar;\
}
这样,对应的EXCHANGE子句需要修改成
EXCHANGE2(m_lValue1,m_dwValue2);
上述代码看起来是完 美的,可是有一些非凡照旧不正确,这些非凡环境就是=用于赋值不正确的环境 。
有两种常见问题:
leftvar和rightvar别离是指针范例,可是其实想拷贝它们指向的缓冲区的内 容(如字符串拷贝)。
为了节制显示精度,对话框节制变量是一个CString工具,它是文档工具中对 应变量的名目化后的信息。最常见的是, leftvar是一个浮点数,需要以几个小 数位名目输出,因此rightvar是一个CString工具。
为了实现上面的目标,就不能利用=来直接赋值,而应该用一个函数Assign( 函数名虽然可以任意取啦)来做这件事。为此,修改上述的EXCHANGE和EXCHANGE2 宏如下:
#define EXCHANGE(var)\
if(flag)\
{\
Assign(pLeft->var,pRight->var);\
}else{\
Assign(pRight->var,pLeft->var);\
}
#define EXCHANGE2(leftvar,rightvar)\
if(flag)\
{\
Assign(pLeft->leftvar,pRight->rightvar);\
}else{\
Assign(pRight->rightvar,pLeft->leftvar);\
}
这样只要针对每个范例对实现一次Assign即可。由于C++允 许重载,这显得很容易。需要实现的函数一般有:
函数 | 成果 |
void Assign(CString & left,CString & right) | 直接赋值CString范例 |
void Assign(CString & left, float & fValue) | 名目化float数值到left |
void Assign(float & fValue,CString & right) | 从字符串中读取出float |
void Assign(CString & left, double& dValue) | 名目化double数值到left |
void Assign(double& dValue,CString & right) | 从字符串中读取出double |
void Assign(CString & left, int & iValue) | 名目化int数值到left |
void Assign(int & iValue,CString & right) | 从字符串中读取出int |
void Assign(CString & left, short& sValue) | 名目化short数值到left |
void Assign(short & sValue,CString & right) | 从字符串中读取出short |
void Assign(CString & left, long & lValue) | 名目化long数值到left |
void Assign(long & lValue,CString & right) | 从字符串中读取出long |
void Assign(CString & left, CTime & time) | 名目化CTime数值到left |
void Assign(CTime & time,CString & right) | 从字符串中读取出CTime |
到底要实现哪些范例对,需要读者按照本身项目需要设计。
小结
宏的成果应该尚有很多,可是我才疏学浅,只能想到这么一点,但愿能对大 家有所辅佐。