预处理指令在程序的预处理阶段生效,可以简单处理源码,生成新的源码以供编译。# 代表预处理指令的开始,后面是指令关键字,允许任意个数的空白字符存在,常见预处理指令如下:

  1. include:用来包含一个源代码文件

  2. define:用来定义一个宏

  3. undef:取消定义的宏

  4. if (表达式):表达式为真则执行下面的代码

  5. ifdef:如果宏已经定义,就执行下面的代码

  6. ifndef:如果宏没有定义,就执行下面的代码

  7. elif:和 #if 搭配使用,走代码的另一个分支。

  8. endif:结束一个 #if ……#elif编译块

  9. error:停止编译并显示错误信息,可以放在if语句中使用。

之前写代码一直会使用这些这些预处理的指令,但是没仔细总结过。因为最近在阅读和调试开源项目,发现预处理指令在debug的时候还有一些意想不到的用法,并且很方便。所以就做一次总结。

  1. // 标准用法
  2. #if (表达式)
  3. ...
  4. #endif

eg:

  1. #ifndef __MP4_WRITER_H__
  2. #define __MP4_WRITER_H__
  3. #endif

ifndef是在预编译阶段起作用的。
假如有两个文件同时include了这个头文件,那么MP4_WRITER_H就被定义了,当之后其他文件再次引用的时候,此时的判断ifndef就会自动跳过这个文件的重复编译

跨平台场景中的应用

debug方面的应用

阅读KDE的一个开源应用kolourpaint看到的,先在程序头中定义了一个等于0的宏:

  1. #define DEBUG_KP_MAIN_WINDOW 0

然后在代码中需要debug的信息前后都加上预编译的指令:

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "kpMainWindow::addRecentURL(" << url << ")";
#endif
    if (url.isEmpty ())
        return;


    KSharedConfig::Ptr cfg = KSharedConfig::openConfig();

    // KConfig::readEntry() does not actually reread from disk, hence doesn't
    // realize what other processes have done e.g. Settings / Show Path
    cfg->reparseConfiguration ();

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items ();
#endif
    // HACK: Something might have changed interprocess.
    // If we could PROPAGATE: interprocess, then this wouldn't be required.
    d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles));
#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\tafter loading config=" << d->actionOpenRecent->items ();
#endif

这样,就相当于做了一个模块化的debug开关。这个模块需要debug了就修改一下宏,不需要了就关掉。此外比如那种在一个源文件内调试不同的复杂模块也可以做多个宏的定义,针对不同模块加上不同的宏调试开关。

#和##运算符

#可以将replcaement-text令牌转换为引号引起来的字符串
ex:

#include <iostream>
using namespace std;
 #define

条件编译

有几个指令可以用来有选择的对部分源代码进行编译,这个过程被称为条件编译。

预定义宏

C++提供了以下所示的一些预定义宏:

描述
LINE 在编译时包含当前行号
FILE 编译时包含当前文件名
DATE 包含一个格式为mm/dd/yy的字符串,表示将源文件转换为目标代码的日期
TIME 格式为hh:mm:ss格式的字符串,表示程序被编译的时间
#include <iostream>
using namespace std;

int main ()
{
    cout << "Value of __LINE__ : " << __LINE__ << endl;
    cout << "Value of __FILE__ : " << __FILE__ << endl;
    cout << "Value of __DATE__ : " << __DATE__ << endl;
    cout << "Value of __TIME__ : " << __TIME__ << endl;

    return 0;
}
Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48