C++的预处理(Proprocess)

转自:http://www.cnblogs.com/pmars/archive/2012/10/17/2727626.htmlhtml

C++的预处理(Preprocess),是指在C++程序源代码被编译以前,由预处理器(Preprocessor)对C++程序源代码进行的处理。这个过程并不对程序的源代码进行解析,但它把源代分割或处理成为特定的符号用来支持宏调调用。ios

经常使用的C++预处理

1)经常使用的预处理:git

复制代码
  #include 包含头文件
  #if 条件
  #else 不然
  #elif 不然若是
  #endif 结束条件
  #ifdef 或 #if defined 若是定义了一个符号, 就执行操做
  #ifndef 或 #if !defined 若是没有定义一个符号,就指执行操做
  #define 定义一个符号
  #undef 删除一个符号
  #line 从新定义当前行号和文件名
  #error 输出编译错误 消息, 中止编译
  #pragma 提供 机器专用的特性,同时保证与C++的彻底兼容
复制代码

2)#include 在 程序中包含头文件程序员

  头文件一般以.h结尾,其 内容可以使用#include预处理器指令包含到 程序中头文件中通常包含: 函数原型 与 全局变量express

  形式常有下面两种ide

  #include <iostream>
  #include "myheader.h"

  前者<>用来引用标准库头文件,后者""经常使用来引用自定义的头文件函数

  前者<>编译器只搜索包含标准库头文件的默认 目录,后者首先搜索正在编译的源文件所在的 目录,找不到时再搜索包含标准库头文件的默认 目录.布局

  若是把头文件放在其余 目录下,为了查找到它,必须在双引号中指定从源文件到头文件的完整路径测试

3)#define 定义符号、宏spa

  1>符号

复制代码
  #define PI 3.1415925 定义符号PI为3.1415925
  #undef PI 取消PI的值
  这里PI看起来像一个变量,但它与变量没有任何关系,它只是一个符号或标志,在 程序代码编译前,此符号会用一组指定的字符来代替
  3.14159265 不是一个数值,只是一个字符串,不会进行检查
  在编译前,预处理器会遍历代码,在它认为置换有意义的地方,用字符串PI的定义值(3.14159265)来代替
  在注释或字符串中的PI不进行替换
  在C中常以#define来定义符号常量,但在C++中最好使用const 来定义常量
  #define PI 3.14159265
  const long double PI=3.14159265;
  二者比较下,前者没有类型的指定容易引发没必要须的麻烦,然后者定义清楚,因此在C++中推荐使用const来定义常量
  #define 的缺点:
    1)不支持类型检查
    2)不考虑做用域
    3)符号名不能限制在一个命名 空间中
复制代码

  2>#undef 删除#define定义的符号

  define PI 3.14159265
  ... //之间全部的PI均可以被替换为3.14159265
  #undef PI
  以后再也不有PI这个标识符

  3>定义宏

复制代码
  #define Print(Var) cout<<(Var)<<endl
  用宏名中的参数带入语句中的参数
  宏后面没有;号
  Print(Var)中的Print和(之间不能有空格,不然(就会被解释为置换字符串的一部分
  #define Print(Var, digits) cout << setw(digits) << (Var) << endl
  调用
  Print(ival, 15)
  预处理器就会把它换成
  cout << setw(15) << (ival) << endl;
  全部的状况下均可以使用内联函数来代替宏,这样能够加强类型的检查
  template<class T> inline void Print (const T& var, const int& digits)
  {
    cout<<setw(digits)<<var<<endl;
  }
  调用
  Print(ival, 15);
  使用宏时应注意的易引发的错误:
  #define max(x,y) x>y?x:y;+
  调用 result = max(myval, 99); 则换成 result = myval>99?myval:99; 这个没有问题是正确的
  调用 result = max(myval++, 99); 则换成 result = myval++>99?myval++:99; 这样若是myval>99那么myval就会递增两次,这种状况下()是没什么用的如result=max((x),y)则 result = (myval++)>99?(myval++):99;
  再如
  #define product(m,n) m*n
  调用
  result = product(5+1,6);则替换为result = 5+1*6; 因此产生了错误的结果,此时应使用()把参数括起
  #define product(m,n) (m)*(n)
  则result = product(5+1,6);则替换为result = (5+1)*(6);
复制代码

  结论: 通常用内联函数来代替预处理器宏

  技巧:

  1)给替换变量加引号

  #define MYSTR "I love you"
  cout << MYSTR ; //I love you而不是"I love you"
  若是
  cout << "MYSTR" ; //则会输出"MYSTR"而不是"I love you"
  能够这样作
  cout << #MYSTR ; //则会输出 "I love you"即cout << "\"I love you\"";

  2)在宏表达式中链接几个参数

  如
  #define join(a,b) ab 这样不会理解为参数a的值与参数b的值的链接,即如join(10,999)不会理解为10999而是把ab理解为字符串,即输出ab
  这时能够
  #define join(a,b) a##b
  则join(10,999)就会输出10999

  3)逻辑预处理器指令

复制代码
  #if defined CALCAVERAGE 或 #ifdef CALCAVERAGE
  int count=sizeof(data)/sizeof(data[0]);
  for(int i=0; i<count; i++)
  average += data;
  average /= count;
  #endif
  若是已经定义符号CALCAVERAGE则把#if与#endif间的语句放在要编译的源代码内
复制代码

  4)防止重复引入某些头文件

复制代码
  #ifndef COMPARE_H
  #define COMPARE_H 注意: 这里只是定义一个没有值的符号COMPARE_H, 下面的namespace compare不是COMPARE_H的 内容,这里的定义不像是定义一个常量或宏,仅仅定义一个符号,指出此符号已定义,则就会有下面的 内容namespace compare{...
  namespace compare{
  double max(const double* data, int size);
  double min(const double* data, int size);
  }
  #endif
  比较
  #define VERSION \
  3
  由于有换行符\ 因此上句等价于 #define VERSION 3
  由此能够看出#define COMPARE_H与namespace compare是独立没有关系的两个行
  也能够这样用
  #if defined block1 && defined block2
  ...
  #endif
  #if CPU==PENTIUM4
  ...
  #endif
  #if LANGUAGE == ENGLISH
  #define Greeting "Good Morning."
  #elif LANGUAGE == GERMAN
  #define Greeting "Guten Tag."
  #elif LANGUAGE == FRENCH
  #define Greeting "Bonjour."
  #else
  #define Greeting "Hi."
  #endif
  std::cout<<Greeting << std::endl;
  #if VERSION == 3
  ...
  #elif VERSION == 4
  ...
  #else
  ...
  #endif
复制代码

  5)标准的预处理器宏

复制代码
  __LINE__ 当前源文件中的代码行号,十进制整数
  __FILE__ 源文件的名称,字符串字面量
  __DATE__ 源文件的处理日期,字符串字面量,格式mmm dd yyyy其中mmm是月份如Jan、Feb等 dd是01-31 yyyy是四位的年份
  __TIME__ 源文件的编译 时间,也是字符串字面量格式是hh:mm:ss
  __STDC__ 这取决于实现方式,若是编译器选项设置为编译标准的C代码,一般就定义它,不然就不定义它
  __cplusplus 在编译C++ 程序时,它就定义为199711L
  使用#line能够修改__FILE__返回的字符串
  如
  #line 1000 把当前行号设置为1000
  #line 1000 "the program file" 修改__FILE__返回的字符串行号改成了1000,文件名改成了"the program file"
  #line __LINE__ "the program file" 修改__FILE__返回的字符串行号没变,文件名改成了"the program file"
  cout << "program last complied at "<<__TIME__
  << " on " << __DATE__
  << endl;
复制代码

  6)#error

  在预处理阶段,若是出现了错误,则#error指令能够生成一个诊断 消息,并显示为一个编译错误,同时停止编译
  #ifndef __cplusplus
  #error "Error - Should be C++"
  #endif

  7)#pragma

  专门用于实现预先定义好的选项,其结果在编译器说明文档中进行了详细的解释。编译器未识别出来的#pragma指令都会被忽略

  8)assert()宏

复制代码
  在标准库头文件<cassert>中声明
  用于在 程序中 测试一个逻辑表达式,若是逻辑表达式为false, 则assert()会终止 程序,并显示诊断 消息
  用于在条件不知足就会出现重大错误,因此应确保后面的语句不该再继续执行,因此它的应用很是灵活
  注意: assert不是错误处理 机制,逻辑表达式的结果不该产生负面效果,也不该超出 程序员的控制(如找开一个文件是否成功), 程序应提供适当的代码来处理这种状况
  assert(expression);
  assert(expression) && assert(expression2);
  可使用#define NDEBUG来关闭断言 机制
  #include <iostream>
  #include <cassert>
  using std::cout;
  using std::endl;
  int main()
  {
    int x=0;
    int y=0;
    cout<<endl;
    for(x=0; x<20; x++)
    {
      cout<<"x= "<<x <<" y= "<<y<<endl;
      assert(x<y); //当x>=y与x==5时,就报错,并终止 程序的执行
    }
    return 0;
  }
复制代码

C++预处理指令

  1、预处理的由来:

    在C++的历史发展中,有不少的语言特征(特别是语言的晦涩之处)来自于C语言,预处理就是其中的一个。C++从C语言那里把C语言预处理器继承过来(C语言预处理器,被Bjarne博士简称为Cpp,不知道是否是C Program Preprocessor的简称)。

  2、常见的预处理功能:

    预处理器的主要做用就是把经过预处理的内建功能对一个资源进行等价替换,最多见的预处理有:文件包含,条件编译、布局控制和宏替换4种。

    1,文件包含:#include 是一种最为常见的预处理,主要是作为文件的引用组合源程序正文。

    2,条件编译:#if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进,行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。

    3,布局控制:#progma,这也是咱们应用预处理的一个重要方面,主要功能是为编译程序提供很是规的控制流信息。

    4,宏替换: #define,这是最多见的用法,它能够定义符号常量、函数功能、从新命名、字符串的拼接等各类功能。

  3、预处理指令:

    预处理指令的格式以下:

复制代码
  #directive tokens
  #符号应该是这一行的第一个非空字符,通常咱们把它放在起始位置。若是指令一行放不下,能够经过\进行控制,例如:
  #define Error if(error) exit(1) 等价于
  #define Error \
  if(error) exit(1)
  不过咱们为了美化起见,通常都不怎么这么用,更常见的方式以下:
  # ifdef __BORLANDC__
  if_true<(is_convertible<Value,named_template_param_base>::value)>::template then<make_named_arg, make_key_value>::type Make;
  # else
  enum { is_named = is_named_parameter<Value>::value };
  typedef typename if_true<(is_named)>::template
  then<make_named_arg, make_key_value>::type Make;
  # endif
  下面咱们看一下常见的预处理指令:
  #define 宏定义
  #undef 未定义宏
  #include 文本包含
  #ifdef 若是宏被定义就进行编译
  #ifndef 若是宏未被定义就进行编译
  #endif 结束编译块的控制
  #if 非零就对代码进行编译
  #else 做为其余预处理的剩余选项进行编译
  #elif 这是一种#else和#if的组合选项
  #line 改变当前的行数和文件名称
  #error 输出一个错误信息
  #pragma 为编译程序提供很是规的控制流信息
复制代码

  下面咱们对这些预处理进行一一的说明,考虑到宏的重要性和繁琐性,咱们把它放到最后讲。

  4、文件包含指令:

    这种预处理使用方式是最为常见的,平时咱们编写程序都会用到,最多见的用法是:

复制代码
    #include <iostream> //标准库头文件
    #include <iostream.h> //旧式的标准库头文件
    #include "IO.h" //用户自定义的头文件
    #include "../file.h" //UNIX下的父目录下的头文件
    #include "/usr/local/file.h" //UNIX下的完整路径
    #include "..\file.h" //Dos下的父目录下的头文件
    #include "\usr\local\file.h" //Dos下的完整路径
复制代码

    这里面有2个地方要注意:

    一、咱们用<iostream>仍是<iostream.h>?

    咱们主张使用<iostream>,而不是<iostream.h>,为何呢?我想你可能还记得我曾经给出过几点理由,这里我大体的说一下:

    首先,.h格式的头文件早在98年9月份就被标准委员会抛弃了,咱们应该紧跟标准,以适合时代的发展。

    其次,iostream.h只支持窄字符集,iostream则支持窄/宽字符集。

    还有,标准对iostream做了不少的改动,接口和实现都有了变化。

    最后,iostream组件所有放入namespace std中,防止了名字污染。

    二、<io.h>和"io.h"的区别?

    其实他们惟一的区别就是搜索路径不一样:

    对于#include <io.h> ,编译器从标准库路径开始搜索

    对于#include "io.h" ,编译器从用户的工做路径开始搜索

  5、编译控制指令:

    这些指令的主要目的是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。

    使用格式,以下:

复制代码
  一、
  #ifdef identifier
  your code
  #endif
  若是identifier为一个定义了的符号,your code就会被编译,不然剔除
  二、
  #ifndef identifier
  your code
  #endif
  若是identifier为一个未定义的符号,your code就会被编译,不然剔除
  三、
  #if expression
  your code
  #endif
  若是expression非零,your code就会被编译,不然剔除
  四、
  #ifdef identifier
  your code1
  #else
  your code2
  #endif
  若是identifier为一个定义了的符号,your code1就会被编译,不然your code2就会被编译
  五、
  #if expressin1
  your code1
  #elif expression2
  your code2
  #else
  your code3
  #enif
  若是epression1非零,就编译your code1,不然,若是expression2非零,就编译y
  our code2,不然,就编译your code3
相关文章
相关标签/搜索