详解C/C++预处理器

?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意义相同。
??? 4、其它预处理指令
??? 除了上面讨论的常用预处理指令外,还有三个不太常见的预处理指令:#line、#error、#pragma,下面分别介绍。
??? ① #line
??? #line指令用于重新设定当前由__FILE__和__LINE__宏指定的源文件名字和行号。
??? #line一般形式为#line number "filename",其中行号number为任何正整数,文件名filename可选。#line主要用于调试及其它特殊应用,注意在#line后面指定的行号数字是表示从下一行开始的行号。
??? ② #error
??? #error指令使预处理器发出一条错误消息,然后停止执行预处理。
??? #error 一般形式为#error info,如#error MFC requires C++ compilation。
??? ③ #pragma
??? #pragma指令可能是最复杂的预处理指令,它的作用是设定编译器的状态或指示编译器完成一些特定的动作。
??? #pragma一般形式为#pragma para,其中para为参数,下面介绍一些常用的参数。
??? #pragma once,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。
??? #pragma message("info"),在编译信息输出窗口中输出相应的信息,例如#pragma message("Hello")。
??? #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号警告信息作为一个错误)。
??? #pragma comment(…),设置一个注释记录到对象文件或者可执行文件中。常用lib注释类型,用来将一个库文件链接到目标文件中,一般形式为#pragma comment(lib,"*.lib"),其作用与在项目属性链接器"附加依赖项"中输入库文件的效果相同。

时间: 2024-08-03 16:55:47

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

Linux内核模块编程与内核模块LICENSE -《详解(第3版)》预读

Linux内核模块简介 Linux内核的整体结构已经非常庞大,而其包含的组件也非常多.我们怎样把需要的部分都包含在内核中呢?一种方法是把所有需要的功能都编译到Linux内核.这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核. 有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?Linux提供了这样的一种机制,这种机制被称为模块(Module).模块具有这样的特点. 模块本

热烈祝贺华清远见《ARM处理器开发详解》第2版正式出版

2014年6月,由华清远见研发中心组织多名业 内顶尖讲师编写的<ARM处理器开发详解>一书正式出版.本书以S5PV210处理器为平台,详细介绍了嵌入式系统开发的各个主要环节,并注重实践,辅以 代码讲解,从分析的角度来讲解嵌入式开发的各种技术,将嵌入式软.硬件理论和嵌入式实验实践融合在一起. 华清远见作为国内最早推出嵌入式培训的机构,到 目前为止教学采用的实验平台.图书.课件全部来自自主研发,成为业内唯一公开连续出版图书的培训机构.这次最新出版的<ARM处理器开发详解>同样秉承了 华

处理器拦截器(HandlerInterceptor)详解

处理器拦截器(HandlerInterceptor)详解 编程界的小学生 关注 2017.04.06 15:19* 字数 881 阅读 657评论 0喜欢 4 简介SpringWebMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于处理器进行预处理和后处理. 应用场景1.日志记录,可以记录请求信息的日志,以便进行信息监控.信息统计等.2.权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面.3.性能监控:典型的是慢日志. HandlerIntercep

预定义宏,C语言预定义的宏详解

1.预定义宏 对于预定义宏,相信大家并不陌生.为了方便处理一些有用的信息,预处理器定义了一些预处理标识符,也就是预定义宏.预定义宏的名称都是以"__"(两条下划线)开头和结尾的,如果宏名是由两个单词组成,那么中间以"_"(一条下划线)进行连接.并且,宏名称一般都由大写字符组成. 在日常项目编程中,预定义宏尤其对多目标平台代码的编写通常具有重大意义. 通过预定义宏,程序员使用"#ifdef"与"#endif"等预处理指令,就可使

Java 集合类详解

1.java集合类图 1.1 1.2 上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,而点线边框的是接口,比如Collection,Iterator,List等. 发现一个特点,上述所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法.它的一个

各种音视频编解码学习详解

各种音视频编解码学习详解 媒体业务是网络的主要业务之间.尤其移动互联网业务的兴起,在运营商和应用开发商中,媒体业务份量极重,其中媒体的编解码服务涉及需求分析.应用开发.释放license收费等等.最近因为项目的关系,需要理清媒体的codec,比较搞的是,在豆丁网上看运营商的规范 标准,同一运营商同样的业务在不同文档中不同的要求,而且有些要求就我看来应当是历史的延续,也就是现在已经很少采用了.所以豆丁上看不出所以然,从 wiki上查.中文的wiki信息量有限,很短,而wiki的英文内容内多,删减版

CDN技术详解及实现原理

CDN技术详解 一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精华放上网.公诸同好. 第一章    引言    “第一公里”是指万维网流量向用户传送的第一个出口,是网站服务器接入互联网的链路所能提供的带宽.这个带宽决定了一个 网站能为用户提供的访问速度和并发访问量.如果业务繁忙,用户的访问数越多,拥塞越严重,网站会在最需要向用户提供服务时失去用户.(还有“中间一公里” 和

一份spring配置文件及其详解

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/axu20/archive/2009/10/14/4668188.aspx 1.基本配置:<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/

高并发高流量网站架构详解

(推荐)高并发高流量网站架构详解 Web2.0的兴起,掀起了互联网新一轮的网络创业大潮.以用户为导 向的新网站建设概念,细分了网站功能和用户群,不仅成功的造就了一大批新生的网站,也极大的方便了上网的人们.但Web2.0以用户为导向的理念,使得新 生的网站有了新的特点--高并发,高流量,数据量大,逻辑复杂等,对网站建设也提出了新的要求. 本文围绕高并发高流量的网站架构设计问题,主要研究讨论了以下内容: 首先在整个网络的高度讨论了使用镜像网站,CDN内容分发网络等技术对负载均衡带来的便利及各自的优缺