在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 其格式一般为: #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) // 不显示4507和34号警告信息 #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 ) 在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。 (7)pragma 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//设定插入点 #include //字符处理 #include //定义错误码 #include <float.h> //浮点数处理 #include //文件输入/输出 #include //参数化输入/输出 #include //数据流输入/输出 #include //定义各种数据类型最值常量 #include //定义本地化函数 #include //定义数学函数 #include //定义输入/输出函数 #include //定义杂项函数及内存分配函数 #include <string.h> //字符串处理 #include //基于数组的输入/输出 #include //定义关于时间的函数 #include //宽字符处理及输入/输出 #include //宽字符分类 标准 C++ (同上的不再注释) #include //STL 通用算法 #include //STL 位集容器 #include #include #include #include #include //复数类 #include #include #include #include #include //STL 双端队列容器 #include //异常处理类 #include #include //STL 定义运算函数(代替运算符) #include #include //STL 线性列表容器 #include
预编译头文件 今天在改一个很大的程序,慢慢看,慢慢改。突然发现一个.c文件,里面什么也没有, 就几个头文件,我一看,我靠,这不是把简单的问题搞复杂了吗,随手删掉那个c文件。 结果不能编译了,我靠: fatal error C1083: Cannot open precompiled header file: \'Debug/v13_3.pch\': No such file or directory 怎么rebuild all都不行。 上网查了一下,才搞懂了: ----------------总结------ 如果工程很大,头文件很多,而有几个头文件又是经常要用的,那么 1。把这些头文件全部写到一个头文件里面去,比如写到preh.h 2。写一个preh.c,里面只一句话:#include "preh.h" 3。对于preh.c,在project setting里面设置creat precompiled headers,对于其他 .c文件,设置use precompiled header file // 哈哈 我试了一下,效果很明显,不用precompiled header,编译一次我可以去上个厕所,用 precompiled header,编译的时候,我可以站起来伸个懒腰,活动活动就差不多啦 ---------转载的文章---------- 预编译头的概念: 所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是 以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的 C/C++代码--------甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会 被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编 译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。注意及 时清理那些没有用的预编译头文件。 也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它 只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过 的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单 位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有 头文件中的东西(.eg Macro, Preprocesser )都要重新处理一遍。VC的预编译头文件 保存的正是这部分信息。以避免每次都要重新处理这些头文件。 预编译头的作用: 根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次 都编译那些不需要经常改变的代码。编译性能当然就提高了。 预编译头的使用: 要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的 代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件) 想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个“系统级别”的 ,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个 典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard 会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们 会发现这个头文件里包含了以下的头文件: #include// MFC core and standard components #include // MFC extensions #include // MFC Automation classes #include // MFC support for Internet Explorer 4 Common Controls #include 这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文 件的,所以说他们是稳定的。 那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我 们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件 里只有一句代码就是:#include “Stdafx.h”。原因是理所当然的,我们仅仅是要它能 够编译而已?D?D?D也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指 定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打 开project ->Setting->C/C++ 对话框。把Category指向Precompiled Header。在左边的 树形视图里选择整个工程 Project Options(右下角的那个白的地方)可以看到 /Fp “debug/PCH.pch”,这就是指 定生成的.pch文件的名字,默认的通常是 <工程名>.pch(我的示例工程名就是PCH)。 然后,在左边的树形视图里选择StdAfx.cpp.//这时只能选一个cpp文件! 这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件 ,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个 Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文 件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件 。 然后我们再选择一个其它的文件来看看,//其他cpp文件 在这里,Precomplier 选择了 Use ???一项,头文件是我们指定创建PCH 文件的stda fx.h 文件。事实上,这里是使用工程里的设置,(如图1)/Yu”stdafx.h”。 这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以 下是注意事项: 1):如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,我强调一遍 是最开头,包含 你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如 果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的, 你自己试以下就知道了,绝对有很惊人的效果?.. fatal error C1010: unexpected end of file while looking for precompiled header directive Generating Code... 2)如果你把pch文件不小心丢了,编译的时候就会产生很多的不正常的行为。根据以上 的分析,你只要让编译器生成一个pch文件。也就是说把 stdafx.cpp(即指定/Yc的那个 cpp文件)从新编译一遍。当然你可以傻傻的 Rebuild All。简单一点就是选择那个cpp 文件,按一下Ctrl + F7就可以了。不然可是很浪费时间的哦。 // 呵呵,如果你居然耐着性子看到了这里,那么再回到帖子最开始看看我的总结吧!
常用CMD 最常用: msconfig.exe 系统配置 实用程序 mspaint 画图板 mstsc 远程桌面连接 devmgmt.msc 设备管理器 notepad 打开记事本 services.msc 本地服务设置 taskmgr 任务管理器 explorer 打开资源管理器 regedit.exe 注册表 cmd.exe cmd命令提示符 calc 启动计算器 compmgmt.msc 计算机管理 inetmgr 打开inretnet信息服务 control 打开控制面板 dxdiag 检查DirectX信息 ipconfig Internet协议配置工具 inetcpl.cpl Internet选项 appwiz.cpl 添加或删除程序 msinfo32.exe 系统信息 sysdm.cpl 系统属性 次常用: diskmgmt.msc 磁盘管理实用程序 dfrg.msc 磁盘碎片整理程序 secpol.msc 本地安全策略 sndvol32 音量控制程序 eventvwr 事件查看器 perfmon.msc 计算机性能监测程序 rononce -p 15秒关机 osk 打开屏幕键盘 lusrmgr.msc 本机用户和组 Clipbrd 剪贴板查看器
C中的预编译宏定义 在将一个C源程序转换为可执行程序的过程中, 编译预处理是最初的步骤. 这一步骤是由预处理器(preprocessor)来完成的. 在源流程序被编译器处理之前, 预处理器首先对源程序中的"宏(macro)"进行处理. C初学者可能对预处理器没什么概念, 这是情有可原的: 一般的C编译器都将预处理, 汇编, 编译, 连接过程集成到一起了. 编译预处理往往在后台运行. 在有的C编译器中, 这些过程统统由一个单独的程序来完成, 编译的不同阶段实现这些不同的功能. 可以指定相应的命令选项来执行这些功能. 有的C编译器使用分别的程序来完成这些步骤. 可单独调用这些程序来完成. 在gcc中, 进行编译预处理的程序被称为CPP, 它的可执行文件名为cpp. 编译预处理命令的语法与C语言的语法是完全独立的. 比如: 你可以将一个宏扩展为与C语法格格不入的内容, 但该内容与后面的语句结合在一个若能生成合法的C语句, 也是可以正确编译的. (一) 预处理命令简介 预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下: #define 定义一个预处理宏 #undef 取消宏的定义 #include 包含文件命令 #include_next 与#include相似, 但它有着特殊的用途 #if 编译预处理中的条件命令, 相当于C语法中的if语句 #ifdef 判断某个宏是否被定义, 若已定义, 执行随后的语句 #ifndef 与#ifdef相反, 判断某个宏是否未被定义 #elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if #else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else #endif #if, #ifdef, #ifndef这些条件命令的结束标志. defined 与#if, #elif配合使用, 判断某个宏是否被定义 #line 标志该语句所在的行号 # 将宏参数替代为以参数值为内容的字符窜常量 ## 将两个相邻的标记(token)连接为一个单独的标记 #pragma 说明编译器信息 #warning 显示编译警告信息 #error 显示编译错误信息 (二) 预处理的文法 预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志. 预处理语句格式: #command name(...) token(s) 1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容. 2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99). 3, 语句中可以利用"\"来换行. e.g. # define ONE 1 /* ONE == 1 */ 等价于: #define ONE 1 #define err(flag, msg) if(flag) \ printf(msg) 等价于: #define err(flag, msg) if(flag) printf(msg) (三) 预处理命令详述 1, #define #define命令定义一个宏: #define MACRO_NAME(args) tokens(opt) 之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的. 对象宏 不带参数的宏被称为"对象宏(objectlike macro)" #define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量. e.g. #define MAX 100 int a[MAX]; #ifndef __FILE_H__ #define __FILE_H__ #include "file.h" #endif #define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件. 要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容. 函数宏 带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率. 函数宏的参数是固定的情况 函数宏的定义采用这样的方式: #define name( args ) tokens 其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号. 注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为 以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格. e.g. #define mul(x,y) ((x)*(y)) 注意, 函数宏之后的参数要用括号括起来, 看看这个例子: e.g. #define mul(x,y) x*y "mul(1, 2+2);" 将被扩展为: 1*2 + 2 同样, 整个标记串也应该用括号引用起来: e.g. #define mul(x,y) (x)*(y) sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0 调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句: e.g. mul (f(a,b), g(c,d)); e.g. #define insert(stmt) stmt insert ( a=1; b=2;) 相当于在代码中加入 a=1; b=2 . insert ( a=1, b=2;) 就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符. insert ((a=1, b=2;)) 可解决上述问题. 在定义和调用函数宏时候, 要注意一些问题: 1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题. example_3.7: #define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp} 如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp}; 明显后面的;是多余的, 我们应该这样调用: swap(1,2) 虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容: #define swap(x,y) \ do { unsigned long _temp=x; x=y; y=_tmp} while (0) swap(1,2); 将被替换为: do { unsigned long _temp=1; 1=2; 2=_tmp} while (0); 在Linux内核源代码中对这种do-while(0)语句有这广泛的应用. 2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明. eg_3.8: #define incr(v, low, high) \ for ((v) = (low),; (v) <= (high); (v)++) 只能以这样的形式被调用: incr(a, 1, 10) /* increase a form 1 to 10 */ 函数宏中的参数包括可变参数列表的情况 C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表. #define name(args, ...) tokens #define name(...) tokens "..."代表可变参数列表, 如果它不是仅有的参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃). 通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况. e.g. #ifdef DEBUG #define my_printf(...) fprintf(stderr, __VA_ARGS__) #else #define my_printf(...) printf(__VA_ARGS__) #endif tokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表. 注意在使用#define时候的一些常见错误: #define MAX = 100 #define MAX 100; =, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";". 注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍. 关于定义宏的另外一些问题 (1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释... e.g. #define NULL 0 #define NULL /* null pointer */ 0 上面的重定义是相同的, 但下面的重定义不同: #define fun(x) x+1 #define fun(x) x + 1 或: #define fun(y) y+1 如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息. 应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告. (2) 在gcc中, 可在命令行中指定对象宏的定义: e.g. $ gcc -Wall -DMAX=100 -o tmp tmp.c 相当于在tmp.c中添加" #define MAX 100". 那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢? ---若-DMAX=1, 则正确编译. ---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1. 注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1 (3) #define所定义的宏的作用域 宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别 e.g. #define ONE 1 sum = ONE + TWO /* sum = 1 + TWO */ #define TWO 2 sum = ONE + TWO /* sum = 1 + 2 */ #undef ONE sum = ONE + TWO /* sum = ONE + 2 */ char c[] = "TWO" /* c[] = "TWO", NOT "2"! */ (4) 宏的替换可以是递归的, 所以可以嵌套定义宏. e.g. # define ONE NUMBER_1 # define NUMBER_1 1 int a = ONE /* a = 1 */ 2, #undef #undef用来取消宏定义, 它与#define对立: #undef name 如够被取消的宏实际上没有被#define所定义, 针对它的#undef并不会产生错误. 当一个宏定义被取消后, 可以再度定义它. 3, #if, #elif, #else, #endif #if, #elif, #else, #endif用于条件编译: #if 常量表达式1 语句... #elif 常量表达式2 语句... #elif 常量表达式3 语句... ... #else 语句... #endif #if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制. else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息. 使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释. e.g. #if 0 { 一大段代码; } #endif 常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0. #if MACRO_NON_DEFINED == #if 0 在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef. 注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息. 4, #ifdef, #ifndef, defined. #ifdef, #ifndef, defined用来测试某个宏是否被定义 #ifdef name 或 #ifndef name 它们经常用于避免头文件的重复引用: #ifndef __FILE_H__ #define __FILE_H__ #include "file.h" #endif defined(name): 若宏被定义,则返回1, 否则返回0. 它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件: #if defined(VAX) && defined(UNIX) && !defined(DEBUG) 和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息. 5, #include , #include_next #include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容. #include "headfile" #include#include 预处理标记 前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种. 实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include 包包含的"系统头文件"可能是另外的文件. 但这不值得被注意. 感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件. 关于#include "headfile"和#include 的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记. 相对于#include, 我们对#include_next不太熟悉. #include_next仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件. 比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件. 可参考cpp手册进一步了解#include_next 6, 预定义宏 标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义. 下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏: __LINE__ 当前语句所在的行号, 以10进制整数标注. __FILE__ 当前源文件的文件名, 以字符串常量标注. __DATE__ 程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注. __TIME__ 程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回. __STDC__ 如果当前编译器符合ISO标准, 那么该宏的值为1 __STDC_VERSION__ 如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L. 我用gcc, 如果不指定-std=c99, 其他情况都给出__STDC_VERSION__未定义的错误信息, 咋回事呢? __STDC_HOSTED__ 如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库. gcc定义的预定义宏: __OPTMIZE__ 如果编译过程中使用了优化, 那么该宏被定义为1. __OPTMIZE_SIZE__ 同上, 但仅在优化是针对代码大小而非速度时才被定义为1. __VERSION__ 显示所用gcc的版本号. 可参考"GCC the complete reference". 要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null 7, #line #line用来修改__LINE__和__FILE__. e.g. printf("line: %d, file: %s\n", __LINE__, __FILE__); #line 100 "haha" printf("line: %d, file: %s\n", __LINE__, __FILE__); printf("line: %d, file: %s\n", __LINE__, __FILE__); 显示: line: 34, file: 1.c line: 100, file: haha line: 101, file: haha 8, #pragma, _Pragma #pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下: #pragma GCC name token(s) #pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的. (1) #pragma GCC dependency dependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息. e.g. 在demo.c中给出这样一句: #pragma GCC dependency "temp-file" 然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ gcc demo.c 会给出这样的警告信息: warning: current file is older than temp-file 如果当前文件比指定的文件新, 则不给出任何警告信息. 还可以在在#pragma中给添加自定义的警告信息. e.g. #pragma GCC dependency "temp-file" "demo.c needs to be updated!" 1.c:27:38: warning: extra tokens at end of #pragma directive 1.c:27:38: warning: current file is older than temp-file 注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息. (2) #pragma GCC poison token(s) 若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息. e.g. #pragma GCC poison scanf scanf("%d", &a); warning: extra tokens at end of #pragma directive error: attempt to use poisoned "scanf" 注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行. (3) #pragma GCC system_header 从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用 #warning显式指明). (这条#pragma语句还没发现用什么大的用处) 由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma: e.g. #define PRAGMA_DEP #pragma GCC dependency "temp-file" 由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句: #define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"") 注意, ()中包含的""引用之前引该加上\转义字符. 9, #, ## #和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中. #用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量. e.g. #define TEST(a,b) printf( #a "<" #b "=%d\n", (a)<(b)); 注意: #只针对紧随其后的token有效! ##用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token. e.g. #define TYPE(type, n) type n 之后调用: TYPE(int, a) = 1; TYPE(long, b) = 1999; 将被替换为: int a = 1; long b = 1999; (10) #warning, #error #warning, #error分别用于在编译时显示警告和错误信息, 格式如下: #warning tokens #error tokens e.g. #warning "some warning" 注意, #error和#warning后的token要用""引用起来! (在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.
目录 一.预处理的工作方式... 3 1.1.预处理的功能... 3 1.2预处理的工作方式... 3 二.预处理指令... 4 2.1.预处理指令... 4 2.2.指令规则... 4 三.宏定义命令----#define. 4 3.1.无参数的宏... 4 3.2带参数的宏... 5 3.3.预处理操作符#和##. 6 3.3.1.操作符#. 6 3.3.2.操作符##. 6 四.文件包含------include. 6 五.条件编译... 7 5.1使用#if 7 5.2使用#ifdef和#ifndef 9 5.3使用#defined和#undef 10 六.其他预处理命令... 11 6.1.预定义的宏名... 11 6.2.重置行号和文件名命令------------#line. 11 6.3.修改编译器设置命令 ------------#pragma. 12 6.4.产生错误信息命令 ------------#error 12 七.内联函数... 13 在嵌入式系统编程中不管是内核的驱动程序还是应用程序的编写,涉及到大量的预处理与条件编译,这样做的好处主要体现在代码的移植性强以及代码的修改方便等方面。因此引入了预处理与条件编译的概念。 在C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。 一.预处理的工作方式 1.1.预处理的功能 在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下: ●将源文件中以”include”格式包含的文件复制到编译的源文件中。 ●用实际值替换用“#define”定义的字符串。 ●根据“#if”后面的条件决定需要编译的代码。 1.2预处理的工作方式 预处理的行为是由指令控制的。这些指令是由#字符开头的一些命令。 #define指令定义了一个宏---用来代表其他东西的一个命令,通常是某一个类型的常量。预处理会通过将宏的名字和它的定义存储在一起来响应#define指令。当这个宏在后面的程序中使用到时,预处理器”扩展”了宏,将宏替换为它所定义的值。 #include指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分“包含”进来。例如:下面这行命令: #include指示预处理器打开一个名字为stdio.h的文件,并将它的内容加到当前的程序中。 预处理器的输入是一个C语言程序,程序可能包含指令。预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器的输出是另外一个程序:原程序的一个编辑后的版本,不再包含指令。预处理器的输出被直接交给编译器,编译器检查程序是否有错误,并经程序翻译为目标代码。 二.预处理指令 2.1.预处理指令 大多数预处理器指令属于下面3种类型: ●宏定义:#define 指令定义一个宏,#undef指令删除一个宏定义。 ●文件包含:#include指令导致一个指定文件的内容被包含到程序中。 ●条件编译:#if,#ifdef,#ifndef,#elif,#else和#dendif指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外。 剩下的#error,#line和#pragma指令更特殊的指令,较少用到。 2.2.指令规则 ●指令都是以#开始。#符号不需要在一行的行首,只要她之前有空白字符就行。在#后是指令名,接着是指令所需要的其他信息。 ●在指令的符号之间可以插入任意数量的空格或横向制表符。 ●指令总是第一个换行符处结束,除非明确地指明要继续。 ●指令可以出现在程序中德任何地方。我们通常将#define和#include指令放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。 ●注释可以与指令放在同一行。 三.宏定义命令----#define 使用#define命令并不是真正的定义符号常量,而是定义一个可以替换的宏。被定义为宏的标示符称为“宏名”。在编译预处理过程时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。 在C语言中,宏分为有参数和无参数两种。 3.1.无参数的宏 其定义格式如下: #define 宏名 字符串 在以上宏定义语句中,各部分的含义如下: ● #:表示这是一条预处理命令(凡是以“#”开始的均为预处理命令)。 ●define:关键字“define”为宏定义命令。 ●宏名:是一个标示符,必须符合C语言标示符的规定,一般以大写字母标示宏名。 ●字符串:可以是常数,表达式,格式串等。在前面使用的符号常量的定义就是一个无参数宏定义。 Notice: 预处理命令语句后面一般不会添加分号,如果在#define最后有分号,在宏替换时分号也将替换到源代码中去。在宏名和字符串之间可以有任意个空格。 Eg:#define PI 3.14 在使用宏定义时,还需要注意以下几点: ●宏定义是宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。 ●宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。 ●宏名在源程序只能够若用引号括起来,则预处理程序不对其作宏替换。 ●宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层替换。 ●习惯上宏名可用大写字母表示,以方便与变量区别。但也允许用小写字母。 3.2带参数的宏 #define命令定义宏时,还可以为宏设置参数。与函数中的参数类似,在宏定于中的参数为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,还要用实参去代换形参。 带参宏定义的一般形式为: #define 宏名(形参表) 字符串 在定义带参数的宏时,宏名和形参表之间不能有空格出现,否则,就将宏定义成为无参数形式,而导致程序出错。 Eg:#define ABS(x) (x)<0?-(x):(x) 以上的宏定义中,如果x的值小于0,则使用一元运算符(-)对其取负,得到正数。 带参的宏和带参的函数相似,但其本质是不同的。使用带参宏时,在预处理时将程序源代码替换到相应的位置,编译时得到完整的目标代码,而不进行函数调用,因此程序执行效率要高些。而函数调用只需要编译一次函数,代码量较少,一般情况下,对于简单的功能,可使用宏替换的形式来使用。 3.3.预处理操作符#和## 3.3.1.操作符# 在使用#define定义宏时,可使用操作符#在字符串中输出实参。Eg: #define AREA(x,y) printf(“长为“#x”,宽为“#y”的长方形的面积:%d\n”,(x)*(y)); 3.3.2.操作符## 与操作符#类似,操作符##也可用在带参宏中替换部分内容。该操作符将宏中的两个部分连接成一个内容。例如,定义如下宏: #define VAR(n) v##n 当使用一下方式引用宏: VAR(1) 预处理时,将得到以下形式: V1 如果使用以下宏定义: #define FUNC(n) oper##n 当实参为1时,预处理后得到一下形式: oper1 四.文件包含------include 当一个C语言程序由多个文件模块组成时,主模块中一般包含main函数和一些当前程序专用的函数。程序从main函数开始执行,在执行过程中,可调用当前文件中的函数,也可调用其他文件模块中的函数。 如果在模块中要调用其他文件模块中的函数,首先必须在主模块中声明该函数原型。一般都是采用文件包含的方法,包含其他文件模块的头文件。 文件包含中指定的文件名即可以用引号括起来,也可以用尖括号括起来,格式如下: #include< 文件名> 或 #include“文件名” 如果使用尖括号<>括起文件名,则编译程序将到C语言开发环境中设置好的 include文件中去找指定的文件。 因为C语言的标准头文件都存放在include文件夹中,所以一般对标准头文件采用尖括号;对编程自己编写的文件,则使用双引号。如果自己编写的文件不是存放在当前工作文件夹,可以在#include命令后面加在路径。 #include命令的作用是把指定的文件模块内容插入到#include所在的位置,当程序编译链接时,系统会把所有#include指定的文件链接生成可执行代码。文件包含必须以#开头,表示这是编译预处理命令,行尾不能用分号结束。 #include所包含的文件,其扩展名可以是“.c”,表示包含普通C语言源程序。也可以是 “.h”,表示C语言程序的头文件。C语言系统中大量的定义与声明是以头文件形式提供的。 通过#define包含进来的文件模块中还可以再包含其他文件,这种用法称为嵌套包含。嵌套的层数与具体C语言系统有关,但是一般可以嵌套8层以上。 五.条件编译 预处理器还提供了条件编译功能。在预处理时,按照不同的条件去编译程序的不同部分,从而得到不同的目标代码。使用条件编译,可方便地处理程序的调试版本和正式版本,也可使用条件编译使程序的移植更方便。 5.1使用#if 与C语言的条件分支语句类似,在预处理时,也可以使用分支,根据不同的情况编译不同的源代码段。 #if 的使用格式如下: #if 常量表达式 程序段 #else 程序段 #endif 该条件编译命令的执行过程为:若常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。 Eg: #define DEBUG 1 int main() { int i,j; char ch[26]; for(i='a';j=0;i<='z';i++,j++) { ch[j]=i; #if DEBUG printf("ch[%d]=%c\n",j,ch[j]); #endif } for(j=0;j<26;j++) { printf("%c",ch[j]); } return 0; } #if预编译命令还可使用多分支语句格式,具体格式如下: #if 常量表达式 1 程序段 1 #elif 常量表达式 2 程序段 2 … … #elif 常量表达式 n 程序段 n #else 程序段 m #endif 关键字#elif与多分支if语句中的else if类似。 Eg: #define os win #if os=win #include"win.h" #elif os=linux #include"linux.h" #elif os=mac #include"mac.h" #endif #if和#elif还可以进行嵌套,C89标准中,嵌套深度可以到达8层,而C99允许嵌套达到63层。在嵌套时,每个#endif,#else或#elif与最近的#if或#elif配对。 Eg: #define MAX 100 #define OLD -1 int main() { int i; #if MAX>50 { #if OLD>3 { i=1; { #elif OLD>0 { i=2; } #else { i=3; } #endif } #else { #if OLD>3 { i=4; } #elif OLD>4 { i=5; } #else { i=6; } #endif } #endif return 0; } 5.2使用#ifdef和#ifndef 在上面的#if条件编译命令中,需要判断符号常量定义的具体值。在很多情况下,其实不需要判断符号常量的值,只需要判断是否定义了该符号常量。这时,可不使用#if命令,而使用另外一个预编译命令———#ifdef. #ifdef命令的使用格式如下: #ifdef 标识符 程序段 1 #else 程序段 2 #endif 其意义是,如果#ifdef后面的标识符已被定义过,则对“程序段1”进行编译;如果没有定义标识符,则编译“程序段2”。一般不使用#else及后面的“程序2”。 而#ifndef的意义与#ifdef相反,其格式如下: #ifndef 标识符 程序段 1 #else 程序段 2 #endif 其意义是:如果未定义标识符,则编译“程序段1”;否则编译“程序段2”。 5.3使用#defined和#undef 与#ifdef类似的,可以在#if命令中使用define来判断是否已定义指定的标识符。例如: #if defined 标识符 程序段 1 #endif 与下面的标示方式意义相同。 #ifdef 标识符 程序段 1 #endif 也可使用逻辑运算符,对defined取反。例如: #if ! define 标识符 程序段 1 #endif 与下面的标示方式意义相同。 #ifndef 标识符 程序段 1 #endif 在#ifdef和#ifndef命令后面的标识符是使用#define进行定义的。在程序中,还可以使用#undef取消对标识符的定义,其形式为: #undef 标识符 Eg: #define MAX 100 …… #undef MAX 在以上代码中,首先使用#define定义标识符MAX,经过一段程序代码后,又可以使用#undef取消已定义的标识符。使用#undef命令后,再使用#ifdef max,将不会编译后的源代码,因为此时标识符MAX已经被取消定义了。 六.其他预处理命令 6.1.预定义的宏名 ANSI C标准预定义了五个宏名,每个宏名的前后均有两个下画线,避免与程序员定义相同的宏名(一般都不会定义前后有两个下划线的宏)。这5个宏名如下: ● __DATE__:当前源程序的创建日期。 ● __FILE__:当前源程序的文件名称(包括盘符和路径)。 ● __LINE__:当前被编译代码的行号。 ● __STDC__:返回编译器是否位标准C,若其值为1表示符合标准C,否则不是标准C. ● __TIME__:当前源程序的创建时间。 Eg: #include int main() { int j; printf("日期:%s\n",__DATE__); printf("时间:%s\n",__TIME__}; printf("文件名:%s\n",__FILE__); printf("这是第%d行代码\n",__LINE__); printf("本编译器%s标准C\n",(__STD__)?"符合":"不符合"); return 0; } 6.2.重置行号和文件名命令------------#line 使用__LINE__预定义宏名赈灾编译的程序行号。使用#line命令可改变预定义宏__LINE__与__FILE__的内容,该命令的基本形如下: #line number[“filename”] 其中的数字为一个正整数,可选的文件名为有效文件标识符。行号为源代码中当前行号,文件名为源文件的名字。命令为#line主要用于调试以及其他特殊应用。 Eg: 1:#include 2:#include 4:#line 1000 6:int main() 7:{ 8: printf("当前行号:%d\n",__LINE__); 9: return 0; 10:} 在以上程序中,在第4行中使用#line定义的行号为从1000开始(不包括#line这行)。所以第5行的编号将为1000,第6行为1001,第7行为1002,第8行为1003. 6.3.修改编译器设置命令 ------------#pragma #pragma命令的作用是设定编译器的状态,或者指示编译器完全一些特定的动作。#pragma命令对每个编译器给出了一个方法,在保持与C语言完全兼容的情况下,给出主机或者操作系统专有的特征。其格式一般为: #pragma Para 其中,Para为参数,可使用的参数很多,下面列出常用的参数: Message参数,该参数能够在编译信息输出窗口中输出对应的信息,这对于源代码信息的控制是非常重要的,其使用方法是: #pragma message(消息文本) 当编译器遇到这条指令时,就在编译输出窗口中将消息文本显示出来。 另外一个使用比较多得pragma参数是code_seg.格式如: #pragma code_seg([“section_name”[,section_class]]) 它能够设置程序中函数代码存放的代码段,在开发驱动程序的时候就会使用到它。 参数once,可保证头文件被编译一次,其格式为: #pragma once 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。 6.4.产生错误信息命令 ------------#error #error命令强制编译器停止编译,并输出一个错误信息,主要用于程序调试。其使用如下: #error 信息错误 注意,错误信息不用双括号括起来。当遇到#error命令时,错误信息将显示出来。 例如,以下编译预处理器命令判断预定义宏__STDC__,如果其值不为1,则显示一个错误信息,提示程序员该编译器不支持ANSI C标准。 #if __STDC__!=1 #error NOT ANSI C #endif 七.内联函数 在使用#define定义带参数宏时,在调用函数时,一般需要增加系统的开销,如参数传递,跳转控制,返回结果等额外操作需要系统内存和执行时间。而使用带参数宏时,通过宏替换可再编译前将函数代码展开导源代码中,使编译后的目标文件含有多段重复的代码。这样做,会增加程序的代码量,都可以减少执行时间。 在C99标准钟,还提供另外一种解决方法:使用内联函数。 在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替代。显然,这种做法不会产生转去转回得问题。都是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标代码量,进而增加空间的开销,而在时间开销上不像函数调用时那么大,可见它是以增加目标代码为代码来换取时间的节省。 定义内联函数的方法很简单,只要在定义函数头的前面加上关键字inline即可。内联函数的定义与一般函数一样。例如,定于一个两个整数相加的函数: #include #include inline int add(int x,int y); inline int add(int x,int y) { return x+y; } int main() { int i,j,k; printf("请输入两个整数的值:\n"); scanf("%d %d",&i,&j); k=add(i,j); printf("k=%d\n",k); return 0; } 在程序中,调用函数add时,该函数在编译时会将以上代码复制过来,而不是像一般函数那样是运行时被调用。 内联函数具有一般函数的特性,它与一般函数所不同之处在于函数调用的处理。一般函数进行调用时,要讲程序执行权转导被调函数中,然后再返回到调用到它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应该注意如下几点: ● 在内联函数内部允许用循环语句和开关语句。 ● 内联函数的定义必须出现在内联函数第一次被调用之前。 其实,在程序中声明一个函数为内联时,编译以后这个函数不一定是内联的, 即程序只是建议编译器使用内联函数,但是编译器会根据函数情况决定是否使用内联,所以如果编写的内联函数中出现循环或者开关语句,程序也不会提示出错,但那个函数已经不是内联函数了。 一般都是讲一个小型函数作为内联函数。
C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境。本节将介绍如何应用预处理程序和注释简化程序开发过程,并提高程序的可读性。ANSI标准定义的C语言预处理程序包括下列命令: #define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma等。非常明显,所有预处理命令均以符号#开头,下面分别加以介绍。 一 #define 命令#define定义了一个标识符及一个串。在源程序中每次遇到该标识符时,均以定义的串代换它。ANSI标准将标识符定义为宏名,将替换过程称为宏替换。命令的一般形式为: #define identifier string 注意: 1该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,仅由一新行结束。 2宏名定义后,即可成为其它宏名定义中的一部分。 3 宏替换仅仅是以文本串代替宏标识符,前提是宏标识符必须独立的识别出来,否则不进行替换。例如: #define XYZ this is a tes 使用宏printf("XYZ");//该段不打印"this is a test"而打印"XYZ"。因为预编译器识别出的是"XYZ" 4如果串长于一行,可以在该行末尾用一反斜杠' \'续行。 #defineLONG_STRING"this is a very long\ string that is used as an example" 5 C语言程序普遍使用大写字母定义标识符。 6 用宏代换代替实在的函数的一大好处是宏替换增加了代码的速度,因为不存在函数调用的开销。但增加速度也有代价:由于重复编码而增加了程序长度。 二 #error 命令#error强迫编译程序停止编译,主要用于程序调试。 #error指令使预处理器发出一条错误消息,该消息包含指令中的文本.这条指令的目的就是在程序崩溃之前能够给出一定的信息。 三 #include 命令#i nclude使编译程序将另一源文件嵌入带有#include的源文件,被读入的源文件必须用双引号或尖括号括起来。例如: #include"stdio.h"或者#include这两行代码均使用C编译程序读入并编译用于处理磁盘文件库的子程序。 将文件嵌入#i nclude命令中的文件内是可行的,这种方式称为嵌套的嵌入文件,嵌套层次依赖于具体实现。 如果显式路径名为文件标识符的一部分,则仅在那些子目录中搜索被嵌入文件。否则,如果文件名用双引号括起来,则首先检索当前工作目录。如果未发现文件,则在命令行中说明的所有目录中搜索。如果仍未发现文件,则搜索实现时定义的标准目录。 如果没有显式路径名且文件名被尖括号括起来,则首先在编译命令行中的目录内检索。如果文件没找到,则检索标准目录,不检索当前工作目录。 四 条件编译命令 有几个命令可对程序源代码的各部分有选择地进行编译,该过程称为条件编译。商业软件公司广泛应用条件编译来提供和维护某一程序的许多顾客版本。 #if、#else,#elif及#endif #if的一般含义是如果#if后面的常量表达式为true,则编译它与#endif之间的代码,否则跳过这些代码。命令#endif标识一个#if块的结束。 #if constant-expression statement sequence #endif Eg: #define MAX 91 #include using namespace std; int main() { #if MAX > 99 cout<<"MAX is bigger than 99"<<endl; #elif MAX > 90 cout<<"MAX is bigger than 90"<<endl; #else cout<<"MAX is smaller than 90"<<endl; #endif return 0; } 跟在#if后面的表达式在编译时求值,因此它必须仅含常量及已定义过的标识符,不可使用变量。表达式不许含有操作符sizeof(sizeof也是编译时求值)。 #else命令的功能有点象C语言中的else;#else建立另一选择(在#if失败的情况下)。注意,#else属于#if块。 #elif命令意义与ELSE IF 相同,它形成一个if else-if阶梯状语句,可进行多种编译选择。#elif 后跟一个常量表达式。如果表达式为true,则编译其后的代码块,不对其它#elif表达式进行测试。否则,顺序测试下一块。 #if expression statement sequence #elif expression1 statement sequence #endif 在嵌套的条件编译中#endif、#else或#elif与最近#if或#elif匹配。 # ifdef 和# ifndef 条件编译的另一种方法是用#ifdef与#ifndef命令,它们分别表示"如果有定义"及"如果无定义"。# ifdef的一般形式是: # ifdef macroname statement sequence #endif #ifdef与#ifndef可以用于#if、#else,#elif语句中,但必须与一个#endif。 #define MAX 91 #include using namespace std; int main() { #ifdef MAX cout<<"hello,MAX!"<<endl; #else cout<<"where is MAX?"<<endl; #endif #ifndef LEO cout<<"LEO is not defined"<<endl; #endif return 0; } 命令#undef 取消其后那个前面已定义过有宏名定义。一般形式为: #undef macroname 命令#line改变__LINE__与__FILE__的内容,它们是在编译程序中预先定义的标识符。命令的基本形式如下: #line number["filename"] 其中的数字为任何正整数,可选的文件名为任意有效文件标识符。行号为源程序中当前行号,文件名为源文件的名字。命令#line主要用于调试及其它特殊应用。注意:在#line后面的数字标识从下一行开始的数字标识。 #line 100 "jia" cout<<"#line change line and filename!"< //line 100 cout<<__LINE__< //101 cout<<__FILE__< //jia 五 #pragma 命令#pragma 为实现时定义的命令,它允许向编译程序传送各种指令。 #pragma的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 其格式一般为: #Pragma Para 1 message 参数。 Message 参数能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为: #pragma message(“消息文本”) 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法 #ifdef _X86 #pragma message(“_X86 macro activated!”) #endif 当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_ X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。 2 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) /* 不显示4507和34号警告信息。如果编译时总是出现4507号警告和34号警告, 而认为肯定不会有错误,可以使用这条指令。*/ #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 ) 在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。 7 pragma comment(...) 该指令将一个注释记录放入一个对象文件或可执行文件中。 常用的lib关键字,可以帮我们连入一个库文件。 8 progma pack(n) 指定结构体对齐方式。#pragma pack(n)来设定变量以n字节对齐方式。 n 字节对齐就是说变量存放的起始地址的偏移量有两种情况: 第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式, 第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。 结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必须为n的倍数。 下面举例说明其用法。 #pragma pack(push) //保存对齐状态 #pragma pack(4)//设定为4字节对齐 struct test { char m1; double m4; int m3; }; #pragma pack(pop)//恢复对齐状态 为测试该功能,可以使用sizeof()测试结构体的长度!
其格式一般为: #pragma Para。
message 参数
Message 参数能够在编译信息输出窗口中输出相应的信息,这对于 源代码信息的控制是非常重要的。其使用方法为:
1
|
#pragmamessage(“消息文本”)
|
当 编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制 源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在 编译的时候就进行检查。假设我们希望判断自己有没有在 源代码的什么地方定义了_X86这个宏可以用下面的方法
1
2
3
|
#ifdef_X86
#pragmamessage("_X86macroactivated!")
#endif
|
当我们定义了_X86这个宏以后, 应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated! ”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。
code_seg
另一个使用得比较多的pragma参数是code_seg。格式如:
1
|
#pragmacode_seg(["section-name"[,"section-class"]])
|
它能够设置程序中函数代码存放的 代码段,当我们开发 驱动程序的时候就会使用到它。
#pragma once
(比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
#pragma once是编译相关,就是说这个 编译系统上能用,但在其他编译系统不一定可以,也就是说移植性差,不过现在基本上已经是每个 编译器都有这个定义了。
#ifndef,#define,#endif这个是C++语言相关,这是C++语言中的 宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的 编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式
#pragma hdrstop
#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以 预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。
#pragma resource
#pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括 窗体外观的定义。
#pragma warning
1
|
#pragmawarning(disable:450734;once:4385;error:164)
|
等价于:
1
2
3
|
#pragmawarning(disable:450734)//不显示4507和34号警告信息
#pragmawarning(once:4385)//4385号警告信息仅报告一次
#pragmawarning(error:164)//把164号警告信息作为一个错误。
|
同时这个pragma warning 也支持如下格式:
1
2
|
#pragmawarning(push[,n])
#pragmawarning(pop)
|
这里n代表一个警告等级(1---4)。
1
2
3
|
#pragmawarning(push)保存所有警告信息的现有的警告状态。
#pragmawarning(push,n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。
#pragmawarning(pop)向栈中弹出最后一个警告信息,
|
在入栈和 出栈之间所作的一切改动取消。例如:
1
2
3
4
5
6
|
#pragmawarning(push)
#pragmawarning(disable:4705)
#pragmawarning(disable:4706)
#pragmawarning(disable:4707)
//.......
#pragmawarning(pop)
|
在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。
pragma comment
1
|
pragmacomment(...)
|
该指令将一个注释记录放入一个对象文件或 可执行文件中。
常用的lib关键字,可以帮我们连入一个 库文件。
每个 编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。例如,对循环优化功能:
1
2
|
#pragmaloop_opt(on)//激活
#pragmaloop_opt(off)//终止
|
有时,程序中会有些函数会使 编译器发出你熟知而想忽略的警告,如“Parameter xxx is never used in function xxx”,可以这样:
1
2
3
4
|
#pragmawarn—100//Turnoffthewarningmessageforwarning#100
intinsert_record(REC*r)
{
/*functionbody*/
}
#pragmawarn+100//Turnthewarningmessageforwarning#100backon
|
函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。
每个 编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从 编译器的文档中查看。
1
2
3
4
5
6
|
#pragmapack(n)和#pragmapop()
structsample
{
chara;
doubleb;
};
|
当sample结构没有加#pragma pack(n)的时候,sample按最大的成员那个对齐;
(所谓的对齐是指对齐数为n时,对每个成员进行对齐,既如果成员a的大小小于n则将a扩大到n个大小;
如果a的大小大于n则使用a的大小;)所以上面那个结构的大小为16字节.
当sample结构加#pragma pack(1)的时候,sizeof(sample)=9字节;无空字节。
(另注:当n大于sample结构的最大成员的大小时,n取最大成员的大小。
所以当n越大时,结构的速度越快,大小越大;反之则)
#pragma pop()就是取消#pragma pack(n)的意思了,也就是说接下来的结构不用#pragma pack(n)
1
|
#pragmacomment(comment-type,["commentstring"])
|
comment-type是一个预定义的 标识符,指定注释的类型,应该是compiler,exestr,lib,linker之一。
comment string是一个提供为comment-type提供附加信息的字符串。
注释类型:
1、compiler:
放置 编译器的版本或者名字到一个对象文件,该选项是被linker忽略的。
2、exestr:
在以后的版本将被取消。
3、lib:
放置一个库搜索记录到对象文件中,这个类型应该是和comment string(指定你要Linker搜索的lib的名称和路径)这个库的名字放在Object文件的默认库搜索记录的后面,linker搜索这个这个库就像你在命令行输入这个命令一样。你可以在一个 源文件中设置多个库记录,它们在object文件中的顺序和在源文件中的顺序一样。如果默认库和附加库的次序是需要区别的,使用Z编译开关是防止默认库放到object模块。
4、linker:
指定一个连接选项,这样就不用在命令行输入或者在 开发环境中设置了。
只有下面的linker选项能被传给Linker.
1
|
/DEFAULTLIB,/EXPORT,/INCLUDE,/MANIFESTDEPENDENCY,/MERGE,/SECTION
|
(1) /DEFAULTLIB:library
/DEFAULTLIB 选项将一个 library 添加到 LINK 在解析引用时搜索的库列表。用 /DEFAULTLIB指定的库在命令行上指定的库之后和 .obj 文件中指定的默认库之前被搜索。忽略所有默认库 (/NODEFAULTLIB) 选项重写 /DEFAULTLIB:library。如果在两者中指定了相同的 library 名称,忽略库 (/NODEFAULTLIB:
library) 选项将重写 /DEFAULTLIB:
library。
(2)/EXPORT:
entryname[,@
ordinal[,NONAME]][,DATA]
使用该选项,可以从程序导出函数,以便其他程序可以调用该函数。也可以导出数据。通常在 DLL 中定义导出。
entryname是调用程序要使用的函数或 数据项的名称。ordinal 在导出表中指定范围在 1 至 65,535 的索引;如果没有指定 ordinal,则 LINK 将分配一个。
NONAME关键字只将函数导出为序号,没有
entryname。
DATA 关键字指定导出项为 数据项。客户程序中的 数据项必须用
extern __declspec(dllimport)来声明。
有三种导出定义的方法,按照建议的使用顺序依次为:
源代码中的 __declspec(dllexport).def 文件中的 EXPORTS 语句LINK 命令中的 /EXPORT 规范所有这三种方法可以用在同一个程序中。LINK 在生成包含导出的程序时还创建 导入库,除非生成中使用了 .exp 文件。
LINK 使用 标识符的修饰形式。 编译器在创建 .obj 文件时修饰 标识符。如果
entryname以其未修饰的形式指定给 链接器(与其在 源代码中一样),则 LINK 将试图匹配该名称。如果无法找到唯一的匹配名称,则 LINK 发出 错误信息。当需要将 标识符指定给 链接器时,请使用 Dumpbin 工具获取该标识符的修饰名形式。
(3)/INCLUDE:symbol
/INCLUDE 选项通知 链接器将指定的符号添加到 符号表。
若要指定多个符号,请在符号名称之间键入逗号 (,)、分号 (;) 或空格。在命令行上,对每个符号指定一次 /INCLUDE:symbol。
链接器通过将包含符号定义的对象添加到程序来解析 symbol。该功能对于添包含不会链接到程序的库对象非常有用。用该选项指定符号将通过 /OPT:REF 重写该符号的移除。
我们经常用到的是#pragma comment(lib,"*.lib")这类的。#pragma comment(lib,"Ws2_32.lib")表示链接Ws2_32.lib这个库。 和在工程设置里写上链入Ws2_32.lib的效果一样,不过这种方法写的 程序别人在使用你的代码的时候就不用再设置工程settings了
#pragma disable
在函数前声明,只对一个函数有效。该函数调用过程中将不可被中断。一般在C51中使用较多。
#pragma data_seg
介绍[1]
用#pragma data_seg建立一个新的 数据段并定义共享数据,其具体格式为:
1
2
3
|
#pragmadata_seg("shareddata")
HWNDsharedwnd=NULL;
//共享数据
#pragmadata_seg()
|
-----------------------------------------------------------------
1,#pragma data_seg()一般用于DLL中。也就是说,在DLL中定义一个共享的有名字的 数据段。最关键的是:这个 数据段中的 全局变量可以被多个进程共享,否则多个进程之间无法共享DLL中的全局变量。
2,共享数据必须初始化,否则微软 编译器会把没有初始化的数据放到. BSS段中,从而导致多个进程之间的共享行为失败。例如,
1
2
3
4
5
6
7
8
9
10
11
12
|
#pragmadata_seg("MyData")
intg_Value;
//Notethattheglobalisnotinitialized.
#pragmadata_seg()
//DLL提供两个接口函数:
intGetValue()
{
returng_Value;
}
voidSetValue(intn)
{
g_Value=n;
}
|
然后启动两个进程A和B,A和B都调用了这个DLL,假如A调用了SetValue(5); B接着调用int m = GetValue(); 那么m的值不一定是5,而是一个未定义的值。因为DLL中的全局数据对于每一个调用它的进程而言,是私有的,不能共享的。假如你对g_Value进行了初始化,那么g_Value就一定会被放进MyData段中。换句话说,如果A调用了SetValue(5); B接着调用int m = GetValue(); 那么m的值就一定是5,这就实现了跨进程之间的数据通信。
应用实例(#pragma pack)
在 网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过 指针偏移的方法来得到各种信息,但这样做不仅 编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了 编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。下面以TCP协议首部为例,说明如何定义协议结构。
其协议结构定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#pragmapack(1)//按照1字节方式进行对齐
structTCPHEADER
{
shortSrcPort;
//16位源端口号
shortDstPort;
//16位目的端口号
intSerialNo;
//32位序列号
intAckNo;
//32位确认号
unsignedcharHaderLen:4;
//4位首部长度
unsignedcharReserved1:4;
//保留16位中的4位
unsignedcharReserved2:2;
//保留16位中的2位
unsignedcharURG:1;
unsignedcharACK:1;
unsignedcharPSH:1;
unsignedcharRST:1;
unsignedcharSYN:1;
unsignedcharFIN:1;
shortWindowSize;
//16位窗口大小
shortTcpChkSum;
//16位TCP检验和
shortUrgentPointer;
//16位紧急指针
};
#pragmapop()//取消1字节对齐方式
|
#pragma pack规定的对齐长度,实际使用的规则是: 结构,联合,或者类的 数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。 但是,当#pragma pack的值等于或超过最长 数据成员的长度的时候,这个值的大小将不产生任何效果。 而结构整体的对齐,则按照 结构体中最大的 数据成员进行。
C中的预处理命令 C中的预处理命令是由ANSIC统一规定的,但它不是C语言的本身组成部分,不能直接对它们进行编译,因为编译程序无法识别它们。必须对程序进行通常的编译(包括词法和语法分析,代码生成,优化等)之前,先对程序中这些特殊的命令进行“预处理”,例如:如果程序中用#include命令包含一个文件“stdio.h”,则在预处理时,将stdio.h文件中的实际内容代替该命令。经过预处理后的程序就像没有使用预处理的程序一样干净了,然后再由编译程序对它进行编译处理,得到可供执行的目标代码。现在的编译系统都包括了预处理,编译和连接部分,在进行编译时一气呵成。我们要记住的是预处理命令不是C语言的一部分,它是在程序编译前由预处理程序完成的。 C提供的预处理功能主要有三种:宏定义,文件包含,条件编译。它们的命令都以“#”开头。 一 , 宏定义 :用一个指定的标识符来代表一个字符串,它的一般形式为: #define 标识符 字符串 #define PI 3.1415926 我们把标识符称为“宏名”,在预编译时将宏名替换成字符串的过程称为“宏展开”,而#define是宏定义命令。 几个应该注意的问题: 1, 是用宏名代替一个字符串,也就是做简单的置换,不做正确性检查,如把上面例子中的1写为小写字母l,预编译程序是不会报错的,只有在正式编译是才显示出来。 2, 宏定义不是C语句,不必在行未加分号,如果加了分号则会连分号一起置换。 3, #define语句出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束,通常#define命令写在文件开头,函数之前,作为文件的一部分,在此文件范围内有效。 4, 可以用#undef命令终止宏定义的作用域。如: #define PI 3.1415926 main(){ } #undef PI mysub(){ } 则在mysub中PI 不代表3.1415926。 5, 在进行宏定义时,可以引用已定义的宏名,可以层层置换。 6, 对程序中用双撇号括起来的字符串内的字符,即使与宏名相同,也不进行置换。 7, 宏定义是专门用于预处理命令的一个专有名词,它与定义变量的含义不同,只做字符替换不做内存分配。 带参数的宏定义,不只进行简单的字符串替换,还进行参数替换。定义的一般形式为:#define 宏名(参数表)字符串 如:#define S(a,b) a*b,具体使用的时候是int area; area=(2,3); 对带参数的宏定义是这样展开置换的:在程序中如果有带参数的宏(如area=(2,3)),则按#define命令行中指定的字符串从左到右进行置换。如果串中包含宏中的形参(如a,b),则将程序语句中的相关参数(可以是常量,变量,或表达式)代替形参。如果宏定义中的字符串中的字符不是参数字符(如上*),则保留,这样就形成了置换的字符串。 带参数的宏与函数有许多相似之处,在调用函数时也是在函数名后的括号内写实参,也要求实参与形参的数目相等,但它们之间还有很大的不同,主要有: 1, 函数调用时,先求出实参表达式的值,然后代入形参,而使用带参的宏只是进行简单的字符替换。 2, 函数调用是在程序运行时处理的,为形参分配临时的内存单元。而宏展开则是在编译前进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有返回值的概念。 3, 对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时代入指定的字符串即可。宏定义时,字符串可以是任何类型的数据。 4, 函数调用只可得到一个返回值,而用宏可以设法得到几个结果。 5, 使用宏次数多时,宏展开后源程序长,因为没展开一次都使程序增长,而函数调用不会这样。 6, 宏替换不占运行时间,只占编译时间,而函数调用则占运行时间(分配单元,保留现场,值传递,返回)。 二 , 文件包含:一个源文件可以将另一个源文件的全部内容包含进来,即将另外的文件包含到本文件中。 #include <文件名> 或 #include“文件名” 感觉它像JAVA中的包,而它的作用像在J2EE中我们可以用*.xml做配置文件,然后各个模块调用这个文件,但这个文件如果修改后,凡使用(包含)此文件的所有文件(因为使用时是拷贝了原来的一份)有都需要从新编译,好像又失去了灵活的意义。 在#include命令中,文件名可以用“”或<>括起来,它们的区别是用<>时,系统到存放在用户当前目录中寻找要包含的文件,若找不到,再按照标准方式查找(即按尖括号的方式查找)。一般说来,如果是为调用库函数而用#include命令来包含相关的头文件,则用<>,以节省查找时间。如果要包含的是用户自己编写的文件(这种文件一般都在当前目录中),一般用“”,若文件不在当前目录中,“”内可给出文件路径。 三,条件编译 一般情况下,源程序中的所有行都参加编译。但有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是条件编译。 1,#indef 标识符 程序段1 #else 程序段2 #endif 当所指定的标识符已经被#include命令定义过,则在程序编译阶段只编译程序1,否则编译程序段2。 2,#if 表达式 程序段1 #else 程序段2 #endif 优点:采用条件编译,可以减少被编译的语句,从而减少目标程序的长度,减少运行时间,当条件编译段比较多时,目标程序长度可大减少。 大