设为首页 加入收藏

TOP

C语言预处理命令详解(一)
2015-01-22 21:13:20 来源: 作者: 【 】 浏览:150
Tags:语言 处理 命令 详解
一 ?前言
? ? ?预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。
?
? ? ?预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
?
? ? ?C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
?
? ? ?本文参考诸多资料,详细介绍常用的几种预处理功能。因成文较早,资料来源大多已不可考,敬请谅解。
?
?
?
?
?
二 ?宏定义
? ? ?C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为宏的标识符称为“宏名”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为宏替换或宏展开。
?
? ? ?宏定义是由源程序中的宏定义命令完成的。宏替换是由预处理程序自动完成的。
?
? ? ?在C语言中,宏定义分为有参数和无参数两种。下面分别讨论这两种宏的定义和调用。
?
2.1 无参宏定义
? ? ?无参宏的宏名后不带参数。其定义的一般形式为:
?
? ? ? ? #define ?标识符 ?字符串
?
? ? ?其中,“#”表示这是一条预处理命令(以#开头的均为预处理命令)。“define”为宏定义命令。“标识符”为符号常量,即宏名。“字符串”可以是常数、表达式、格式串等。
?
? ? ?宏定义用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的文本替换,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。
?
? ? ?注意理解宏替换中“换”的概念,即在对相关命令或语句的含义和功能作具体分析之前就要进行文本替换。
?
? ?【例1】定义常量:
?
1 #define MAX_TIME 1000
? ? ?若在程序里面写if(time < MAX_TIME){.........},则编译器在处理该代码前会将MAX_TIME替换为1000。
?
? ? ?注意,这种情况下使用const定义常量可能更好,如const int MAX_TIME = 1000;。因为const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行简单的字符文本替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。
?
? ? 【例2】反例:
?
1 #define pint (int*)
2 pint pa, pb;
? ? ?本意是定义pa和pb均为int型指针,但实际上变成int* pa,pb;。pa是int型指针,而pb是int型变量。本例中可用typedef来代替define,这样pa和pb就都是int型指针了。因为宏定义只是简单的字符串代换,在预处理阶段完成,而typedef是在编译时处理的,它不是作简单的代换,而是对类型说明符重新命名,被命名的标识符具有类型定义说明的功能。typedef的具体说明见附录6.4。
?
? ? ?无参宏注意事项:
?
宏名一般用大写字母表示,以便于与变量区别。
宏定义末尾不必加分号,否则连分号一并替换。
宏定义可以嵌套。
可用#undef命令终止宏定义的作用域。
使用宏可提高程序通用性和易读性,减少不一致性,减少输入错误和便于修改。如数组大小常用宏定义。
预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。
字符串" "中永远不包含宏,否则该宏名当字符串处理。
宏定义不分配内存,变量定义分配内存。
2.2 带参宏定义
? ? ?C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。
?
? ? ?对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。
?
? ? ?带参宏定义的一般形式为:
?
? ? ? ?#define ?宏名(形参表) ?字符串
?
? ? ?在字符串中含有各个形参。
?
? ? ?带参宏调用的一般形式为:
?
宏名(实参表);
?
? ? ?在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
?
? ? ?在带参宏定义中,形参不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值,要用它们去代换形参,因此必须作类型说明,这点与函数不同。函数中形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中只是符号代换,不存在值传递问题。
?
? ? 【例3】
?
1 #define INC(x) x+1 ?//宏定义
2 y = INC(5); ? ? ? ? //宏调用
? ? ?在宏调用时,用实参5去代替形参x,经预处理宏展开后的语句为y=5+1。
?
? ? 【例4】反例:
?
1 #define SQ(r) r*r
? ? ?上述这种实参为表达式的宏定义,在一般使用时没有问题;但遇到如area=SQ(a+b);时就会出现问题,宏展开后变为area=a+b*a+b;,显然违背本意。
?
? ? ?相比之下,函数调用时会先把实参表达式的值(a+b)求出来再赋予形参r;而宏替换对实参表达式不作计算直接地照原样代换。因此在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
?
? ? ?进一步地,考虑到运算符优先级和结合性,遇到area=10/SQ(a+b);时即使形参加括号仍会出错。因此,还应在宏定义中的整个字符串外加括号,
?
? ? ?综上,正确的宏定义是#define SQ(r) ((r)*(r)),即宏定义时建议所有的层次都要加括号。
?
? ? 【例5】带参函数和带参宏的区别:
?
复制代码
?1 #define SQUARE(x) ((x)*(x))
?2 int Square(int x){
?3 ? ? return (x * x); //未考虑溢出保护
?4 }
?5?
?6 int main(void){
?7 ? ? int i = 1;
?8 ? ? while(i <= 5)
?9 ? ? ? ? printf("i = %d, Square = %d\n", i, Square(i++));
10?
11 ? ? int j = 1;
12 ? ? while(j <= 5)
13 ? ? ? ? printf("j = %d, SQUARE = %d\n", j, SQUARE(j++));
14 ? ??
15 ? ? return 0;
16 }
复制代码
? ? ?执行后输出如下:
?
复制代码
1 i = 2, Square = 1
2 i = 3, Square = 4
3 i = 4, Square = 9
4 i = 5, Square = 16
5 i = 6, Square = 25
6 j = 3, SQUARE = 1
7 j = 5, SQUARE = 9
8 j = 7, SQUARE
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 1/7/7
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Objective-C中常用的结构体NSRang.. 下一篇编程算法 - 和为s的连续正整数序..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: