[转] C++的预处理及预处理器

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

常用的C++预处理

1)常用的预处理:

  #include 包含头文件
  #if 条件
  #else 否则
  #elif 否则如果
  #endif 结束条件
  #ifdef 或 #if defined 如果定义了一个符号, 就执行操作
  #ifndef 或 #if !defined 如果没有定义一个符号,就指执行操作
  #define 定义一个符号
  #undef 删除一个符号
  #line 重新定义当前行号和文件名
  #error 输出编译错误 消息, 停止编译
  #pragma 提供 机器专用的特性,同时保证与C++的完全兼容

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

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

  形式常有下面两种

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

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

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

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

3)#define 定义符号、宏

  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++预处理指令

  一、预处理的由来:

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

  二、常见的预处理功能:

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

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

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

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

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

  三、预处理指令:

    预处理指令的格式如下:

  #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 为编译程序提供非常规的控制流信息

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

  四、文件包含指令:

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

    #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个地方要注意:

    1、我们用<iostream>还是<iostream.h>?

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

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

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

    还有,标准对iostream作了很多的改动,接口和实现都有了变化。

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

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

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

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

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

  五、编译控制指令:

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

    使用格式,如下:

    1、
  #ifdef identifier
  your code
  #endif
  如果identifier为一个定义了的符号,your code就会被编译,否则剔除
  2、
  #ifndef identifier
  your code
  #endif
  如果identifier为一个未定义的符号,your code就会被编译,否则剔除
  3、
  #if expression
  your code
  #endif
  如果expression非零,your code就会被编译,否则剔除
  4、
  #ifdef identifier
  your code1
  #else
  your code2
  #endif
  如果identifier为一个定义了的符号,your code1就会被编译,否则your code2就会被编译
  5、
  #if expressin1
  your code1
  #elif expression2
  your code2
  #else
  your code3
  #enif
  如果epression1非零,就编译your code1,否则,如果expression2非零,就编译y
  our code2,否则,就编译your code3

以上原文链接:http://www.cnblogs.com/pmars/archive/2012/10/17/2727626.html

C++中的预处理器

C++的预处理器预处理器指令包括如下12个。


#if         #else      #elif         #endif       #ifdef      #ifndef     #undef


#error    #line      #pragma   #include    #define


第一行7个就是控制语句,重点在后面5个。



1. #define


#define macro-name char-sequence


#define定义了一个标识符和一个字符序列。每次在源程序中遇到该标识符就用定义的字符序列替代它。


标识符被即宏名;替换过程即宏替换。


#define ONE 1
#define TWO ONE + ONE
#define PI 3.14
#define ERRMSG "ERR!!!"
//.......
printf(ERRMSG);


宏定义中如果字符序列超过一行,可以在末尾用反斜杠续行


#define LONGSTRING "this is a very very \
                    very very.......\
                    long string"


宏名不一定要大些,但C++程序员大都习惯用大些字母。并且习惯常将所有的宏集中在一或多个单独的头文件中。


宏名可以有变元。遇到宏名时,其定义中所用的变元均由实际程序中的变元来代替。这一点很像inline函数。但define的对象,编译器只是做简单的字符替换,其他操作例如类型检查。应尽量使用inline来代替define。


#include <stdio.h>
#define ABS(a) (a)<0? -(a) :(a)  // 这里的括号很重要

int main(void)
{
  printf("abs of -1 and 1: %d %d", ABS(-1), ABS(1));
  return 0; 
}



#error


#error error-message // error message不用双引号括起来


#error主要用在调试中,强迫编译器停止编译 。错误信息会随编译的其他信息一同显示。



#include


#include <xx.h>  //使用编译器创建者定义的方式查找
                 //通常是从项目定义的众多包含目录下开始
#include "xx.h"  //使用其他实现所定义的方式查找
                 //通常是从当前工作目录开始,
                 //如未找到,则按<>的规定继续查找



#line


#line number "filename"


用来改变__LINE__和__FILE__的内容。这两个标识符是编译器中预先定义的标识符。


前者表示已编译的代码行数,后者是一字符串,包含已编译的源文件名。


number为__LINE__的新值,filename为任意有效的文件名标识符,是__FILE__ 的新值。



#pragma


#pragma是一个实现时定义的指令,用它向编译器传送各种指令。例如


#pragma once



#和##预处理器运算符


#和##与#define一起使用。


#,stringize运算符,使它后面的变元转换成带引号的串。


#include <stdio.h>
#define mkstr(s) #s
int main(void)
{
  //I like C++ 就是mkstr宏中的s,
  //预处理器将这一大串加上引号变成"I like C++"
  printf(mkstr(I like C++)); 
  return 0 ;
}


##,pasting运算符,用于连接两个符号。


#include <stdio.h>
#define concat(a,b) a##b
int main(void)
{
  int xy = 10;
  printf("%d", concat(x,y)); //预处理器将其变成打印xy的值
  return 0 ;
}



预定义的宏名


__LINE__      __FILE__    __DATE__     __TIME__    __STDC__    __cplusplus

以上原文链接:http://www.cnblogs.com/mumuliang/archive/2010/11/24/1886891.html

时间: 2024-10-28 11:03:06

[转] C++的预处理及预处理器的相关文章

预处理器

本文是对C++预处理器的学习整理,参考了网站www.learncpp.com相关章节的内容. 一.概述 代码在编译之前需要通过预处理器进行预处理,预处理器运行时,逐行扫描代码寻找预处理指令.预处理指令是以#开头.换行符结尾(不是分号:)的代码. 预处理器主要实现一下三个功能: 1. include 2. macro define 宏定义. 3. 条件编译 二.include 故名思意,#include用于包含头文件,预处理器在遇到#include 指令时,将相应头文件的内容复制到指令所在的位置.

C和指针 (pointers on C)——第十四章:预处理器

第十四章 预处理器 我跳过了先进的指针主题的章节. 太多的技巧,太学科不适合今天的我.但我真的读,读懂.假设谁读了私下能够交流一下.有的小技巧还是非常有意思. 预处理器这一章的内容.大家肯定都用过.什么#include,#define #ifdef #undef这些,可是绝对用的不多.作为全面了解学C,还是应该都看一看. 预处理器使用方法非常讲究,用不好会失误,用好了会大大加快执行时速度(不是编译速度). 总结: C程序的第一个步骤就是预处理.预处理器共包括下面几个符号: 1.#define 定

笔记2:预处理器-(1)预处理指令

通常来说预处理器是由预处理指令(由#字符开头的一些命令)控制的,而大多数指令都属于以下三种类型之一: 1.宏定义:#define指令定义一个宏,#undef指令删除一个宏定义 2.文件包括:#include指令导致一个指定文件的内容被包含到程序之中 3.条件编译:#if.#ifdef.#ifndef.#elif.#else和#endif指令可以根据预处理器可以测试的条件来确定是将一段文本块包含到程序中还是讲其排除在程序之外 (*.除此之外还有#error.#line.#pragma等指令,相对来

C预处理器

C预处理器是一种简单的宏处理器. 预处理器是由特殊的预处理器命令行控制的,它们是以#符号开头的源文件行. 预处理器的一般操作:从源文件中删除所有的预处理器命令行,并在源文件中执行这些预处理器命令所指定的转换操作 预处理器代码行的语法与C语言其他部分的语法是完全独立的,但经过预处理所产生的源代码必须在上下文环境中合法 常见的预处理器命令: #define  定义一个预处理器宏   #undef     取消一个预处理器宏 #include   插入另一个源文件的文本 #if        测试一个

笔记3:预处理器-(2)宏定义

#define指令称为宏定义指令,通常用#define指令来定义一个宏用来代表其他东西的一个名字(如常量表达式等).通常来说预处理器会通过将宏的名字和它的定义存储在一起来响应#define指令.当这个宏在后面的程序中使用到时,预处理器会"扩展"宏,将宏替换为其定义值. 简单的宏 简单的宏的定义格式: #define 标识符 替换列表 如: #define DTE_LEN 80 #define TRUE 1 #define FALSE 0 #define PI 3.1415926 #de

C语言难点2之预处理器

C语言难点2之预处理器 1 预处理阶段 在预处理阶段中,C预处理器在源代码编译之前对其进行一些文本性质的操作.它的主要任务包括删除注释,插入被#include指定包含的文件的内容,定义和替换由#define指令定义的符号一起确定代码的部分内容是否应该根据一些条件编译指令进行编译. 2 #define命令 采用宏定义时候的易错点: 格式: #define  name  stuff 有了这条指令之后,每当有符号name出现在这条指令后面时,预处理器就会把它替换成stuff. 注意:宏定义后面不能加上

C Primer Plus之C预处理器和C库

编译程序前,先由预处理器检查程序(因此称为预处理器).根据程序中使用的预处理器指令,预处理器用符号缩略语所代表的内容替换程序中的缩略语. 预处理器不能理解C,它一般是接受一些文件并将其转换成其他文本. 翻译程序的第一步 对程序作预处理前,编译器会对它进行几次翻译处理. 编译器首先把源代码中出现的字符映射到源字符集(?).该过程处理多字节字符和使C外观更加国际化的三元字符(?)扩展 编译器查找反斜线(\)后紧跟换行符的实例并删除这些实例.注意:在这种场合下,“换行符”代表按下回车键在源代码文件中新

C Primer Plus读书笔记-预处理器指令

代码中经常看到  #define ,#ifdef #define :明显常量 一般的指令长度仅限与一行代码,除了\(反斜杠线)可以拓展到下一行外. 一般情况下,每个#define行由三个部分组成. 第一部分为#define 指令本身 第二部分为所选择的缩略语,这些缩略语称为宏(macro).  宏的名字中不允许有空格!只能使用字母,下划线(_),和数字之间的组合,第一个字符不能为数字 第三部分(#define行除了第一第二部分外的部分).称为替换列表或主体. 预处理器在程序中发现宏的实例后,总会

C和C++的面向对象专题(8)——更为高级的预处理器PHP

本专栏文章列表 一.何为面向对象 二.C语言也能实现面向对象 三.C++中的不优雅特性 四.解决封装,避免接口 五.合理使用模板,避免代码冗余 六.C++也能反射 七.单例模式解决静态成员对象和全局对象的构造顺序难题 八.更为高级的预处理器PHP 八.更为高级的预处理器PHP C++的宏在某些情况下非常难用,例如将代码展开成为这样: Macro( A, B, C, D ) => func("A", A); func("B", B); func("C&