Who are you?

C/C++预处理指令

最近在看IJG JPEG函数库的一些代码时发现开发者在程序中用了许多的预处理命令,加之以前就遇到过好多次但是一直没有花时间去学习,就趁这次机会好好的学习一下。

本文主要记录一些常用的C/C++预处理指令并对其用法进行简要介绍,参考来源已附文末。

常见的预处理命令大致如下:

  • #空指令:无任何效果
  • #include:包含一个源代码文件
  • #define:定义宏
  • #undef:取消已定义的宏
  • #if:如果给定条件为真,则编译下面代码
  • #ifdef:如果宏已经定义,则编译下面代码
  • #ifndef:如果宏没有定义,则编译下面代码
  • #elif:如果前面给定的#if条件不为真,当前条件为真,则编译下面代码
  • #endif:结束一个#if...#else条件编译块
  • #error:停止编译并显示错误信息

什么是预处理指令

预处理指令:以#号开头的代码行

一般规则是:

  1. #号必须是该行除了任何空白字符外的第一个字符;
  2. #后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符;
  3. 整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。

需要注意的是:预处理指令是在编译器进行编译之前进行的操作。预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。

常见的预处理指令

#include

作用:包含一个源代码文件(也就是我们经常说的头文件)。一般来说,头文件主要有两大类:

  1. 标准库中的头文件:使用尖括号括起来,使用时从标准库中寻找
  2. 自定义的头文件:使用双引号括起来,使用时从当前源代码文件中寻找,若找不到,再从标准库中寻找

#define

作用:定义一个宏(macro)。其全称应该位宏指令(macroinstruction),简单来说就是把较长的指令序列用某种规则对应到较短的指令序列的规则或模式。在office中的宏实质是一系列的批处理命令,而在我们目前所研究的C语法中,其只是简单的文本搜索和替换,不做计算也不做表达式求解,因而在C++中,一般建议使用const来定义常量进而替换宏,因为const会有常量的数据类型。宏一般有两类:

  1. 不带参数:

    1
    2
    #define MAX 1000
    int a=MAX;
  2. 带参数:

    1
    2
    3
    4
    #define SQR(x) ((x)*(x)) //参数括号建议加上
    #ifdef SQR //只需要宏名,不用带参数,带入参数将报错
    b=SQR(a+3);
    #endif
  3. 带参数宏中#的使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #define STR(s) #s
    #define CONS(a,b) (int)(a##e##b)
    #ifdef STR
    printf(STR(VCK)); //将参数转化为一个字符串
    #endif
    #ifdef CONS
    printf(CONS(1,2)); //将宏参数连在一起,也就是aeb,代入参数1e2,表示100
    #endif
    /* #的作用
    * 一个#表示将参数转为一个字符串;
    * 两个#表示将参数连接在一起;
    */
  4. 空宏定义(e.g.:#define DEBUG

    作用:只有一个参数的宏称为空宏定义,其不对代码产生任何影响。主要是为了方便代码阅读(如__IN__和__OUT__宏表示参数是输入还是输出)和作为标记(UNICODE和DEBUG)进而切换配置,自己写程序时可以多加留意。常与其他一些预处理命令连用来进行代码调试和维护。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h>
    #define D(x)
    int main()
    {
    D(printf("null macro")); //不进行任何操作,等同于代码注释
    return 0;
    }
    /* #define D(x)
    * 等同于
    * #define D(x) do{}while(0) 执行一次便退出,并且执行内容为空
    */
  5. 预定义宏

    作用:在C中预定义了一些宏,称为预定义宏,主要用来提供当前编译的信息。主要由以下几种:

    • __LINE__:被编译的文件的行数,整型常量
    • __FILE__:被编译的文件的名字,字符串
    • __DATE__:编译的日期(格式为M D Y形式的字符串)
    • __TIME__:编译的时间(格式为h:m:s形式的字符串)
    • __STDC__:如果编译器接受标准C,那么值为整型常量1
    • __func__:当前所在函数名,在C++中为__FUNCTION__,在VC中不支持该宏,但在g++中可以使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /* 一般用法 */
    #include<iostream>
    using namespace std;
    #ifndef __func__
    #define __func__ (__FUNCTION__)
    #endif
    void func()
    {
    cout<<"func name is:"<<__func__<<endl;
    cout<<"func name is:"<<__FUNCTION__<<endl; //在C中将会报错,但在C++中不会
    }
    int main()
    {
    cout<<"date is :"<<__DATE__<<endl;
    cout<<"time is :"<<__TIME__<<endl;
    cout<<"file is :"<<__FILE__<<endl;
    cout<<"line is :"<<__LINE__<<endl;
    func();
    }

#ifdef, #ifndef, #endif

作用:条件编译指令,将决定程序中哪些代码被编译,而哪些代码不被编译

  1. define - ifdef - endif

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #include <stdio.h>
    #include <stdlib.h>
    #define DEBUG
    int main(void)
    {
    int i = 0;
    char c;
    while(1)
    {
    i++;
    c = getchar();
    if('\n' != c)
    {
    getchar();
    }
    if('q' == c || 'Q' == c)
    {
    #ifdef DEBUG //判断DEBUG是否被定义了
    printf("We get:%c,about to exit.\n",c);
    #endif
    break;
    }
    else
    {
    printf("i = %d",i);
    #ifdef DEBUG
    printf(",we get:%c",c);
    #endif
    printf("\n");
    }
    }
    printf("Hello World!\n");
    return 0;
    }
    /*#endif用于终止#ifdef预处理指令。*/
  2. ifdef - ifndef

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <stdio.h>
    #define DEBUG
    main()
    {
    #ifdef DEBUG
    printf("yes ");
    #endif
    #ifndef DEBUG
    printf("no ");
    #endif
    }
    //#ifdefined等价于#ifdef;
    //#if!defined等价于#ifndef
  3. if - else - endif

    在预处理阶段就智能屏蔽掉相应的语句,在实际编译时屏蔽掉的语句不再进行编译。

  4. if - elseif - else - endif

    适用于多个条件,进而将需要编译的部分代码在编译前就进行确认。

实战借鉴

利用宏来调试代码

在不删除代码的前提下修改代码,可以利用预处理命令来实现。学会利用宏来注释代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#if 0
///< 旧的代码(或函数) (旧的代码, 将会被预处理的时候,屏蔽掉, 不进行编译)
#else
///< 新的代码(或函数)
#endif
#ifndef JOE_DEBUG
///< 新的代码(或函数)
#else
///< 旧的代码(或函数) (旧的代码, 将会被预处理的时候,屏蔽掉, 不进行编译)
#endif
#ifdef Q_DEBUG
///< 新的代码(或函数)
#else
///< 旧的代码(或函数) (旧的代码, 将会被预处理的时候,屏蔽掉, 不进行编译)
#endif

跨平台编程

根据不同平台本省自带的有别于其他平台的系统宏,我们可以实现代码在各个平台之间的无缝移植。

1
2
3
4
5
6
7
8
9
10
11
#ifdef OS_Win
#include <windows.h>
#endif
#ifdef OS_Mac
#include <mac.h>
#endif
#ifdef OS_Linux
#include <linux.h>
#endif

其他

还有另外的一些系统自定义的预处理命令,如#line,#error等,对于#error,我们可以利用其来显示错误信息,

1
2
3
4
/* 如果JOE宏没有定义,那么编译就此结束, 编译器就会显示红色的错误 */
#ifndef JOE
#error "JOE is not exits"
#endif

参考来源: