宏与C预处理器
出处:按学科分类—工业技术 企业管理出版社《工程师手册》第857页(2965字)
C预处理器是C语言最有用的特征之一。虽然大多数高级语言允许程序中定义的编译器常量和条件编译,但一般不允许定义宏。或许这就是C语言有时被称为可移植的宏汇编语言的原因。利用C预处理器丰富的指令集可以彻底改变C程序的面貌,可以使复杂的程序变得易读,高效。希望本章的其余部分和本书中的程序能够有效地说明C预处理器的最新特点。
C预处理器允许程序段的条件编译,用户定义的符号替换以及用户定义的多参数宏。所有这些预处理器指令都在C代码编译之前处理,然后将之删除。每条预处理器指令都以符号#开始,其后为预处理器关键字,下表列出了最常用的预处理器指令及其基本用途:
#define NAME macro 将符号NAME定义为macro(参数可选)
#include“file” 将文件“file”拷贝到当前编译中
#include<file〉 包含标准C库中的文件file
#if expression 如expression为true,则编译后面的代码
#ifdef symbol 如symbol有定义,则编译后面的代码
#ifndef symbol 如symbol未定义,则编译后面的代码
#else 如对应的#if为false,则编译后面的代码
#endif 指出前面#else,#if,#ifdef或#ifndef的结束
#undef macro 取消前面对macro的定义
1.条件编译指令
上表列举的大部分预处理器指令用来对程序段进行条件编译。例如,在函数stats(3.3.4节已介绍过)的下述版本中,DEBUG的定义用来指出printf语句应该编译:
如果预处理器参数DEBUG在#ifdefDEBUG语句之前已定义,则两个printf语句应编译为程序的一部分,以有助于对函数stats(甚至调用程序)的调试。
2.别名与宏
在所有预处理器指令中,#define指令最为有用,因为它允许以相对简单的方式定义别名和带参数的宏。#define最通常的用途是定义不带参数的宏,即用一个串(宏的定义)代替另一个串(宏的名称)。这样,所有字符串,包括C关键字,都可以定义别名。例如,宏定义
#define DO for(
将用四个字符的串for(取代串DO(两个字母均为大写,以与C的关键字do相区别)的每次出现。类似地,可以用多个#define语句为C所有的关键字定义新的别名,(当然一般情况下这是不必要的)。即使单个字符也可以有别名,如可用DEGIN作{的别名,用END作}的别名,这样可使C程序看起来象Pascal程序。
当利用参数定义真正的宏时,#define指令将更有用。上述DO的宏可以扩展定义为一个简单的FORTRAN格式的do循环:
#define DO(var,bdg,end)for(var=beg;var<=end;var++)
三个宏参数var,beg和end分别是DO循环的循环变量,初始值和结束值。在任何情况下。宏DO(var,beg,end)都将按定义被扩展。例如,
DO(i,1,10)
扩展为
for(i=1;i<=10;i++)
这正是一个for语句的开始部分,循环将从变量i=1开始,循环10次直到i=10。虽然宏DO确能缩短这样一个简单for循环和书写长度,但使用时必须小心。当宏与其它的宏,操作符或函数一起使用时,可能会起意想不到的程序错误。例如,在上述宏中,如果参数var为一指针,则DO(*ptr,1,10)完全不能正确进行,因为它增加的是指针本身的值,而不是指针所指内容。这可能导致很奇怪的循环次数(如果循环还能结束的话)。再举一个例子,考察下述求变量立方的宏CUBE:
#define CUBE(x)(x)*(x)*(x)
这个宏对CUBE(i+j)将工作得很好(虽然不太有效),因为CUBE(i+j)将扩展为(i+j)*(i+j)*(i+j).但是,CUBE(i+j)扩展为(i+j)*(i+j)*(i+j),因此,i被增量三次,而不是一次,其结果值为x*(x+1)*(x+2).而不是x的立方。
将三元条件操作符(参看3.3.2节)用在宏定义中,可以快速实现以下函数:变量的绝对值(ABS),两个交易的最小值(MIN)和最大值(MAX),对浮点变量四舍五入取整(ROUND)。它们的宏定义为:
#define ABS(a) ((((a)<0)?(-a):(a))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define MIN(a,b) (((a)<(b))?(a):(b))
#define ROUND(a) (((a)<0)?(int)((a)-0.5):(int)((a)+0.5))
注意上述宏中每个参数都括在括号内以便在表达式中自由使用时对操作符的顺序没有不确定性,因为参量自身可能含有操作符。
到目前为止我们所定义的宏,其名字都只含有大写字母。这并不是C语言的要求,但这样易于区别宏与C语言的关键字,(宏可以定义在一个模块中,其它模块只须用#include指令包含它)。对宏名用大写字母,变量和函数名用小写字母,这一习惯将在本书及附带盘的所有程序中沿用。