预处理(宏定义、文件包含、条件编译)

来源:互联网 发布:网络维护好学吗 编辑:程序博客网 时间:2024/06/01 09:51

预处理(pre-treatment),是指在进行最后加工完善以前进行的准备过程,具体应用在不同的行业或领域,会有不同的解释。

      一、含义

程序设计中的预处理(Preprocess),程序设计领域,预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的符号用来支持宏调用。
C预处理的概念:ANSI C标准规定,可以在C源程序中使用编译预处理命令,就是程序进行编译前对程序中的编译预处理命令作相应的预处理。
二、相关概念
1、预处理器(Preprocessor)
预处理器是一个文本处理程序,它在程序编译的第一个阶段处理源代码的文本。当然预处理器不只是编译之前才被调用处理源代码,它也可以被其他程序单独的调用以实现文本的处理。
2、预处理指令
预处理指令,比如#define 和 #ifdef,一般被用来使源代码在不同的执行环境中被方便的修改或者编译。源代码中这些指令会告诉预处理器执行特定的操作。比如告诉预处理器在源代码中替换特定字符等。
------------------------------------------------------------------------------------------------------------
C提供的预处理主要有三种——宏定义包含条件编译

    三、宏定义

    1 不带参数

宏定义又称为宏代换、宏替换,简称“宏”。
格式:
#define  标识符   字符串
其中的标识符就是所谓的符号常量,也称为“宏名”。
预处理(预编译)工作也叫做宏展开:将宏名替换为字符串。
掌握"宏"概念的关键是“换”。一切以换为前提、做任何事情之前先要换,准确理解之前就要“换”。
即在对相关命令或语句的含义和功能作具体分析之前就要换:
#define Pi 3.1415926
把程序中出现的Pi全部换成3.1415926
说明
(1)宏名一般用大写
(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组大小常用宏定义
(3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
(4)宏定义末尾不加分号;
(5)宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。
(6)可以用#undef命令终止宏定义的作用域
(7)宏定义允许嵌套
(8)字符串" "中永远不包含宏
(9)宏定义不分配内存,变量定义分配内存。
(10)宏定义不存在类型问题,它的参数也是无类型的。
2 带参数
除了一般的字符串替换,还要做参数代换
格式
#define宏名(参数表) 字符串
例如:#define S(a,b) a*b
area=S(3,2);第一步被换为area=a*b; ,第二步被换为area=3*2;
类似于函数调用,有一个哑实结合的过程:
(1)实参如果是表达式容易出问题
#define S(r) r*r
area=S(a+b);第一步换为area=r*r;,第二步被换为area=a+b*a+b;
正确的宏定义是#define S(r) ((r)*(r))
(2)宏名和参数的括号间不能有空格
(3)宏替换只作替换,不做计算,不做表达式求解
(4)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存
(5)宏的哑实结合不存在类型,也没有类型转换。
(6)函数只有一个返回值,利用宏则可以设法得到多个值
(7)宏展开使源程序变长,函数调用不会
(8)宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)
#define用法:
1、 用无参宏定义一个简单的常量
#define LEN 12
这个是最常见的用法,但也会出错。
比如下面几个知识点你会吗?可以看下:
(1)#define NAME "zhangyuncong"
程序中有"NAME"则,它会不会被替换呢?
(2)#define 0x abcd
可以吗?也就是说,可不可以用不是标识符的字母替换成别的东西?
(3)#define NAME "zhang
这个可以吗?
(4)#define NAME "zhangyuncong"
程序中有上面的宏定义,并且,程序里有句:
NAMELIST这样,会不会被替换成"zhangyuncong"LIST
四个题答案都是十分明确的。
第一个,""内的东西不会被宏替换。这一点应该大都知道。
第二个,宏定义前面的那个必须是合法的用户标识符
第三个,宏定义也不是说后面东西随便写,不能把字符串的两个""拆开。
第四个:只替换标识符,不替换别的东西。NAMELIST整体是个标识符,而没有NAME标识符,所以不替换。
也就是说,这种情况下记住:#define第一位置第二位置
(1) 不替换程序中字符串里的东西。
(2) 第一位置只能是合法的标识符(可以是关键字)
(3) 第二位置如果有字符串,必须把""配对。
(4) 只替换与第一位置完全相同的标识符
还有就是老生常谈的话:记住这是简单的替换而已,不要在中间计算结果,一定要替换出表达式之后再算。
2、 带参宏一般用法
比如#define MAX(a,b) ((a)>(b)?(a):(b))
则遇到MAX(1+2,value)则会把它替换成:
((1+2)>(value)?(1+2):(value))
注意事项和无参宏差不多。
但还是应注意
#define FUN(a) "a"
则,输入FUN(345)会被替换成什么?
其实,如果这么写,无论宏的实参是什么,都不会影响其被替换成"a"的命运。
也就是说,""内的字符不被当成形参,即使它和一模一样。
那么,你会问了,我要是想让这里输入FUN(345)它就替换成"345"该怎么实现呢?
请看下面关于#的用法
3、 有参宏定义中#的用法
#define STR(str) #str
#用于把宏定义中的参数两端加上字符串的""
比如,这里STR(my#name)会被替换成"my#name"
一般由任意字符都可以做形参,但以下情况会出错:
STR())这样,编译器不会把“)”当成STR()的参数。
STR(,)同上,编译器不会把“,”当成STR的参数。
STR(A,B)如果实参过多,则编译器会把多余的参数舍去。(VC++2008为例)
STR((A,B))会被解读为实参为:(A,B),而不是被解读为两个实参,第一个是(A第二个是B)。
4、 有参宏定义中##的用法
#define WIDE(str) L##str
则会将形参str的前面加上L
比如:WIDE("abc")就会被替换成L"abc"
如果有#defineFUN(a,b) vo##a##b()
那么FUN(id ma,in)会被替换成void main()
5、 多行宏定义:
#define doit(m,n) for(int i=0;i<(n);++i)\
{\
m+=i;\
}
如:BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
       //{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
       //}}AFX_MSG_MAP
             END_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)和  END_MESSAGE_MAP()都是宏定义,不是函数,在他们之间添加消息响应函数。
四、条件编译
          一般情况下,源程序中所有的行都参加编译。但有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”(conditional compile)。条件编译语句排版时,需考虑以下三种位置:1)条件编译语句块与函数定义体之间不存在相互嵌套(主要在(.h)文件中);2)条件编译语句块嵌套在函数体之外(主要在(.c)文件中);3)条件编译语句嵌套在函数体内 (主要在(.c)文件中)。条件编译指令将决定那些代码被编译,而哪些是不被编译的。可根据表达式的值或某个特定宏是否被定义来确定编译条件。

   1 格式:

(1)不相互嵌套

条件编译关键字语句顶格左对齐;
所含的#include语句(块) #define语句(块)甚至是被嵌套下级条件编译语句块,按照语句块嵌套的排版方式进行缩进排版 。
(2)函数体外
这种情况下,条件编译语句块不影响函数体
条件编译关键字语句顶格左对齐;
所含的函数体定义无需缩进,依旧按照单个函数体定义的排版方式进行。
(3)函数体内
a)当条件编译语句块与被包语句所属的语句块之间没有逻辑路径交叉时,以下两种方式均可
按照语句块嵌套方式进行缩进排版 (推荐);
条件编译语句不影响原先语句块排版,条件编译语句与所包含的关键字语句块左对齐 。
b)当条件编译语句块与被包语句所属的语句块之间存在逻辑路径交叉时
条件编译语句顶格左对齐,其它语句按照正常顺序排版。
(4)条件编译的形式如下所示(NNN、MMM等都是在某处已经定义为 1 或者 0 的):
#if NNN
statement1;
#elif MMM
statement2;
#else
statement3;
#endif
2  指令的介绍及使用
条件编译指令将决定哪些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。
(1).#if、#else、#elif和#endif指令
一般形式有如下几种
(a)#if 表达式
语句段1
[#else
语句段2]
#endif
如果表达式为真,就编译语句段1,否则编译语句段2
(b)#if 表达式1
语句段1
#elif 表达式2
语句段2
#else
语句段3
#endif
如果表达式1真,则编译语句段1,否则判断表达式2;如果表达式2为真,则编译语句段2,否则编译语句段3。
源文件中的每个#if指令必须与一个#endif指令匹配。 任意数量的 #elif 指令可以出现在 #if 和 #endif 指令之间,但是,最多允许一个 #else 指令。 #else 指令,如果有,则必须是 #endif 之前的最后一个指令。
(2).#ifdef和#ifndef
(a)#ifdef的一般形式:
#ifdef 宏名
语句段
#endif
作用:如果在此之前已定义了这样的宏名,则编译语句段。
(b)#ifndef的一般形式:
#ifndef 宏名
语句段
#endif
作用:如果在此之前没有定义这样的宏名,则编译语句段。
#else可以用于#ifdef和#ifndef中,但#elif不可以。
(3).#error
指令将使编译器显示一条错误信息,然后停止编译。
(4).#line
指令可以改变编译器用来指出警告和错误信息的文件号和行号。
(5).#pragma
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。   如:#pragma comment(lib,"wsock32.lib")
具体详见:http://baike.baidu.com/link?url=kXQHx-II63NRwO5-ajaeALxlEn4nq1GJd0UVI3oD4ODVohYKfbk_nQn9ojBu4iyt-YM-4AGBwEnuLTZNxlIH4_,讲的挺详细
举例
#include <stdio.h>
#define LETTER 1
void main()
{char str[20]="C Language",c;
int i;
i=0;
while((c=str[i])!='\0')
{ i++;
#if LETTER
if(c>='a' && c<='z')
c=c-32;
#else
if(c>='A' && c<='Z')
c=c+32;
#endif
printf("%c",c);
}
}运行结果为:C LANGUAGE
五、文件包含
文件包含是指一个C语言源程序中将另一个C语言源程序包含进来,通过#include预处理指令实现。如下图所示:

1、一般形式:
#include<被包含文件名>或者#include"被包含文件名".
2、作用:
预处理时,将制定文件包含到当前文件中,插入到文件包含指令相应位置处,再对合并后的文件进行编译。
3、用途:
减少程序设计人员的重复劳动,提高程序开发效率。

转载自:http://blog.csdn.net/bzhxuexi/article/details/12854471
参考:http://www.cnblogs.com/rusty/archive/2011/03/27/1996806.html


0 0