c/c++预处理器

C/C++程序编译流程:预处理->编译->汇编->链接。预处理器是在程序源文件被编译之前根据预处理指令对程序源文件进行处理的程序。C/C++提供的预处理指令主要有文件包含(include)、宏定义(macro)、条件编译等。预处理指令以#号开头标识,末尾不包含分号。预处理指令不是C/C++语言本身的组成部分(是伪指令),不能直接对它们进行编译和链接。

1、文件包含

文件包含是指一个源文件可以将另一个文件的全部内容包含进来。利用指令#include可实现这一功能。
文件包含指令的一般格式:
#include <文件名>或#include “文件名”

  • 其作用是:预处理时,把文件名指定的文件内容复制到本文件,再对合并后的文件进行编译
  • 尖括号形式和双引号形式的区别是:
    用尖括号时,系统到存放c++库函数头文件所在的目录中寻找要包含的头文件,这称为标准方式;
    用双引号时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找。
    一般来说,系统定义的头文件(包含c++库函数的头文件)用<>;用户自定义的头文件用""
    在双引号形式中,可以指出文件路径和文件名。如果在双引号中没有给出绝对路径,则默认为用户当前目录中的文件
    ./表示当前目录,…/表示当前目录的父目录。#include使用
  • 从理论上说,#include可以包含任何类型的文件,只要这些文件的内容扩展后符合c++语法。一般情况下,#include命令都写在源文件的头部,#include中包含头文件。为什么不常见include .c文件
  • 一个#include命令只能指定一个被包含文件,如果要包含n个文件,必须用n个#include命令
  • 被包含文件与其所在文件,在预处理后,成为一个文件

2、宏定义

宏定义包括:不带参数的宏定义和带参数的宏定义。#define为宏定义指令。
2.1 不带参数的宏定义
#define 标识符 代码序列 ==> #define 宏名 宏体
代码序列(宏体)可以是常量、关键字、语句、表达式、空字符。
作用:用一个指定的标识符(宏名)代替宏体。源程序中一旦出现宏定义控制行,预处理程序就把该行以后的程序中同宏名一致的标识符全部置换为所定义的宏体。

2.2 带参数的宏定义
#define 宏名(形式参数列表) 宏体
宏展开包括:1-宏名的替换,2-宏参数的替换

  • 编程习惯:宏名一般用大写字母;变量名一般用小写字母

  • 宏定义是用宏名代替宏体;宏展开是用宏体替换宏名(预处理时进行)。宏展开只是做简单的置换,并不做语法检查,如果宏体写错了,则预处理时也照样带入,只有在编译被宏展开后的源程序时才会报错。源代码中的宏名和宏体中的宏形参名必须是标识符才会被替换,像注释、字符串常量以及标识符内出现的宏名或宏形参名则不会被替换。
    1、#define NAME vrmozart
    源代码中 //NAME、/* NAME*/、“NAME”、my_NAME_blog中的宏名NAME都不会被替换。
    2、#define BLOG(name) my_name_blog=“name”
    宏体中宏形参名name也都不会被替换。
    3、连接符##-宏体中标识符内出现的宏形参名能够被替换
    #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
    4、符号#-宏形参名被替换为宏实参名的字符串形式(即在宏实参名两端加双引号")
    #define STR(name) #name
    STR(vrmozart)表示"vrmozart"

  • 宏名的作用域是指宏定义的有效范围,宏名的作用域从宏定义的结束处开始到其所在的源代码文件末尾。如果需要终止宏名的作用域,可以用预处理指令#undef加上宏名。

  • 如有必要,宏名可被重复定义,被重复定义后,宏名原先的意义被新意义所代替。

  • 宏定义代码序列中可以引用已经定义的宏名,即宏定义可以嵌套。

  • 凡带运算符的参数要用圆括号括起来
    错误: #define S( r ) PI* r * r
    area=S(a+b) 宏展开后,area=PI* a + b * a + b
    正确:#define S( r ) PI*( r ) * ( r )
    area=S(a+b) 宏展开后,area=PI*(a+b)*(a+b)

  • 带参宏、带参函数、内联函数的各个优劣

  • 预定义宏(还不太会用):
    __ DATE __,字符串常量类型,表示当前所在源文件的编译日期,输出格式为Mmm dd yyyy(如May 27 2006)。

    __ TIME__,字符串常量类型,表示当前所在源文件的编译日期,输出格式为hh:mm:ss(如09:11:10)。

    __ FILE__,字符串常量类型,表示当前所在源文件名,且包含文件路径。

    __ LINE__,整数常量类型,表示当前所在源文件中的行号。

    __ FUNCTION__,字符串常量类型,表示当前所在函数名
    这些预定义宏在调试程序时是很有用的,因为你可以很容易的知道程序运行到了那个文件的那一行,是那个函数。

  • 用户除了可以在源文件的开头使用#define定义宏外,还可在编译器项目属性“预处理器”属性页定义宏。“预处理器定义”中的宏定义要先于源文件中的宏定义被处理,其有效范围为整个项目,除非在源文件中遇到重定义或用 #undef 指定取消宏定义名,否则该宏定义名在源文件中一直保持有效。

3、条件编译

条件编译:一般情况下,源程序中的所有行均参加编译,但有时希望部分行在满足一定条件下才进行编译,即对部分内容指定编译条件。

  • 条件编译可以使程序适应不同计算机系统和不同情况,提高了源程序的通用性。
  • 指令及含义
    #if 表达式为真(非零)就对代码进行编译;
    #ifdef 如果宏被定义就进行编译;
    #ifndef 如果宏未被定义就进行编译;
    #else 作为其它预处理的剩余选项进行编译;
    #elif 这是一种#else和#if的组合选项;
    #endif 结束编译块的控制。
  • 常用形式
    1、 #if_#endif形式:
    #if 表达式 或 #ifdef 宏名 或 #ifndef 宏名
    程序段
    #endif
    含义:如果表达式值为真或者该宏名已定义或者该宏名未定义,则编译后面的程序段;否则就不编译,跳过这段程序。
    2、#if_#else_#endif形式:
    #if 表达式 或 #ifdef 宏名 或 #ifndef 宏名
    程序段1
    #else
    程序段2
    #endif
    含义:如果表达式值为真或者该宏名已定义或者该宏名未定义,则编译后面的程序段1;否则编译后面的程序段2。
    3、#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、其他预处理指令

  1. #line
    #line指令用于重新设定当前由__FILE__和__LINE__宏指定的源文件名字和行号。
    #line一般形式为#line number “filename”,其中行号number为任何正整数,文件名filename可选。#line主要用于调试及其它特殊应用,注意在#line后面指定的行号数字是表示从下一行开始的行号。
  2. #error
    #error指令使预处理器发出一条错误消息,然后停止执行预处理。
    #error 一般形式为#error info,如#error MFC requires C++ compilation。
  3. #pragma
    #pragma指令可能是最复杂的预处理指令,它的作用是设定编译器的状态或指示编译器完成一些特定的动作。
    #pragma一般形式为#pragma para,其中para为参数,下面介绍一些常用的参数。
    1.#pragma once,只要在头文件的最开始加入这条指令,就可以避免该头文件被重复包含重复编译。相当于头文件中的
    #ifndef __*** _ H __
    #define __*** _ H __
    … … // 一些声明语句
    #endif
    C++ 防止头文件被重复包含(#pragma once 与 #ifndef 的区别)
    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"),其作用与在项目属性链接器“附加依赖项”中输入库文件的效果相同。

参考:

C++ 编译过程简介
《Visual C++ 程序设计基础》p173-p186
#include用法
#include使用
为什么不常见include .c文件
带参宏、带参函数、内联函数的各个优劣
C++ 防止头文件被重复包含(#pragma once 与 #ifndef 的区别)

你可能感兴趣的:(c/c++预处理器)