预处理就是在进行编译的第一遍词法扫描和语法分析之前所作的工作。说白了,就是对源文件进行编译前,先对预处理部分进行处理,然后对处理后的代码进行编译。这样做的好处是,经过处理后的代码,将会变的很精短。
关于预处理命令中的文件包含(#include),宏定义(#define),条件编译(#ifdef,#else,#endif,#if等)
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
TRACE_FILE_LINE_INFO();//这里显示行号和文本信息
当然,采用其他方式也可以实现这一功能,但是使用宏有以下特殊好处: 只有需要的代码才会被编译,减少了符号表的尺寸,也减少了代码尺寸 宏在编译时被展开,因此用于表示代码位置的__FILE__,__LINE__宏可以起作用,如果用函数实现,这两个宏则不能起作用。
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);
void NewLock(LPCTSTR szFileName,UINT uLineNo); void NewUnlock(LPCTSTR szFileName,UINT uLineNo);
#define Lock() NewLock(__FILE__,__LINE__) #define Unlock() NewUnlock(__FILE,__LINE__)
这样,当不同模块使用这个函数时,宏替换功能在编译时起作用,自动使用了__FILE__和__LINE__为参数,调用了新设计的函数。调试的时候就可以根据日志来判断什么地方遗漏了调用Unlock。
4、给一个函数捆绑其他功能
上述方法修改了原来函数的设计。实际上,这两个函数本身没有问题,只是使用者使用上出了问题。你可能只需要在调试版本中测试到底谁遗漏了这些重要信息。对于一些严谨的公司,一旦软件被修改,推出销售前就需要进行严格的测试。因此项目经理可能不会允许修改原有函数的设计,要求直接捆绑一个测试代码。产品发售时,删除捆绑代码即可。 使用宏也可以捆绑代码,这需要首先了解一个宏的特点:如果你的代码中出现了一个字符串,编译器会首先匹配宏,并试图用宏展开。这样,即使你有同名的函数,它也不会被当作函数处理。但是,如果一个宏展开时发现,展开式是一个嵌套的宏展开,展开式就试图在进入下一次嵌套展开之前,试图用函数匹配来终止这种无限循环。
为此,定义如下两个宏:
#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 is called Lock();
//here the Lock 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_n1 = pDoc->m_n1; dlg.m_sz2 = pDoc->m_sz2; ...... dlg.m_ln = pDoc->m_ln; //显示对话框 if(dlg.DoModal() == IDOK) { //点击OK按钮后保存设置 pDoc->m_n1 = dlg.m_n1; pDoc->m_sz2 = dlg.m_sz2; ...... pDoc->m_ln = dlg.m_ln; }
下面这个函数就是一个实现:
void DataExchange(CMyDoc * pMyDoc,CParameterDlg * pDlg,BOOL flag ) { BEGIN_EXCHANGE(pMyDoc,CMyDoc,pDlg,CParameterDlg,flag) EXCHANGE(m_n1); EXCHANGE(m_sz2); .... EXCHANGE(m_l2); 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;/ |
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); }
#define EXCHANGE2(leftvar,rightvar) / if(flag)/ {/ pLeft->leftvar,pRight->rightvar;/ }else{/ pRight->rightvar = pLeft->leftvar;/ }
EXCHANGE2(m_l1,m_dw2);
有两种常见问题:
- leftvar和rightvar分别是指针类型,但是其实想拷贝它们指向的缓冲区的内容(如字符串拷贝)。
- 为了控制显示精度,对话框控制变量是一个CString对象,它是文档对象中对应变量的格式化后的信息。最常见的是, leftvar是一个浮点数,需要以几个小数位格式输出,因此rightvar是一个CString对象。
#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 & f) | 格式化float数值到left |
void Assign(float & f,CString & right) | 从字符串中读取出float |
void Assign(CString & left, double& d) | 格式化double数值到left |
void Assign(double& d,CString & right) | 从字符串中读取出double |
void Assign(CString & left, int & i) | 格式化int数值到left |
void Assign(int & i,CString & right) | 从字符串中读取出int |
void Assign(CString & left, short& s) | 格式化short数值到left |
void Assign(short & s,CString & right) | 从字符串中读取出short |
void Assign(CString & left, long & l) | 格式化long数值到left |
void Assign(long & l,CString & right) | 从字符串中读取出long |
void Assign(CString & left, CTime & time) | 格式化CTime数值到left |
void Assign(CTime & time,CString & right) | 从字符串中读取出CTime |
问题一: #if ,#ifdef与#if define的区别
对于条件编译#if,#ifdef和#if defined这些,深究起来还是有些意思
#if要求是一个表达式,为真则执行以下代码,
#ifdef则只要求是否定义,并不关心这个值是什么。
#if defined则是#if和#define的连用。
比如:
#if 0
/* some code here*/
/* commented */
#endif
#ifdef __MMI_MESSAGES_CLUB__
#include "MessagesResDef.h"
#endif
#if defined( __MMI_IRDA_SUPPORT__ )
#include "ConnectivityResDef.h "
#endif
#if defined还可以串连多个条件:
#if defined( __MMI_MYAPP1_SUPPORT__ ) || defined( __MMI_MYAPP2_SUPPORT__ )
#include "ConnectivityResDef.h "
#endif
问题二:VC++预定义宏
在MSDN中输入“__LINE__”,就可以进入“Predefined Macros”的相关界面。
我这里介绍一下它的简单用法(在写程序的日志文件时很有用,这里用MessageBox来替代写文件)
#include <afx.h>
#include <iostream>
using namespace std;
//////////////////////////////////////
//主函数部分
int main()
{
string str = __FILE__;//当前文件名
int i = __LINE__;//当前所在行数
string time = __TIME__;//系统时间 (时、分、秒)
string timestamp = __TIMESTAMP__;//系统时间 (年月日小时分秒)
CString sss;
sss = str.c_str(); //将string类型转换成CString类型
MessageBox(NULL,sss,"Test",MB_OK);
cout<<i<<endl;
sss = time.c_str();
MessageBox(NULL,sss,"Test",MB_OK);
sss = timestamp.c_str();
MessageBox(NULL,sss,"Test",MB_OK);
char ch;
cin>>ch;
return 0;
}
====================================================================
这些宏有:
Predefined Macros
The compiler recognizes six predefined ANSI C macros (see Table 1.1), and the Microsoft C++ implementation provides several more (see Table 1.2). These macros take no arguments and cannot be redefined. Their value (except for __LINE__ and __FILE__) must be constant throughout compilation. Some of the predefined macros listed below are defined with multiple values. Their values can be set by selecting the corresponding menu option in the Visual C++ development environment, or by using a command-line switch. See the tables below for more information.
Table 1.1 ANSI Predefined Macros
Macro | Description |
__DATE__ | The compilation date of the current source file. The date is a string literal of the form Mmm dd yyyy. The month name Mmm is the same as for dates generated by the library function asctime declared in TIME.H. |
__FILE__ | The name of the current source file. __FILE__ expands to a string surrounded by double quotation marks. |
__LINE__ | The line number in the current source file. The line number is a decimal integer constant. It can be altered with a #line directive. |
__STDC__ | Indicates full conformance with the ANSI C standard. Defined as the integer constant 1 only if the /Za compiler option is given and you are not compiling C++ code; otherwise is undefined. |
__TIME__ | The most recent compilation time of the current source file. The time is a string literal of the form hh:mm:ss. |
__TIMESTAMP__ | The date and time of the last modification of the current source file, expressed as a string literal in the form Ddd Mmm Date hh:mm:ss yyyy, where Ddd is the abbreviated day of the week and Date is an integer from 1 to 31. |
Table 1.2 Microsoft-Specific Predefined Macros
Macro | Description |
_CHAR_UNSIGNED | Default char type is unsigned. Defined when /J is specified. |
__cplusplus | Defined for C++ programs only. |
_CPPRTTI | Defined for code compiled with /GR (Enable Run-Time Type Information). |
_CPPUNWIND | Defined for code compiled with /GX (Enable Exception Handling). |
_DLL | Defined when /MD or /MDd (Multithread DLL) is specified. |
_M_ALPHA | Defined for DEC ALPHA platforms. It is defined as 1 by the ALPHA compiler, and it is not defined if another compiler is used. |
_M_IX86 | Defined for x86 processors. See Table 1.3 for more details. |
_M_MPPC | Defined for Power Macintosh platforms. Default is 601 (/QP601). See Table 1.4 for more details. |
_M_MRX000 | Defined for MIPS platforms. Default is 4000 (/QMR4000). See Table 1.5 for more details. |
_M_PPC | Defined for PowerPC platforms. Default is 604 (/QP604). See Table 1.6 for more details. |
_MFC_VER | Defines the MFC version. Defined as 0x0421 for Microsoft Foundation Class Library 4.21. Always defined. |
_MSC_EXTENSIONS | This macro is defined when compiling with the /Ze compiler option (the default). Its value, when defined, is 1. |
_MSC_VER | Defines the compiler version. Defined as 1200 for Microsoft Visual C++ 6.0. Always defined. |
_MT | Defined when /MD or /MDd (Multithreaded DLL) or /MT or /MTd (Multithreaded) is specified. |
_WIN32 | Defined for applications for Win32®. Always defined. |
As shown in following tables, the compiler generates a value for the preprocessor identifiers that reflect the processor option specified.
Option in Developer Studio | Command-Line Option | Resulting Value |
Blend | /GB | _M_IX86 = 500 (Default. Future compilers will emit a different value to reflect the dominant processor.) |
Pentium | /G5 | _M_IX86 = 500 |
Pentium Pro | /G6 | _M_IX86 = 600 |
80386 | /G3 | _M_IX86 = 300 |
80486 | /G4 | _M_IX86 = 400 |
Option in development environment | Command-Line Option | Resulting Value |
PowerPC 601 | /QP601 | _M_MPPC = 601 (Default) |
PowerPC 603 | /QP603 | _M_MPPC = 603 |
PowerPC 604 | /QP604 | _M_MPPC = 604 |
PowerPC 620 | /QP620 | _M_MPPC = 620 |
Table 1.5 Values for _M_MRX000
Option in Developer Studio | Command-Line Option | Resulting Value |
R4000 | /QMR4000 | _M_MRX000 = 4000 (Default) |
R4100 | /QMR4100 | _M_MRX000 = 4100 |
R4200 | /QMR4200 | _M_MRX000 = 4200 |
R4400 | /QMR4400 | _M_MRX000 = 4400 |
R4600 | /QMR4600 | _M_MRX000 = 4600 |
R10000 | /QMR10000 | _M_MRX000 = 10000 |
Option in Developer Studio | Command-Line Option | Resulting Value |
PowerPC 601 | /QP601 | _M_PPC = 601 |
PowerPC 603 | /QP603 | _M_PPC = 603 |
PowerPC 604 | /QP604 | _M_PPC = 604 (Default) |
PowerPC 620 | /QP620 | _M_PPC = 620 |