C++预处理、宏

一、C++编译过程

从C++源文件到可执行文件的编译过程,有如下几个步骤,g++提供了很多编译选项,可以让我们控制整个编译过程:

  • 预处理(g++选项 -E):预处理就是本文要详细说的宏替换、头文件包含等,结果直接输出到控制台
  • 编译 (g++选项 -S):编译是指对预处理后的代码进行语法和语义分析,最终得到汇编代码或接近汇编的其他中间代码,结果保存为.s文件
  • 汇编 (g++选项 -c):汇编是指将上一步得到的汇编或中间代码转换为目标机器的二进制指令,一般是每个源文件生成一个二进制文件(VS是.obj,GCC是.o)
  • 链接 (g++选项 空) :链接是对上一步得到的多个二进制文件“链接”成可执行文件或库文件等,链接后结果就是可执行文件了

使用这些选项,我们就可以让编译在某一步结束之后停下来,输出那一步的结果。

二、预处理功能

预处理器是在程序源文件被编译之前根据预处理指令对程序源文件进行处理的程序。预处理器指令以#号开头标识,末尾不包含分号。预处理命令不是C/C++语言本身的组成部分,不能直接对它们进行编译和链接。

最常见的预处理有:源文件包含,宏替换,条件编译、编译器预留指令4种。 

  1. 源文件包含: #include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。 
  2. 宏替换: #define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。
  3. 条件编译: #if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。 
  4. 编辑器预留指令: #progma,这也是我们应用预处理的一个重要方面,主要功能是为编译程序提供非常规的控制流信息。 
  5. 重定义行号和文件名: #line,
  6. 错误信息: #error,
1、源文件包含 #include
预处理指令#include用于包含头文件,有两种形式:#include <xxx.h>,#include "xxx.h"。
尖括号形式表示被包含的文件在系统目录中。如果被包含的文件不一定在系统目录中,应该用双引号形式。
在双引号形式中可以指出文件路径和文件名。如果在双引号中没有给出绝对路径,则默认为用户当前目录中的文件,此时系统首先在用户当前目录中寻找要包含的文件,若找不到再在系统目录中查找。如#include "../file.h"    //UNIX下的父目录下的头文件 
对于用户自己编写的头文件,宜用双引号形式。对于系统提供的头文件,既可以用尖括号形式,也可以用双引号形式,都能找到被包含的文件,但显然用尖括号形式更直截了当,效率更高。
./表示当前目录,../表示当前目录的父目录。
2、宏替换 #define
现代的C++程序设计原则不推荐适用宏定义常量或函数宏,应该尽量少的使用 #define ,如果可能,用 const 变量或 inline 函数代替。
预定义宏
  • __DATE__,字符串常量类型,表示当前所在源文件的编译日期,输出格式为Mmm dd yyyy(如May 27 2006)。
  • __TIME__,字符串常量类型,表示当前所在源文件的编译日期,输出格式为hh:mm:ss(如09:11:10)。
  • __FILE__,字符串常量类型,表示当前所在源文件名,且包含文件路径。
  • __LINE__,整数常量类型,表示当前所在源文件中的行号。
  • __FUNCTION__,字符串常量类型,表示当前所在函数名。
这些预定义宏在调试程序时是很有用的,用于输出调试信息。
int main()
{
	#define PRINT(arg) std::cout << #arg": " << arg << '\n'
	PRINT(__cplusplus);
	PRINT(__LINE__);
	PRINT(__FILE__);
	PRINT(__DATE__);
	PRINT(__TIME__);
	#ifdef __STDC__
     		PRINT(__STDC__);
	#endif
}
注释:预定义宏一般以“__”作为前缀,所以用户自定义宏应该避开“__”开头。
3、条件编译指令
       一般情况下,在进行编译时对源程序中的每一行都要编译,但是有时希望程序中某一部分内容只在满足一定条件时才进行编译,如果不满足这个条件,就不编译这部分内容,这就是条件编译。条件编译主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到多个版本控制、防止对文件重复包含的功能。
常用形式:
#if_#endif形式:
       #if expression 或 #ifdef identifier 或 #ifndef identifier
          your code
       #endif
#if_#else_#endif形式:
       #if expression 或 #ifdef identifier 或 #ifndef identifier
          your code
       #else
           your code
       #endif
#if_#elif_#endif形式:
       #if expression1
         your code1
       #elif expression2
          your code2
       #elif expression3
          your code3
       #endif
注意这种形式#elif不可以用于#ifdef和#ifndef中,但#else可以。
4、编译器预留指令 #progma
作用是设定编译器的状态或指示编译器完成一些特定的动作。
#pragma一般形式为#pragma para,其中para为参数,下面介绍一些常用的参数。
  1. #pragma once,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。
  2. #pragma message("info"),在编译信息输出窗口中输出相应的信息,例如#pragma message("Hello")。
  3. #pragma warning(...),设置编译器处理编译警告信息的方式,例如#pragma warning(disable:4507 34;once : 4385;error:164)等价于#pragma warning(disable:4507 34)(不显示4507和34号警告信息)、#pragma warning(once:4385)(4385号警告信息仅报告一次)、#pragma warning(error:164)(把164号警告信息作为一个错误)。
  4. #pragma comment(…),设置一个注释记录到对象文件或者可执行文件中。常用lib注释类型,用来将一个库文件链接到目标文件中,一般形式为#pragma comment(lib,"*.lib"),其作用与在项目属性链接器“附加依赖项”中输入库文件的效果相同。
5、错误信息 #error
  • 作用是在程序崩溃之前能够给出错误信息。
  • #error语法:#error   info
例如:
     #ifndef UNIX 
         #error This software requires the UNIX OS. 
     #endif 













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值