C/C++编译系统编译程序的过程为预处理、编译、链接。预处理器是在程序源文件被编译之前根据预处理指令对程序源文件进行处理的程序。预处理器指令以#号开头标识,末尾不包含分号。预处理命令不是C/C++语言本身的组成部分,不能直接对它们进行编译和链接。C/C++语言的一个重要功能是可以使用预处理指令和具有预处理的功能。C/C++提供的预处理功能主要有文件包含、宏替换、条件编译等。
    1、文件包含
    预处理指令#include用于包含头文件,有两种形式:#include <xxx.h>,#include "xxx.h"。
尖括号形式表示被包含的文件在系统目录中。如果被包含的文件不一定在系统目录中,应该用双引号形式。
在双引号形式中可以指出文件路径和文件名。如果在双引号中没有给出绝对路径,则默认为用户当前目录中的文件,此时系统首先在用户当前目录中寻找要包含的文件,若找不到再在系统目录中查找。
对于用户自己编写的头文件,宜用双引号形式。对于系统提供的头文件,既可以用尖括号形式,也可以用双引号形式,都能找到被包含的文件,但显然用尖括号形式更直截了当,效率更高。
./表示当前目录,../表示当前目录的父目录。
    2、宏替换
    ①
宏定义
    宏定义的作用一般是用一个短的名字代表一个长的代码序列。宏定义包括无参数宏定义和带参数宏定义两类。宏名和宏参数所代表的代码序列可以是任何意义的内容,如类型、常量、变量、操作符、表达式、语句、函数、代码块等。但要尤其注意的是宏名和宏参数必须是合法的标识符,其所代表的内容及意义在宏展开前后必须一直是独立且保持不变的,不能分开解释和执行。
    无参数宏定义。用一个用户指定的称为宏名的标识符来代表一个代码序列,这种定义的一般形式为#define 标识符
代码序列。其中#define之后的标识符称为宏定义名(简称宏名),在宏定义#define之前可以有若干个空格、制表符,但不允许有其它字符,宏名与代码序列之间用空格符分隔。
带参数宏定义。带参数宏定义进一步扩充了无参数宏定义的能力,这时的宏展开既进行宏名的替换又进行宏参数的替换。带参数的宏定义的一般形式为#define 标识符(参数表) 代码序列,其中参数表中的参数之间用逗号分隔,在代码序列中必须要包含参数表中的的参数。在定义带参数的宏时,宏名与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的宏定义。带参数宏调用提供的实在参数个数必须与宏定义中的形式参数个数相同。
宏定义的有效范围称为宏名的作用域,宏名的作用域从宏定义的结束处开始到其所在的源代码文件末尾。宏名的作用域不受分程序结构的影响。如果需要终止宏名的作用域,可以用预处理指令#undef加上宏名。
宏名一般用大写字母,以便与变量名区别。如有必要,宏名可被重复定义,被重复定义后,宏名原先的意义被新意义所代替。
    宏定义代码序列中必须把""配对,不能把字符串""拆开。例如#define NAME "vrmozart不合法,应为#define NAME "vrmozart"。
    宏定义代码序列中可以引用已经定义的宏名,即宏定义可以嵌套。
    ②
多行宏
    宏定义在源文件中必须单独另起一行,换行符是宏定义的结束标志,因此宏定义以换行结束,不需要分号等符号作分隔符。如果一个宏定义中代码序列太长,一行不够时,可采用续行的方法。续行是在键入回车符之前先键入符号\,注意回车要紧接在符号\之后,中间不能插入其它符号,当然代码序列最后一行结束时不能有\。注意多行宏在调用时只能单独一行调用,不能用在表达式中或作为函数参数。
    ③
宏展开
    预处理器在处理宏定义时,会对宏进行展开(即宏替换)。宏替换首先将源文件中在宏定义随后所有出现的宏名均用其所代表的代码序列替换之,如果是带参数宏则接着将代码序列中的宏形参名替换为宏实参名。宏替换只作代码字符序列的替换工作,不作任何语法的检查,也不作任何的中间计算,一切其它操作都要在替换完后才能进行。如果宏定义不当,错误要到预处理之后的编译阶段才能发现。
源代码中的宏名和宏定义代码序列中的宏形参名必须是标识符才会被替换,即只替换标识符,不替换别的东西,像注释、字符串常量以及标识符内出现的宏名或宏形参名则不会被替换。例如:
    #define NAME vrmozart,源代码//NAME、/*NAME*/、"NAME"、my_NAME_blog中的宏名NAME都不会被替换。
    #define BLOG(name) my_name_blog="name",宏定义代码序列中的宏形参名name也都不会被替换。
    如果希望宏定义代码序列中标识符内出现的宏形参名能够被替换,可以在宏形参名与标识符之间添加连接符##,在宏替换过程中宏形参名和连接符##一起将被替换为宏实参名。##用于把宏参数名与宏定义代码序列中的标识符连接在一起,形成一个新的标识符。例如:
    #define BLOG(name) my_##name,BLOG(vrmozart)表示my_vrmozart
    #define BLOG(name) name##_ blog,BLOG(vrmozart)表示vrmozart_ blog
    #define BLOG(name) my_##name##_blog,BLOG(vrmozart)表示my_vrmozart_ blog
    如果希望宏定义代码序列中的宏形参名被替换为宏实参名的字符串形式(即在宏实参名两端加双引号"),而不是替换为宏实参名,可以在宏定义代码序列中的宏形参名前面添加符号#。#用于把宏参数名变为一个字符串形式。例如:
    #define STR(name) #vrmozart,STR(vrmozart)表示"vrmozart"
    当宏参数是另一个宏的时候,需要注意的是宏定义代码序列中有用#或##的宏参数是不会再展开。
    ④
宏的独立性
    在宏定义中说过,宏名和宏形参名所代表的内容及意义在宏展开前后必须一直是独立且保持不变的,不能分开解释和执行。其原因如下,在宏调用时,用宏定义的代码序列替换宏名,用宏实参名替换宏形参名。替换后,宏定义的代码序列就与源文件中相邻的代码自然连接,宏实参名也与代码序列中相邻的代码自然连接,宏定义的代码序列和宏实参名的独立性就不一定依旧存在。例如:
    #define SQR(x) x*x,希望实现表达式的平方计算。
    对于宏调用p=SQR(y),能得到希望的宏展开p=y*y。但对于宏调用q=SQR(u+v),得到的宏展开是q=u+v*u+v。显然,后者的展开结果不是程序设计者所希望的。为能保持宏实参名替换后的独立性,应在宏定义中给形式参数加上括号。进一步,为了保证宏名调用的独立性,作为算式的宏定义代码序列也应加括号。SQR宏定义改写成#define SQR(x) ((x)*(x))才是正确的宏定义。
    ⑤
宏调用与函数调用的区别
    函数调用在程序运行时实行,而宏展开是在编译的预处理阶段进行;函数调用占用程序运行时间,宏调用只占编译时间;函数调用对实参有类型要求,而宏调用实在参数与宏定义形式参数之间没有类型的概念,只有字符序列的对应关系;函数调用可返回一个值,宏调用获得希望的代码序列。另外,函数调用时,实参表达式分别独立求值在前,执行函数体在后。宏调用是实在参数字符序列替换形式参数。
    ⑥
预定义宏
    __DATE__,字符串常量类型,表示当前所在源文件的编译日期,输出格式为Mmm dd yyyy(如May 27 2006)。
    __TIME__,字符串常量类型,表示当前所在源文件的编译日期,输出格式为hh:mm:ss(如09:11:10)。
    __FILE__,字符串常量类型,表示当前所在源文件名,且包含文件路径。
    __LINE__,整数常量类型,表示当前所在源文件中的行号。
    __FUNCTION__,字符串常量类型,表示当前所在函数名。
    这些预定义宏在调试程序时是很有用的,因为你可以很容易的知道程序运行到了那个文件的那一行,是那个函数。
    用户除了可以在源文件的开头使用#define定义宏外,还可在编译器项目属性"预处理器"属性页定义宏。这种宏定义方式支持数字和字符串,一般形式为:标识符=数字或字符串常量,如果省略=以及后面的内容,则宏名标识符默认为整数1。定义宏的方法是在"预处理器定义"属性输入宏定义内容,多个宏定义之间用分号隔开。"预处理器定义"中的宏定义要先于源文件中的宏定义被处理,其有效范围为整个项目,除非在源文件中遇到重定义或用 #undef 指定取消宏定义名,否则该宏定义名在源文件中一直保持有效。
    3、条件编译指令
    一般情况下,在进行编译时对源程序中的每一行都要编译,但是有时希望程序中某一部分内容只在满足一定条件时才进行编译,如果不满足这个条件,就不编译这部分内容,这就是条件编译。条件编译主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到多个版本控制、防止对文件重复包含的功能。#if,#ifndef,#ifdef,#else,#elif,#endif是比较常见条件编译预处理指令,可根据表达式的值或某个特定宏是否被定义来确定编译条件。
    ①
指令意义
    #if 表达式非零就对代码进行编译;
    #ifdef 如果宏被定义就进行编译;
    #ifndef 如果宏未被定义就进行编译;
    #else 作为其它预处理的剩余选项进行编译;
    #elif 这是一种#else和#if的组合选项;
    #endif 结束编译块的控制。
    ②
常用形式
    #if_#endif形式:
    #if 常数表达式
或 #ifdef 宏名
或 #ifndef 宏名
       程序段
    #endif
    如果常数表达式为真或者该宏名已定义或者该宏名未定义,则编译后面的程序段;否则就不编译,跳过这段程序。
    #if_#else_#endif形式:
    #if 常量表达式
或 #ifdef 宏名
或 #ifndef 宏名
       程序段1
    #else
       程序段2
    #endif
    如果常数表达式为真或者该宏名已定义或者该宏名未定义,则编译后面的程序段1;否则编译后面的程序段2。
    #if_#elif_#endif形式:
    #if 常量表达式1
      程序段1
    #elif 常量表达式2
      程序段2
      .......
    #elif 常量表达式n
       程序段n
    #endif
    注意这种形式#elif不可以用于#ifdef和#ifndef中,但#else可以。
    ③
表达式
    预处理器表达式包括的操作符主要涉及到单个数的操作(+、-、~、<<、>>)、多个数的运算(*、/、%、+、-、&、^、|)、关系比较(<、<=、>、>=、==、!=)、宏定义判断(defined)、逻辑操作(!、&&、||),其优先级和行为方式与C++表达式操作符相同。对于预处理器表达式,一定要记住它们是在编译器预处理器上执行的,是在编译前进行的。
    例子:#ifndef 与#if !defined意义相同,#ifdef 与#if defined意义相同。
和34号警告信息)、#pragma warning(once:4385)(4385号警告信息仅报告一次)、#pragma warning(error:164)(把164号警告信息作为一个错误)。
    #pragma comment(…),设置一个注释记录到对象文件或者可执行文件中。常用lib注释类型,用来将一个库文件链接到目标文件中,一般形式为#pragma comment(lib,"*.lib"),其作用与在项目属性链接器"附加依赖项"中输入库文件的效果相同。

详解C/C++预处理器的更多相关文章

  1. 详解六大国产CPU处理器

    一个执着于技术的公众号 CPU作为计算机设备的运算和控制核心,负责指令读取.译码与执行,因研发门槛高.生态构建难,被认为是集成电路产业中的"珠穆朗玛峰". 纵观全球,Intel.A ...

  2. Python闭包详解

    Python闭包详解 1 快速预览 以下是一段简单的闭包代码示例: def foo(): m=3 n=5 def bar(): a=4 return m+n+a return bar >> ...

  3. 20190421-那些年使用过的CSS预处理器(CSS Preprocessor)

    写在前面的乱七八糟的前言: emmm,不得不说,早上七点是个好时间,公园里跳广场舞的大妈,街边卖菜刀看报的大爷,又不得不说,广州图书馆是个好地方,该有的安静,该有的人气,听着楼下小孩子的声音,看着周围 ...

  4. 执行对象Statement、PreparedStatement和CallableStatement详解 JDBC简介(五)

    执行对象是SQL的执行者,SQL是“安排好的任务”,执行对象就是“实际工作的人”. 执行对象有三种: Statement.PreparedStatement和CallableStatement,他们都 ...

  5. 执行对象Statement、PreparedStatement和CallableStatement详解

    执行对象是SQL的执行者,SQL是"安排好的任务",执行对象就是"实际工作的人". 执行对象有三种: Statement.PreparedStatement和C ...

  6. C#中的预处理器指令详解

    这篇文章主要介绍了C#中的预处理器指令详解,本文讲解了#define 和 #undef.#if.#elif.#else和#endif.#warning和#error.#region和#endregio ...

  7. Jmeter(十六) - 从入门到精通 - JMeter前置处理器(详解教程)

    1.简介 前置处理器是在发出“取样器请求”之前执行一些操作.如果将前置处理器附加到取样器元件,则它将在该取样器元件运行之前执行.前置处理器最常用于在取样器请求运行前修改其设置,或更新未从响应文本中提取 ...

  8. Jmeter(十七) - 从入门到精通 - JMeter后置处理器 -上篇(详解教程)

    1.简介 后置处理器是在发出“取样器请求”之后执行一些操作.取样器用来模拟用户请求,有时候服务器的响应数据在后续请求中需要用到,我们的势必要对这些响应数据进行处理,后置处理器就是来完成这项工作的.例如 ...

  9. Jmeter(十八) - 从入门到精通 - JMeter后置处理器 -下篇(详解教程)

    1.简介 后置处理器是在发出“取样器请求”之后执行一些操作.取样器用来模拟用户请求,有时候服务器的响应数据在后续请求中需要用到,我们的势必要对这些响应数据进行处理,后置处理器就是来完成这项工作的.例如 ...

随机推荐

  1. Ubuntu16.04安装VMware Tools问题

    *************************************************************************** 问题:客户机操作系统已将 CD-ROM 门锁定, ...

  2. [转]15 个顶级 HTML5 游戏引擎

    本文转自:http://www.open-open.com/news/view/13874db 1) HTML5 Game Engine Construct 2 is a leading high q ...

  3. Table of Contents - JMS

    JMS Specification v1.1 JMS 基本概念 Message QueueBrowser 消息选择器 消息确认 ConnectionMetaData ExceptionListener ...

  4. 基于AE连通性分析

    曾经做管线连通性分析,总觉得ARCGIS应该有现成的方案可以实现,但最终没有找到,后来只好自己写了套代码,但在搜索过程中找到了这样一估代码,当时留了下来,那我现在也把它留下来. Dim pLayer ...

  5. PAT1015—— 德才论

    宋代史学家司马光在<资治通鉴>中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人.凡取人之术,苟不得圣人,君子而与之,与其得小人,不若得愚人 ...

  6. Part 53 to 55 Talking about Reflection in C#

    Part 53 Reflection in C# Part 54 Reflection Example here is the code private void btnDiscover_Click( ...

  7. CI框架程序--本地调试之后部署新浪SAE

    前几天给朋友写了个简单的网站, 想想还是部署到服务器上让朋友看一下效果! 用CI框架写的,有个SAE新浪云的账号,可以用!就部署到上面去了!途中遇到了一些问题!一一解决了! 在这里分享一下 供遇到这些 ...

  8. ubuntu笔记1

    修改grub启动项顺序 在/etc/grub.d/ 目录下 文件前序号越小 在启动界面顺序越靠前, 用sudo mv修改文件名, sudo update-grub更新

  9. php返回json数据函数例子

    json_encode()函数用法. echo json_encode(array('a'=>'bbbb','c'=>'ddddd'); 这样就会生成一个标准的json格式的数据 代码如下 ...

  10. Facade模式和Mediator模式

    相同的目的:把某种策略施加到另一组对象上. Facade从上面施加策略. 其使用是明显且受限的.当策略涉及范围广泛并且可见时. 约定的关注点.都同意使用Facade而不是隐藏于其下的对象. Media ...