# 宏定义 \#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。 我们先通过一个例子来看一下 #define 的用法: ```c #include #define N 100 int main(){ int sum = 20 + N; printf("%d\n", sum); return 0; } /* 运行结果 120 */ ``` 注意第 6 行代码`int sum = 20 + N`,`N`被`100`代替了。 `#define N 100`就是宏定义,`N`为宏名,`100`是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。宏定义是由源程序中的宏定义命令`#define`完成的,宏替换是由预处理程序完成的。 程序中反复使用的表达式就可以使用宏定义。 **对 #define 用法的几点说明** 1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。 2) 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。 3) 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用`#undef`命令。 4) 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替。 5) 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。 ```c #define PI 3.1415926 #define S PI*y*y ``` 6. 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。 7. 可用宏定义表示数据类型,使书写方便。 ```c #define UINT unsigned int ``` 应注意用宏定义表示数据类型和用 `typedef` 定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。 ## 带参数的宏定义 C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。 **带参数的宏定义格式** ```c #define 宏名(形参列表) 字符串 ``` 在字符串中可以含有各个形参。 **带参宏调用格式** ```c 宏名(实参列表) ``` 实例1 ```c #define M(y) y*y+3*y //宏定义 k = M(5); //宏调用 ``` 实例2 ```c #include #define MAX(a, b) (a>b)?a:b int main(){ int x, y, max; printf("Input tow numbers: "); sacnd("%d %d", &x, &y); max = MAX(x, y); printf("max = %d\n", max); return 0; } ``` **对带参宏定义的说明** 1) 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。例如把: ``` #define MAX(a,b) (a>b)?a:b ``` 写为: ``` #define MAX (a,b) (a>b)?a:b ``` 将被认为是无参宏定义,宏名 MAX 代表字符串`(a,b) (a>b)?a:b`。宏展开时,宏调用语句: ``` max = MAX(x,y); ``` 将变为: ``` max = (a,b)(a>b)?a:b(x,y); ``` 这显然是错误的。 2) 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。 这一点和函数是不同的:在函数中,形参和实参是两个不同的变量,都有自己的作用域,调用时要把实参的值传递给形参;而在带参数的宏中,只是符号的替换,不存在值传递的问题。 【示例】输入 n,输出 (n+1)^2 的值。 ``` #include #define SQ(y) (y)*(y) int main(){ int a, sq; printf("input a number: "); scanf("%d", &a); sq = SQ(a+1); printf("sq=%d\n", sq); return 0; } ``` 运行结果: input a number: 9 sq=100 第 2 行为宏定义,形参为 y。第 7 行宏调用中实参为 a+1,是一个表达式,在宏展开时,用 a+1 代换 y,再用 (y)*(y) 代换 SQ,得到如下语句: ``` sq=(a+1)*(a+1); ``` 这与函数的调用是不同的,函数调用时要把实参表达式的值求出来再传递给形参,而宏展开中对实参表达式不作计算,直接按照原样替换。 3) 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。例如上面的宏定义中 (y)*(y) 表达式的 y 都用括号括起来,因此结果是正确的。如果去掉括号,把程序改为以下形式: ``` #include #define SQ(y) y*y int main(){ int a, sq; printf("input a number: "); scanf("%d", &a); sq = SQ(a+1); printf("sq=%d\n", sq); return 0; } ``` 运行结果为: input a number: 9 sq=19 同样输入 9,但结果却是不一样的。问题在哪里呢?这是由于宏展开只是简单的符号替换的过程,没有任何其它的处理。宏替换后将得到以下语句: ``` sq=a+1*a+1; ``` 由于 a 为 9,故 sq 的值为 19。这显然与题意相违,因此参数两边的括号是不能少的。即使在参数两边加括号还是不够的,请看下面程序: ``` #include #define SQ(y) (y)*(y) int main(){ int a,sq; printf("input a number: "); scanf("%d", &a); sq = 200 / SQ(a+1); printf("sq=%d\n", sq); return 0; } ``` 与前面的代码相比,只是把宏调用语句改为: ``` sq = 200/SQ(a+1); ``` 运行程序后,如果仍然输入 9,那么我们希望的结果为 2。但实际情况并非如此: input a number: 9 sq=200 为什么会得这样的结果呢?分析宏调用语句,在宏展开之后变为: ``` sq=200/(a+1)*(a+1); ``` a 为 9 时,由于“/”和“*”运算符优先级和结合性相同,所以先计算 200/(9+1),结果为 20,再计算 20*(9+1),最后得到 200。 为了得到正确答案,应该在宏定义中的整个字符串外加括号: ``` #include #define SQ(y) ((y)*(y)) int main(){ int a,sq; printf("input a number: "); scanf("%d", &a); sq = 200 / SQ(a+1); printf("sq=%d\n", sq); return 0; } ``` 由此可见,对于带参宏定义不仅要在参数两侧加括号,还应该在整个字符串外加括号。 带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。 ## 宏参数的字符串化和宏参数的连接 在宏定义中,有时还会用到`#`和`##`两个符号,它们能够对宏参数进行操作。 ### #用法 `#`用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。 ```c #define STR(s) #s ``` 如果程序中有 ```c printf("%s", STR(wangyuedong)); printf("%s", STR("wang")); ``` 那么在宏替代后的源码为 ```c printf("%s", "wangyuedong"); printf("%s", "\"wang\""); ``` 可以发现,即使给宏参数“传递”的数据中包含引号,使用`#`仍然会在两头添加新的引号,而原来的引号会被转义。 ```c #include #include #define STR(s) #s int main(int argc, char *argv[]){ printf("%s\n", STR(wangyuedong)); printf("%s\n", STR("wang")); system("pause"); return 0; } ``` ### ##用法 `##`称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义: ```c #define CON1(a, b) a##e##b #define CON2(a, b) a##b##00 ``` 如果程序中有 ```c printf("%f\n", CON1(8.5, 2)); printf("%d\n", CON2(12, 34)); ``` 那么在宏替代后的展开源码为 ```c printf("%f\n", 8.5e2); printf("%d\n", 123400); ``` ```c #include #include #define CON1(a, b) a##e##b #define CON2(a, b) a##b##00 int main() { printf("%f\n", CON1(8.5, 2)); printf("%d\n", CON2(12, 34)); system("pause"); return 0; } ```