C语言中的宏定义

合集下载
相关主题
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1. #define getchar() getc(stdin)
空的参数列表不是一定确实需要, 但可以使 getchar 更像一个函数。 (没错, 这就是<stdio.h> 中的 getchar,getchar 的确就是个宏,不是函数 ——虽然它的功能像个函数。) 使用带参数的宏替代实际的函数的优点: 1) 、 程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销—— 存储上下文 信息、复制参数的值等。而一个宏的调用则没有这些运行开销。 2) 、 宏会更“通用”。与函数的参数不同,宏的参数没有类型。因此,只要预处理后的程序 依然是合法的,宏可以接受任何类型的参数。例如,我们可以使用 MAX 宏从两个数中选出 较大的一个,数的类型可以是 int,long int,float,double 等等。 但是带参数的宏也有一些缺点。
1. 2. 3. 4. 5. 6. 本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处 #defineFALSE 0 #defineTRUE 1 #define STE_LEN 80
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处 7. 8. 9. 10. 11. #defineEOS '\0' #defineCR '\r' #definePI 3.14159
1. #define DEBUG
这里顺便提一下,如上面的例子所示,宏定义中的替换列表为空是合法的。 当宏作为常量使用时,C 程序员习惯在名字中只使用大写字母。但是并没有如何将用于其他 目的的宏大写的统一做法。由于宏(特别是带参数的宏)可能是程序中错误的来源,所以一 些程序员更喜欢使用大写字母来引起注意。其他人则倾向于小写,即按照 Kernighan 和 Ritchie 编写的 The C Programming Language 一书中的样式。
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处 2. 3.
Fra Baidu bibliotek
if (IS_EVEN(i)) i++; 预处理器会将这些行替换为
1. 2.
i = ((j+k)>(m-n)?(j+k):(m-n));
if (((i)%2==0)) i++; 如这个例子所显示的,带参数的宏经常用来作为一些简单的函数使用。 MAX 类似一个从两 个值中选取较大的值的函数。 IS_EVEN 则类似于另一种函数,该函数当参数为偶数时返回 1,否则返回 0。 下面的例子是一个更复杂的宏:
使用#define 来为常量命名有许多显著的优点: 1) 、 程序会更易读。一个认真选择的名字可以帮助读者理解常量的意义。否则,程序将包 含大量的“魔法数”,使读者难以理解。 2) 、 程序会更易于修改。我们仅需要改变一个宏定义,就可以改变整个程序中出现的所有 该常量的值。 “硬编码的”常量会更难于修改, 特别是有时候当他们以稍微不同的形式出现时。 (例如,如果一个程序包含一个长度为 100 的数组,它可能会包含一个从 0 到 99 的循环。 如果我们只是试图找到所有程序中出现的 100,那么就会漏掉 99。) 3) 、 可以帮助避免前后不一致或键盘输入错误。 假如数值常量 3.14159 在程序中大量出现, 它可能会被意外地写成 3.1416 或 3.14195。 虽然简单的宏常用于定义常量名,但是它们还有其他应用。 4) 、可以对 C 语法做小的修改。实际上,我们可以通过定义宏的方式给 C 语言符号添加别 名,从而改变 C 语言的语法。例如,对于习惯使用 Pascal 的 begin 和 end(而不是 C 语言 的{和})的程序员,可以定义下面的宏:
1. #define BOOL int
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
虽然有些程序员会使用宏定义的方式来实现此目的,但类型定义(7.6 节)仍然是定义新类 型的最佳方法。 6) 、控制条件编译。如将在 14.4 节中看到的那样,宏在控制条件编译中起重要的作用。例 如,在程序中出现的宏定义可能表明需要将程序在“调试模式”下进行编译,来使用额外的语 句输出调试信息:
1. n = MAX(i++, j);
下面是这条语句在预处理之后的结果:
1. n =((i++)>(j)?(i++):(j));
如果 i 大于 j,那么 i 可能会被(错误地)增加了两次,同时 n 可能被赋予了错误的值。 由于多次计算宏的参数而导致的错误可能非常难于发现, 因为宏调用和函数调用看起来是一 样的。更糟糕的是,这类宏可能在大多数情况下正常工作,仅在特定参数有副作用时失效。 为了自保护,最好避免使用带有副作用的参数。 带参数的宏不仅适用于模拟函数调用。 他们特别经常被作为模板, 来处理我们经常要重复书 写的代码段。如果我们已经写烦了语句
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
1) 、 编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序 的源代码增加(因此编译后的代码变大)。宏使用得越频繁,这种效果就越明显。当宏调用 嵌套时,这个问题会相互叠加从而使程序更加复杂。思考一下,如果我们用 MAX 宏来找出 3 个数中最大的数会怎样?
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
C 语言中的宏定义
1. 简单的宏
简单的宏定义有如下格式: [#define 指令(简单的宏)] #define 标识符替换列表
替换列表是一系列的 C 语言记号,包括标识符、关键字、数、字符常量、字符串字面量、 运算符和标点符号。当预处理器遇到一个宏定义时,会做一个 “标识符”代表“替换列表”的记 录。在文件后面的内容中,不管标识符在任何位置出现,预处理器都会用替换列表代替它。 不要在宏定义中放置任何额外的符号, 否则它们会被作为替换列表的一部分。 一种常见的错 误是在宏定义中使用 = :
1. 2. 3. #define END } #define BEGIN {
我们甚至可以发明自己的语言。例如,我们可以创建一个 LOOP“语句”,来实现一个无限循 环: #define LOOP for (;;)
当然,改变 C 语言的语法通常不是个好主意,因为它会使程序很难被其他程序员所理解。 5) 、对类型重命名。在 5.2 节中,我们通过重命名 int 创建了一个 Boolean 类型:
这里 N 被定义为 100 和;两个记号。 在一个宏定义中,编译器可以检测到绝大多数由多余符号所导致的错误。但不幸的是,编译 器会将每一处使用这个宏的地方标为错误, 而不会直接找到错误的根源—— 宏定义本身, 因 为宏定义已经被预处理器删除了。 简单的宏主要用来定义那些被 Kernighan 和 Ritchie 称为“明示常量”(manifest constant) 的东西。使用宏,我们可以给数值、字符和字符串命名。
1. printf("%d"\n, x);
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
因为每次要显示一个整数 x 都要使用它。我们可以定义下面的宏,使显示整数变得简单些:
1. n = MAX(i, MAX(j,k));
下面是预处理后的这条语句:
1. n=((i)>(((j)>(k)?(j):(k)))?(i):(((j)>(k)?(j):(k))));
2) 、宏参数没有类型检查。当一个函数被调用时,编译器会检查每一个参数来确认它们是 否是正确的类型。如果不是,或者将参数转换成正确的类型,或者由编译器产生一个出错信 息。预处理器不会检查宏参数的类型,也不会进行类型转换。 3) 、无法用一个指针来指向一个宏。如在 17.7 节中将看到的,C 语言允许指针指向函数。 这一概念在特定的编程条件下非常有用。 宏会在预处理过程中被删除, 所以不存在类似的 “指 向宏的指针”。因此,宏不能用于处理这些情况。 4) 、宏可能会不止一次地计算它的参数。函数对它的参数只会计算一次,而宏可能会计算 两次甚至更多次。如果参数有副作用,多次计算参数的值可能会产生意外的结果。考虑下面 的例子,其中 MAX 的一个参数有副作用:
1. 2. 3. #define IS_EVEN(n) ((n)%2==0) #define MAX(x,y) ((x)>(y) ? (x) :(y))
现在如果后面的程序中有如下语句:
1. i = MAX(j+k, m-n); 本文由西安白癜风专科医院 http://www.xapfb120.com/ 收集,转载请注明出处
2. 带参数的宏
带参数的宏定义有如下格式: [#define 指令 —带参数的宏] #define 标识符(x1, x2,…,xn)替换列表
其中 x1, x2,…,xn 是标识符(宏的参数)。这些参数可以在替换列表中根据需要出现任意次。 在宏的名字和左括号之间必须没有空格。 如果有空格, 预处理器会认为是在定义一个简单的 宏,其中(x1,x2,…,xn)是替换列表的一部分。 当预处理器遇到一个带参数的宏,会将定义存储起来以便后面使用。在后面的程序中,如果 任何地方出现了标识符(y1,y2,…,yn)格式的宏调用(其中 y1,y2,…,yn 是一系列标记),预 处理器会使用替换列表替代,并使用 y1 替换 x1,y2 替换 x2,依此类推。 例如,假定我们定义了如下的宏:
1. 2. #define N = 100 int a[N]; /*** WRONG ***/ /* 会成为 int a[= 100]; */
在上面的例子中,我们(错误地)把 N 定义成一对记号(= 和 100)。 在宏定义的末尾使用分号结尾是另一个常见错误:
1. 2. #define N 100; int a[N]; /*** WRONG ***/ /* become int a[100;]; */
1. #define PRINT_INT(x) printf("%d\n", x)
一旦定义了 PRINT_INT ,预处理器会将这行
1. 2. 3. PRINT_INT(i/j); //转换为 printf("%d\n", i/j);
3 #运算符
宏定义可以包含两个运算符:# 和##。编译器不会识别这两种运算符相反,它们会在 预处理时被执行。 #运算符将一个宏的参数转换为字符串字面量(字符串字面量(string literal)是指
1.
#define TOUPPER(c)('a'<=(c)&&(c)<='z'?(c)-'a'+'A':(c))
这个宏检测一个字符 c 是否在'a'与'z'之间。如果在的话,这个宏会用'c'减去'a'再加上'A',来 计算出 c 所对应的大写字母。如果 c 不在这个范围,就保留原来的 c。像这样的字符处理的 宏非常有用,所以 C 语言库在<ctype.h>(23.4 节)中提供了大量的类似的宏。其中之一就 是 toupper,与我们上面的 TOUPPER 例子作用一致(但会更高效,可移植性也更好)。 带参数的宏可以包含空的参数列表,如下例所示:
双引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以 有很多个字符),, 简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引
号. 它仅允许出现在带参数的宏的替换列表中。 (一些 C 程序员将#操作理解为“stringization (字符串化)”;其他人则认为这实在是对英语的滥用。)用比较官方的话说就是将语言符 号(Token)转化为字符串。 #运算符有大量的用途,这里只来讨论其中的一种。假设我们决定在调试过程中使用 PRINT_INT 宏作为一个便捷的方法,来输出一个整型变量或表达式的值。 #运算符可以使 PRINT_INT 为每个输出的值添加标签。下面是改进后的 PRINT_INT :
相关文档
最新文档