c++预编译命令

在所有的预处理指令中,#Pragma   指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与CC++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。   
    
  其格式一般为:   #Pragma   Para   
  其中Para   为参数,下面来看一些常用的参数。   
    
  (1)message   参数。   Message   参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:   
  #Pragma   message(“消息文本”)   
  当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。   
  当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。   
  假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法   
  #ifdef   _X86   
  #Pragma   message(“_X86   macro   activated!”)   
  #endif   
  当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_ X86   macro   activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了 。   
    
  (2)另一个使用得比较多的pragma参数是code_seg。格式如:   
  #pragma   code_seg(   ["section-name"[,"section-class"]   ]   )   
  它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。   
    
  (3)#pragma   once   (比较常用)   
  只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。   
    
  (4)#pragma   hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。   
  有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma   startup指定编译优先级,如果使用了#pragma   package(smart_init)   BCB就会根据优先级的大小先后编译。   
    
  (5)#pragma   resource   "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体外观的定义。   
    
  (6)#pragma   warning(   disable   :   4507   34;   once   :   4385;   error   :   164   )   
  等价于:   
  #pragma   warning(disable:4507   34)   //   不显示450734号警告信息   
  #pragma   warning(once:4385)   //   4385号警告信息仅报告一次   
  #pragma   warning(error:164)   //   164号警告信息作为一个错误。   
  同时这个pragma   warning   也支持如下格式:   
  #pragma   warning(   push   [   ,n   ]   )   
  #pragma   warning(   pop   )   
  这里n代表一个警告等级(1---4)。   
  #pragma   warning(   push   )保存所有警告信息的现有的警告状态。   
  #pragma   warning(   push,   n)保存所有警告信息的现有的警告状态,并且把全局警告   
  等级设定为n。   
  #pragma   warning(   pop   )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的   
  一切改动取消。例如:   
  #pragma   warning(   push   )   
  #pragma   warning(   disable   :   4705   )   
  #pragma   warning(   disable   :   4706   )   
  #pragma   warning(   disable   :   4707   )   
  //.......   
  #pragma   warning(   pop   )   
  在这段代码的最后,重新保存所有的警告信息(包括470547064707)。   
  7pragma   comment(...)   
  该指令将一个注释记录放入一个对象文件或可执行文件中。   
  常用的lib关键字,可以帮我们连入一个库文件。

一、预编译头文件说明

  所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。

  预编译头文件通过编译stdafx.cpp生成,以工程名命名,由于预编译的头文件的后缀是“pch”,所以编译结果文件是projectname.pch。

  编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为,所有在指令#include "stdafx.h"前的代码都是预编译的,它跳过#include "stdafx. h"指令,使用projectname.pch编译这条指令之后的所有代码。

  因此,所有的CPP实现文件第一条语句都是:#include "stdafx.h"。

  另外,每一个实现文件CPP都包含了如下语句:

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

  这是表示,如果生成调试版本,要指示当前文件的名称。__FILE__是一个宏,在编译器编译过程中给它赋值为当前正在编译的文件名称。

  VC.NET默认情况下使用预编译头(/Yu),不明白的在加入新.h文件后编译时总出现fatal error C1010: 在查找预编译头指令时遇到意外的文件结尾的错误。解决方法是在include头文件的地方加上#include "stdafx.h",或者打项目属性,找到“C/C++”文件夹,单击“预编译头”属性页。修改“创建/使用预编译头”属性为“不使用预编译头”。

  二、C/C++头文件一览

  C、传统 C++

#include <assert.h>    //设定插入点
#include <ctype.h>     //字符处理
#include <errno.h>     //定义错误码
#include <float.h>     //浮点数处理
#include <fstream.h>    //文件输入/输出
#include <iomanip.h>    //参数化输入/输出
#include <iostream.h>   //数据流输入/输出
#include <limits.h>    //定义各种数据类型最值常量
#include <locale.h>    //定义本地化函数
#include <math.h>     //定义数学函数
#include <stdio.h>     //定义输入/输出函数
#include <stdlib.h>    //定义杂项函数及内存分配函数
#include <string.h>    //字符串处理
#include <strstrea.h>   //基于数组的输入/输出
#include <time.h>     //定义关于时间的函数
#include <wchar.h>     //宽字符处理及输入/输出
#include <wctype.h>    //宽字符分类

  标准 C++ (同上的不再注释)

#include <algorithm>    //STL 通用算法
#include <bitset>     //STL 位集容器
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>     //复数类
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>      //STL 双端队列容器
#include <exception>    //异常处理类
#include <fstream>
#include <functional>   //STL 定义运算函数(代替运算符)
#include <limits>
#include <list>      //STL 线性列表容器
#include <map>       //STL 映射容器
#include <iomanip>
#include <ios>       //基本输入/输出支持
#include <iosfwd>     //输入/输出系统使用的前置声明
#include <iostream>
#include <istream>     //基本输入流
#include <ostream>     //基本输出流
#include <queue>      //STL 队列容器
#include <set>       //STL 集合容器
#include <sstream>     //基于字符串的流
#include <stack>      //STL 堆栈容器    
#include <stdexcept>    //标准异常类
#include <streambuf>    //底层输入/输出支持
#include <string>     //字符串类
#include <utility>     //STL 通用模板类
#include <vector>     //STL 动态数组容器
#include <cwchar>
#include <cwctype>

using namespace std;

  C99 增加

#include <complex.h>   //复数处理
#include <fenv.h>    //浮点环境
#include <inttypes.h>  //整数格式转换
#include <stdbool.h>   //布尔环境
#include <stdint.h>   //整型环境
#include <tgmath.h>   //通用类型数学宏

  三、预处理的由来

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

  四、常见的预处理功能

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

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

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

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

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

五、预处理指令

  预处理指令的格式如下:

#directive tokens

#符号应该是这一行的第一个非空字符,一般我们把它放在起始位置。如果指令一行放不下,可以通过“/”进行控制,例如:

#define Error if(error) exit(1)

等价于

#define Error /
if(error) exit(1)

  下面我们看一下常见的预处理指令:

#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、如果identifier为一个定义了的符号,your code就会被编译,否则剔除。

#ifdef identifier
your code
#endif

  2、如果identifier为一个未定义的符号,your code就会被编译,否则剔除。

#ifndef identifier
your code
#endif

  3、如果expression非零,your code就会被编译,否则剔除。

#if expression
your code
#endif

  4、如果identifier为一个定义了的符号,your code1就会被编译,否则your code2就会被编译。

#ifdef identifier
your code1
#else
your code2
#endif

  5、如果epression1非零,就编译your code1,否则,如果expression2非零,就编译your code2,否则,就编译your code3。

#if expressin1
your code1
#elif expression2
your code2
#else
your code3
#enif

  其他预编译指令除了上面我们说的集中常用的编译指令,还有3种不太常见的编译指令:#line、#error、#pragma,我们接下来就简单的谈一下。

#line的语法如下:

#line number filename

例如:#line 30 a.h

  其中,文件名a.h可以省略不写。这条指令可以改变当前的行号和文件名,例如上面的这条预处理指令就可以改变当前的行号为30,文件名是a.h。初看起来似乎没有什么用,不过,他还是有点用的,那就是用在编译器的编写中,我们知道编译器对C++源码编译过程中会产生一些中间文件,通过这条指令,可以保证文件名是固定的,不会被这些中间文件代替,有利于进行分析。

#error语法如下:

#error info

例如:

#ifndef UNIX
#error This software requires the UNIX OS.
#endif

  这条指令主要是给出错误信息,上面的这个例子就是,如果没有在UNIX环境下,就会输出This software requires the UNIX OS.然后诱发编译器终止。所以总的来说,这条指令的目的就是在程序崩溃之前能够给出一定的信息。

#pragma是非统一的,他要依靠各个编译器生产者。例如VC++中:

#pragma comment(lib,"dllTest.lib")

导入库dllTest.lib。

  八、预定义标识符

  为了处理一些有用的信息,预处理定义了一些预处理标识符,虽然各种编译器的预处理标识符不尽相同,但是他们都会处理下面的4种:

__FILE__ 正在编译的文件的名字
__LINE__ 正在编译的文件的行号
__DATE__ 编译时刻的日期字符串,例如: "25 Jan 2006"
__TIME__ 编译时刻的时间字符串,例如: "12:30:55"

  例如:cout<<"The file is :"<<__FILE__"<<"! The lines is:"<<__LINE__<<endl;

九、预处理何去何从

  如何取代#include预处理指令,我们在这里就不再一一讨论了。C++并没有为#include提供替代形式,但是namespace提供了一种作用域机制,它能以某种方式支持组合,利用它可以改善#include的行为方式,但是我们还是无法取代#include。

  #pragma应该算是一个可有可无的预处理指令,按照C++之父Bjarne的话说,就是:“#pragma被过分的经常的用于将语言语义的变形隐藏到编译系统里,或者被用于提供带有特殊语义和笨拙语法的语言扩充。”

  对于#ifdef,我们仍然束手无策,就算是我们利用if语句和常量表达式,仍然不足以替代它,因为一个if语句的正文必须在语法上正确,满足类检查,即使他处在一个绝不会被执行的分支里面。

  十、预编译头文件的补充说明

  这里介绍VC6的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc,/Yx,/Fp。其它的详细资料可以参考:
MSDN -> Visual Studio 6.0 Document -> Visual C++ 6.0 Document -> VC++ Programmer Guider -> Compiler and Linker -> Details -> Creating Precompiled Header files

  预编译头的概念:

  所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码,甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。注意及时清理那些没有用的预编译头文件。

  也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西(.eg Macro, Preprocessor )都要重新处理一遍。VC的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件。

  根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次都编译那些不需要经常改变的代码。编译性能当然就提高了。

  要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件)想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个“系统级别”的,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们会发现这个头文件里包含了以下的头文件:

#include <afxwin.h>    // MFC core and standard components
#include <afxext.h>    // MFC extensions
#include <afxdisp.h>   // MFC Automation classes
#include <afxdtctl.h>  // MFC support for Internet Explorer 4 Common Controls
#include <afxcmn.h>     

  这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。

  那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include “Stdafx.h”。原因是理所当然的,我们仅仅是要它能够编译而已―――也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打开project ->Setting->C/C++ 对话框。把Category指向Precompiled Header。在左边的树形视图里选择整个工程,如下图:


(Project ->Setting->C/C++ 对话框)

  在图中我们的Project Options(右下角的那个白的地方)可以看到 /Fp “debug/PCH.pch”,这就是指定生成的.pch文件的名字,默认的通常是 <工程名>.pch(我的示例工程名就是PCH)。

  然后,在左边的树形视图里选择StdAfx.cpp,如图:


(Project ->Setting->C/C++ 对话框->StdAfx.cpp属性)

  这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件。

  然后我们再选择一个其它的文件来看看,如图:


(Project ->Setting->C/C++ 对话框->其他文件属性)

  在这里,Precomplier 选择了 Use ………一项,头文件是我们指定创建PCH 文件的stdafx.h文件。事实上,这里是使用工程里的设置,(如图1)/Yu "stdafx.h"。

  这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:

  1)如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,我强调一遍是最开头,包含你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的,你自己试以下就知道了,绝对有很惊人的效果。

  2)如果你把pch文件不小心丢了,根据以上的分析,你只要让编译器生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)重新编译一遍就可以了。当然你可以傻傻的 Rebuild all。简单一点就是选择那个cpp文件,按一下Ctrl + F7就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值