C语言中的宏定义

所需积分/C币:20 2015-03-08 00:23:44 757KB PDF

C/C++语言中的宏定义介绍和总结,了解其用途
本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 虽然有些程序员会使用宏定义的方式来实现此目的,但类型定义(节)仍然是定义新类 型的最佳方法 、控制条件编译。如将在节中看到的那样,宏在控制条件编译中起重要的作用。例 如,在程序中出现的宏定义可能表明需要将程序在“调试模式”下进行编译,来使用额外的 句输出调试信息: #define debug 这里顺便提一下,如上面的例子所示,宏定义中的替换列表为空是合法的。 当宏作为常量使用时,程序员习惯在名字中只使用人写字母。但是并没有如何将用于其他 目的的宏大写的统一做法。由于宏(特别是带参数的宏)可能是程序中错误的来源,所以 些程序员更喜欢使用大写字母米引起注意。其他人则偭向于小写,即按照 和 编写的 书中的样式。 带参数的宏 带参数的宏定义有如下格式: 指令一带参数的宏 标识符( )替换列表 其中,,是标识符(宏的参数)。这些参数可以在替换列表中根据需要出现任意次 在宏的名字和左括号之间必须没有空格。如果有空格,预处理器会认为是在定义一个简单的 宏,其中( ,)是替换列表的一部分 当预处理器遇到一个带参数的宏,会将定义存储起米以便后面使用。在后面的程序中,如果 任何地方出现了标识符(,…,)格式的宏调用(其中,,是一系列标记),预 处理器会使用替换列表替代,并使用替换 替换,依此类推。 例如,假定我们定义了如下的宏: 123 #define MAX(x, y)((x)>(y)?(x): (y)) #define IS EVEN(n)((n)%2==0) 现在如果后面的程序中有如下语句: i =MAX(j+k 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处 本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 if (Is EVEN(i))i++ 预处理器会将这些行替换为 i=((j+k)>(m-n)?(j+k):(m-n)); if(i)‰2==8))i++; 如这个例子所显示的,带参数的宏经常用来作为一些简单的函数使用 类似个从两 个值中选取较大的值的函数 则类似于另一种函数,该函数当参数为偶数时返回 否则返回 下面的例子是一个史复杂的宏: #defineToUPPER(c)("a'<=(c)8&(c)<='z?(c)-a'+'A':(c)) 这个宏检测一个字符是否在与之间。如果在的话,这个宏会用减去再加上,来 计算出所对应的大写字母。如果不在这个范围,就保留原来的。像这样的字符处理的 宏非常有用,所以语言库在 (节)中提供了人量的类似的宏。其中之一就 是 与我们上面的 例子作用致(但会更高效,可移植性也更好)。 带参数的宏可以包含空的参数列表,如下例所示: #define getchar o getc(stdin) 空的参数列表不是一定确实需要,但可以使 更像一个函数。(没借,这就是 中的 的确就是个宏,不是函数一—虽然它的功能像个函数。 使用带参数的宏替代实际的函数的优点 、程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销—一存储上下文 信息、复制参数的值等。而一个宏的调用则没有这些运行开销。 、宏会更“通用”。与函数的参数不同,宏的参数没有类型。因此,只要预处理后的程序 依然是合法的,宏可以接受任何类型的参数。例如,我们可以使用宏从两个数中选出 较大的一个,数的类型可以是 等等 但是带参数的宏也有一些缺点。 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处 本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 、编详后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导敛程序 的源代码增加(因此编译后的代码变大)。宏使用得越频繁,这种效果就越明显。当宏调用 嵌套时,这个问题会相互叠加从而使程序更加复杂。思考一下,如果我们用宏来找出 个数中最大的数会怎样? n= MAX(i, MAX(,k)) 下面是预处理后的这条语句 n=((i)>((j)>(k)?(j):(k)))?(i):((j)>(k)?(j):(k)); 、宏参数没有类型检查。当个函数被调用时,编译器会检查每个参数来确认它们是 否是正确的类型。如果不是,或者将参数转换成正确的类型,戌者由编译器产生一个出错信 息。预处理器不会检查宏参数的类型,也不会进行类型转换。 、无法用个指针来指向个宏。如在节中将看到的,语言允许指针指向函数。 这一概念在特定的编程条件下非常有用。宏会在预处理过程中被删除,所以不存在类似的“指 向宏的指针”。因此,宏不能用于处理这些情况。 、宏可能会不止一次地计算它的参数。函数对它的参数会计算一次,而宏可能会计算 两次甚至更多次。如果参数有副作用,多次计算参数的值可能会产生意外的结果。考虑下面 的例子,其中的一个参数有副作用 n=MA以(i++,j); 下面是这条语句在预处理之后的结果 n=((i++)>(j)?(i++):(j)); 如果大于,那么可能会被(错淏地)増加了两次,同时可能被赋予了错误的值。 由于多次计算宏的参数而导致的错误可能非常难于发现,因为宏调用和函数调用看起来是 样的。更糟糕的是,这类宏可能在大多数情况下正常工作,仅在特定参数有副作用时失效。 为了自保护,最好避免使用带有副作用的参数 带参数的宏不仅适用于模拟函数调用。他们特别经常被作为模板,来处理我们经常要重复书 写的代码段。如果我们已绎写烦了语句 1 printf("%d"\n,×); 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处 本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 因为每次要显示一个整数都婓使用它。我们可以定义下面的宏,使显示整数变得简单些 #define PrInt INT(x) printf( %d\n", x) 且定义了 预处理器公将这行 PRINT INT(l/1; //转换为 ntf(" %d\n",1/j), 运算符 宏定义可以包含两个运算符:和编译器不会识别这两种运算符相反,它们会在 预处理时被执行 运算符将一个宏的参数转换为字符串字面量字符字面量( string literal)是指 双引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以 有很多个字符,简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引 号它仅允许出现在带参数的宏的替换列表中。(一些程序员将操作理解为“ stringization (字符串化)”;其他人则认为这实在是对英语的滥用。)用比较官方的话说就是将语言符 号转化为字符串。 运算符有大量的用途,这里只来讨论其中的一种。假设我们决定在调试过程中使用 宏作为一个捷的方法,来输出一个整型变量或表达式的值。运算符可以使 为每个输出的值添加标签。下面是改进后的 1 #define PRINT_ INT(x) printf(#x =%d\n",x) 之前的运算符通知预处理器根据 的参数创建个字符串字面量。因此,调用 123 PRINT INT(1/1), //会变为 printf( "i/""=%d\", 1/1); 在语言中相邻的字符串字面量会被合并,因此上边的语句等价于 printf( i/i=%d\n",1/3; 当程序执行时, 网数会同时显示表达式和它的值。例如,如果是,是的话, 输出为 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处 本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 例子 #define Str(x)# int main (int argc chark* argv) 4 printf("‰s\n",STR(It' s a long string));//输出It" s a long str return a 7 运算符 在语言的宏中,被称为连接符( ),它是一种预处理运算符,用 来把两个语言符号组合成单个语言符号。这里的语言符号不一定是宏的变量。并且 双井号不能作为第一个或最后一个元素存在 运算符可以将两个记号(例如标识符)“粘”在一起,成为一个记号。(无需惊讶,运算 符被称为“记号粘合”。)如果其中一个操作数是宏参数,“粘合”会在当形式参数被相应的实 阿参数替换后发生。考虑下面的宏: 如下例子当 破调用时(比如 ),预处理器首先使用自变量(这个例子 是)替换参数。接着,预处理器将和迕接成为一个记号()。下面的声明使用 创建了个标识符: #define MK ID(n)i##n 1234 int MK ID(1), MK ID(2), MK ID(3); //预处理后声明变为: int 11, 12. 13 运算符不属于预处理器经常使用的特性。实际上,想找到一些使用它的情况是比 较困难的。为了找到一个有实际意义的的应用,我们来重新思考前面提到过的宏 如我们所见,当 的参数有副作用时会无法正常工作。一种解决方法是用宏来写 个函数。遗憾的是,往往一个函数是不够的。我们可能需要一个实际参数是 值的函数,还需要参数为值的函数,等等。除了实际参数的类型和返回值 的类型之外,这些函数都一样。因此,这样定义每一个函数似乎是个很蠢的做法。 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处 本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 解决的办法是定义一个宏,并使它展开后成为函数的定义。宏会有唯一的参数 它表示形式参数和返回值的类型。这里还有个问题,如果我们是用宏来创建多个 函数,程序将无法编译。(语言不允许在同一文件中出现两个同名的函数。)为了解决这 个问题,我们是用运算符为每个版本的两数构造不同的名字。下面的例子:请注意 宏的定义中是如何将和相连来形成新函数名的。假如我们需要一个针对值 的函数 <pre name="code"class="cpp >#define GENERIC MAX (type) type type## max(type x, type y) 23456789 return x>y?x:y GENERIC_ MAX(Float) /预处理器会将这行展开为下面的代码 float float_ max(float x, float y return x>y?x:y: < /pre <prex</pre> 1 p></ 11 <pre></pre> 12 p></p> 13 <p align="left">TIPI: </p> 14 <p align="left ></p> <pre name="code"class="cpp">#define PHP_ FUNCTION ZEND FUNCTION 16 #define ZEND FUNCTION(name ZEND NAMED FUNCTION (ZEND FN(name)) 17 #define ZEND FN(name) zif ##name #define ZEND NAMED FUNCTION(name void name(INTERNAL FUNCTION PARAMETE RS) 19 #define internal function parameters int ht, zval return value, zval **retu rn value ptr,\ zval *this ptr, int return value used TSRMLS DC 22 PHP FUNCTION (count ); //预处理器处理以后, PHP FUCNTION( count);就展开为如下代码 25 void zif count (int ht, zval *return value, zval **return value ptr, zval *this_ptr, int return value _used TSRMLS_DC)</pre> 27 p></p> <p align="left"><span style="color: rgb(34, 34, 34); font-size: 14px; text-align :left; text-indent: 28px; background-color: rgb(239, 239, 239)"></ span></p> lign="left ><span style="color: rgb(34, 34, 34); font-family: Helvetica, Aria l, sans-serif; font-size: 14px; line-height: 24px; text-align: left; text-indent: 28px; background- color:rgb(239,239,239)">宏 ZEND FN(name)中有一个"#",它的作用一如之前所说, 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处 本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 是一个连接符,将zif和宏的变量name的值连接起来。以这种连接的方式以基础,多次使用这和宏形式, 可以将它当作一个代码牛成器,这样可以在一定程度上减少代码密度, 38 我们也可以将它理解为一种代码重用的手毁,间接地减少不小心所造成的错误。</span><br p 32 <h1>< a name="ts"></a>5宏的通用属性</h1> 33 < p align="1eft">现在我们已经讨论过简单的宏和带参数的宏了,我们来看一下它们都需要遵 守的规则。</p 34 <p align="left ><span style="color: rgb(51, 51, 255); background-color: rgb(192, 192,192)">1)、</span>宏的替换列表可以包含对另一个宏的调用。例如,我们可以用宏P来定义宏 TWO PI: </p> <p align="left></p> 36 <pre name="code" class="cpp>#definePI 3.14159 37 #define TWO PI (2*PI)</pre> <p></p> 39 p align="le仕t">当预处理器在后面的程序中遇到TMO_PI时,会将它替换成(2*P工)。接着 预处理器会重新检查替换列表,看它是否包含其他宏的调用〈在这个例子中,调用了宏PI)。预处理器会 不断車新检查替换列表,直到将所有的宏名字都替换掉为止。</p 49 <p align="left ><span style="color: rgb(51, 51, 255); font-family: monospace; wh ite- space:pre; background- colon:rgb(192,192,192)">2)、</span>预处理器只会替换完整的记 号,而不会替换记号的片断。因此,预处理器会忽眳嵌在标识符名、字符常量、字符串字面量之中的宏名。 例如,假设程序含有如下代码行:</p> 41 <p align="left"></p> 42 <pre name= "code"class="cpp">#define SIZE 256 int BUFFER S工zE if (BUFFER SIZE> SIZE 45 puts("Error: SIZEexceeded"); 4 //预处理后,这些代码行会变为: int BUFFER SIZE; if (BUFFER_SIZE> 256) 49 puts("Error SIZEexceeded ");</pre> 52 p></p> 51 <pa1ign="1eft">标识符 BUFFER_zISE和字符串" Error: SIZE exceeded"没有被预处理影响, 虽然它们都包含SIZE。</ 52 <p align="left">span style="color: rgb(51, 51, 255); background-color: rgb(192 192,192)">3)、</5pan>一个宏定义的作用范围通常到出现这个宏的文什未尾。由于宏是由< strong>预 处理器处理</ strong>的,他们个遵从通常的范围规则。一个定义在函数中的宏并不是仅在函数内起作用, 而是作用到文件末尾。</ 53 <p align="left ><span style="color: rgb(51, 51, 255); background-color: rgb(192, 192,192)">4)、</span>宏不可以被定义两遍,除非新的定义与旧的定义是一样的。小的间隔上的差异 是允许的,但是宏的替换列表(和参数,如果有的话)中的记号都必须一致。</p 54 <p align="left ><span style="color: rgb(51, 51, 255); background-color: rgb(192 192,192)">5)、</span>宏可以使用 undef指令“取消定义”。执 undef指令有如下形式:</p < p align="left">[#unde指令]# undef标识符</p> 56 <pa1ign="1eft">其中标识符是一个宏名。例如,指令</p〉 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处 本文由西安白癜风专科医院htp:/www.xapfb120com/收集,转载诘注明出处 <p align="left >#undef N</p> 58 < p align="1eft">会删除宏N当前的定义。(如果N没有被定义成一个宏,# undef指令没有任 何作用。)# undef指令的一个用途是取消一个宏的现有定义,以便于重新给出新的定义。</ <h1>< a name="t6"〉</a>6.宏定义屮的圆括号</h1 6 < p align="left">在我们前面定义的宏的替换列表中有大量的圆括号。确实需要它们吗?答案 是绝对需要。如果我们少用几个圆括号,宏可能有时会得到意料之外的一而且是不希望冇的一结果 61 < p align="left">对于在一个实定义中哪里要加圆括号有两条規则要遵守。首先,如果宏的替 换列表中有运算符,那么始终要将替换列表放在括号中:</ 62 <p align="left"></p> 6 <pre name="code" class="cpp >#define TWO PI(2*3. 14159)</pre> 64 <p></p> < p align="1eft">其次,如果宏有参数,每次参数在替换列表中H现时都要放在圆括号中 p> 66 <p align="left></p> 67 <pre name="code" class="cpp">#define SCALE(x)((x)*10)</pre> <p></p> 6 < p align="left">没有括号的话,我们将无法确保编译器会将替按列表和参数作为完整的表达 式。编译器可能会不按我们期望的方式应用运算符的优先级和结合性规则。</p 854÷列pa1gn=”1eft”为了展示为替换列表添加圆括号的重要性,考虑下面的宏定义,其中的替换 7 没有添加圆括号:</p> <p align="left ></p> <pre name="code" class="cpp>#define TWO PI 2*3. 14159</pre> <p></p> <pa1ign="1eft"〉/*需要给替换列表加圆括号*/</p> < p align="left">在预处理时,语句</p> pa1ign="1eft"×</p <pre name="code"class="cpp >conversion factor = 360/TWO PI; //变为 conversion factor =360/2*3.14159;</pre> p></p> φ p align="1e代t">除法会在乘法之前执行,产生的结果并不是期望的结果。</p < p align="left">当宏有参数时,仅给替换列表添加圆括号是不够的。参数的每一次出现都要 添加圆括号。例如,假设 SCALE定义如下:</p 83 pa1ign="1eft"># define scale(x)(x*1)/*需要给x添加括号*/</p> 84 < p align="left">在预处理过程中,语句</p 85 p align="left">j= SCALE(i+1);</p 86 < p align="1eft">变为</p> 87 <p align="left >j=(i+1*10);</p> <pa1ign="1eft">由于乘法的优先级比加法高,这条语句等价于</p> <p align=left>j=1+10;</p> 99 p align="1eft">当然,我们希望的是</p <pa1ign="1eft">j=(i+1)*1;</p <table border=0 cellspacing="0"cellpadding="0"> 9 <tbody> 本文由西安白癜风专科医院htt://www.xapfb120.com/收集,转载请注明出处

...展开详情
img
yin55663328

关注 私信 TA的资源

上传资源赚积分,得勋章
    最新推荐