重读The C programming Lanuage 笔记四:c预处理
C预处理器执行宏替换、条件编译以及包含指定的文件。以#开头的命令行就是与处理器的对象。这些命令行的语法独立于语言的其他部分,它们可以出现在任何地方,其作用可延续到所在编译单元的末尾(与作用域无关)。行边界是有实际意义的;每一行都将单独进行分析。对预处理器而言,记号可以是任何语言记号,也可以是类似于#include指令中表示文件名的字符序列。此外,所有未进行其他定义的字符都将被认为是记号。但是在预处理器指令行中,除空格、横向制表符以外的其他空白符的作用都是没有意义的。
以下摘自百度知道http://zhidao.baidu.com/link?url=kRcWYY-uCXnJSZAovYKbx5iuuRN6NOzPgG13tsDgAK2bIoOc-ngUJFfibod6RWQK1rs6Ni8zOeQDCkEzMPf1BSSo9HbjawwGGAyDCwlcDly
————————————————————————————————————————————————————————————
一、 宏定义和扩展
宏定义:
1.不带参数的宏定义:
格式:#define 标识符 字符串 标识符即“宏名"
预处理器的的工作叫做宏展开:将宏名替换为字符串
如 #define Pi 3.14
说明:
1、宏名一般大写
2、使用宏可提高程序的通用性和易读性,便于修改。 例如:数组大小常用宏定义
3、预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
4、宏定义末尾不加分号(;)
5、宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。
6、可以用#undef命令终止宏定义的作用域
7、宏定义可以嵌套
8、字符串”“中永远不包含宏
9、宏定义不分配内存,变量定义才分配内存
2,带参数的宏定义
格式:#define 宏名(参数表) 字符串
例如:#define S(a,b) a*b 则m =S(3,2) 被展开为 m = 3*2 (宏展开只替换不计算结果,此时m值不为6)
说明
1、实参如果是表达式容易出问题 如#define s(x) x*x 则 s(x+2) 将扩展为 x + 2 * x + 2 正确应写为 #define s(x) (x)*(x)
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"
————————————————————————————————————————————
附注:
假定有下列定义:#define tempfile(dir) #dir "%s" 宏调用tempfile(/usr/tmp)将生成"/usr/tmp" "%s"
随后,该结果将被连接为一个单个的字符串。给定下列定义: #define cat(x, y) x ## y 那么,宏调用cat(var, 123)将生成var123。
但是,宏调用cat(cat(1,2),3)没有定义:##阻止了外层调用的参数的扩展。因此,它将生成下列记号串:cat ( 1 , 2 )3
并且,)3 (不是一个合法的记号,它由第一个参数的最后一个记号与第二个参数的第一个记号连接而成。
如果再引入第二层的宏定义,如下所示:#define xcat(x, y) cat(x,y)
我们就可以得到正确的结果。xcat(xcat(1, 2), 3)将生成123,因为xcat 自身的扩展不包含##运算符。
类似地,ABSDIFF(ABSDIFF(a,b),c)将生成所期望的经完全扩展后的结果。
————————————————————————————————————————
一般由任意字符都可以做形参,但以下情况会出错:
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"
如果有#define FUN(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;\
}
——————————————————————————————————————————————————————————
以上
二、文件包含
下列形式的控制指令:#include <文件名>
将把该行替换为文件名指定的文件的内容。文件名不能包含>或换行符。如果文件名中包含字符"、'、\、或/*,则其行为没有定义。预处理器将在某些特定的位置查找指定的文件
查找的位置与具体的实现相关。
类似地,下列形式的控制指令:#include "文件名"
首先从源文件的位置开始搜索指定文件(搜索过程与具体的实现相关),如果没有找到指定的文件,则按照第一种定义的方式处理。如果文件名中包含字符'、\、或/*,其结果仍然
是没有定义的,但可以使用字符>。
最后,下列形式的指令行:
#include 记号序列
同上述两种情况都不同,它将按照扩展普通文本的方式扩展记号序列进行解释。记号序列必须被解释为<...>或"..."两种形式之一,然后再按照上述方式进行相应的处理。
#include文件可以嵌套。
三、条件编译
预处理器条件:
if 行文本elif 部分opt else部分opt #endif
if行:
#if 常量表达式
#ifdef 标识符
#ifndef 标识符
elif部分:
elif行文本elif 部分opt
elif行:
#elif 常量表达式
else 部分:
else行文本
else行:
#else
其中,每个条件编译指令在程序中单独占一行。预处理器依次对#if以及以后的#elif行中的常量表达式进行计算,直至发现某个指令的常量表达式为非零值为止,这是将放弃值为零的指令行后面的文本。
#if和#elif中的常量表达式将执行通常的宏替换。并且如: define 标识符 或 Define(标识符) 都将在宏扫描之前进行替换。
如果该标识符在预处理器中已经定义,则用1替换,否则用0替换
#ifdef 标识蒋
#ifndef 标识符
分别等价于:
#if defined 标识符
#if !defined 标识符
例如:为保证hdr.h文件只被包含一次,可以将该文件内容包含在下列形式的条件语句中
#if !define(HDR)
#define HDR
//HDR内容
#endif
四、行控制
为了便于其它预处理器生成C语言程序,下列形式的指令行:
#line 常量 "文件名"
#line 常量
将使编译器认为(出于错误诊断的目的):下一行源代码的行号是以十进制整型常量的形式给出的,并且,当前的输入文件是由该标识符命名的。如果缺少带双引号的文件名部分,则将
不改变当前编译的源文件的名字。行中的宏将先进行扩展,然后再进行解释。
五、错误信息生成
下列形式的预处理器控制指令:
#error 记号序列 opt
将使预处理器打印包含该记号序列的诊断信息。
六、pragma
下列形式的控制指令:
#pragma 记号序列opt
将使预处理器执行一个与具体实现相关的操作。无法识别的pragma(编译指示)将被忽略掉。
七、空指令
下列形式的预处理器行不执行任何操作:
#
八、预定义名字
某些标识符是预定义的,扩展后将生成特定的信息。它们同预处理器表达式运算符defined一样,不能取消定义或重新进行定义。
__LINE__ 包含当前源文件行数的十进制常量。
__FILE__ 包含正在被编译的源文件名字的字符串字面值。
__DATE__ 包含编译日期的字符串字面值,其形式为“Mmm dd yyyy”。
__TIME__ 包含编译时间的字符串字面值,其形式为“hh:mm:ss”。
__STDC__ 整型常量1。只有在遵循标准的实现中该标识符才被定义为1。
说明:#error 与#pragma 是ANSI 标准中新引入的特征。这些预定叉的预处理器宏也是新引入的,其中的一些宏先前已经在某些编译器中实现。
九、行连接(在上文宏展开中提到)
通过将以反斜杠\结束的指令行末尾的反斜杠和其后的换行符删除掉。可以将若干指令行合并成一行。这种处理要在分隔记号之前进行。
十、三字符序列(很少使用吧)
C语育源程序的字符集是7 位ASCII码的子集,但它是ISO 646-1983不变代码集的超集。为了将程序通过这种缩减的字符集表示出来,下列所示的所有三字符序列都要用相应的单个
字符替换,这种替换在进行所有它他处理之前进行。
??= # ??( [ ??< {
??/ \ ??) ] ??> }
??' ^ ??! | ??- ~
除此之外不进行其它替换。
说明:三字符序列是ANSI标准新引入的特征。
以上内容除标注外大多摘抄自THE C PROGRAMMING LANGUAGE (2nd)中文版