首页 > 编程笔记
C++ #define用法详解
C++ 提供了很多预处理指令,本节要讲的 #define 就是其中之一,它的功能是定义宏。
所谓宏,是程序中定义的用于替换复杂文本的简短文本。在程序的预编译期,预处理器会解析源代码文本,执行一个替换源程序的动作,把宏引用的地方替换成定义处的文本。这个动作叫做宏的展开。
下面是 #define 定义宏的一般格式:
预处理器会将 #define 之后的第一个和第二个空格之间的文本作为宏名,其后所有的文本作为“可替换文本”,而不管中间有多少个空格。例如:
举个简单的例子:
因此,程序的执行结果为:
注意,如果要替换的文本一行写不完,可以分成多行,在每一行的结尾处加上续行符号“\”(除了最后一行)即可,例如:
在 C++ 标准中,是可以利用预处理命令 #define 定义带参数的宏的,语法格式如下:
可替换的文本中可以引用括号中的参数,传递不同的参数,宏展开后得到的文本也不一样。例如,定义一个比较两数大小的宏。
分析下面的程序:
程序的执行结果为:
上面例子中,如果想让结果输出 35 也很简单,只要给宏加上括号即可:
观察下面的程序:
所谓宏,是程序中定义的用于替换复杂文本的简短文本。在程序的预编译期,预处理器会解析源代码文本,执行一个替换源程序的动作,把宏引用的地方替换成定义处的文本。这个动作叫做宏的展开。
下面是 #define 定义宏的一般格式:
#define 宏名 要替换的文本宏名,即宏的名称,在源代码中替换其后的文本;可替换的文本,也就是宏所指代的文本内容。#define、宏名和可替换文本之间用空格(或制表符)分隔。
预处理器会将 #define 之后的第一个和第二个空格之间的文本作为宏名,其后所有的文本作为“可替换文本”,而不管中间有多少个空格。例如:
#define H 1 + 2 + 3 + 4其中,H 是宏名,后面是
1 + 2 + 3 + 4
是 H 要替换的文本内容。举个简单的例子:
#include <iostream> #define AGE 30 #define NAME "John" int main() { const char* name = NAME; int age = AGE; std::cout << name << " " << age << std::endl; return 0; }程序在编译之前,预处理器会将程序中所有的 NAME 替换为 "John",将程序中所有的 AGE 替换成 30。
因此,程序的执行结果为:
John 30
注意,如果要替换的文本一行写不完,可以分成多行,在每一行的结尾处加上续行符号“\”(除了最后一行)即可,例如:
#include <iostream> #define MESSAGE \ std::cout << "C语言中文网"<< std::endl; \ std::cout << "url:http://c.biancheng.net" << std::endl; int main() { MESSAGE return 0; }执行结果为:
C语言中文网
url:http://c.biancheng.net
带参数的宏
前面给大家讲解的是简单的宏定义,只能进行简单的文字替换,扩展能力有限。如果宏能够像函数那样带有参数,并根据参数的不同自动展开成不同的文本,则宏的扩展能力将大大提高。在 C++ 标准中,是可以利用预处理命令 #define 定义带参数的宏的,语法格式如下:
#define 宏名(参数1, 参数2, ..., 参数 n) 要替换的文本注意,定义带参数的宏,宏名和左括号之间不能有空格。
可替换的文本中可以引用括号中的参数,传递不同的参数,宏展开后得到的文本也不一样。例如,定义一个比较两数大小的宏。
#include <iostream> #define LESS(a,b) (a>b?b:a) int main() { std::cout << LESS(3, 5) << std::endl; // 输出 3 和 5 中的较小值 std::cout << LESS(10, 8) << std::endl; // 输出 10 和 8 中的较小值 return 0; }执行结果为:
3
8
a>b?b:a
必须括起来,否则预处理器将程序中的宏展开后,程序中的输出语句就变成了:std::cout << 3>5?5:3 << std::endl; std::cout << 10>8?8:10 << std::endl;代码中 <<、>、?、: 各种符号杂糅在一起,编译器无法正确识别条件运算符,导致程序编译失败。
带参数的宏和函数的区别
带参数的宏在定义和使用方面和函数非常相似,都可以接受参数,并且可以根据不同的参数产生不同的结果。但是这两者毕竟是不同的东西,存在很大的差异。包括:- 程序运行时,函数依然存在,但宏只存在于预编译期,程序运行时就不存在了;
- 无论调用多少次,函数的代码只占用一份内存空间,而宏会重复占用多份内存;
- 函数调用时有时间和空间方面的额外开销,但宏没有;
- 函数接受参数的传递,但宏只是对参数的简单替换;
- 函数的参数有类型信息,但宏的参数没有类型信息;
- 函数可以调试,宏不能调试。
使用宏的注意事项
宏的行为有时候和所认知的行为会有些不同,主要原因是通常认为宏作为语句块,会优先执行,但这是不对的,宏并没有那么“聪明”,或者可以说预处理器没有那么“聪明”,它所做的只是傻瓜式地将宏展开。分析下面的程序:
#include <iostream> #define ADD(x,y) x+y int main() { std::cout << 5* ADD(3,4) << std::endl; return 0; }如果凭借第一印象,很可能会做这样的计算:5×(3+4)=35。但是,预处理器并不这么认为,而会做这样的解释:5×3+4=19。
程序的执行结果为:
19
所以读者写宏的时候一定要小心,预防宏的行为不符合预期。上面例子中,如果想让结果输出 35 也很简单,只要给宏加上括号即可:
#include <iostream> #define ADD(x,y) (x+y) int main() { std::cout << 5* ADD(3,4) << std::endl; return 0; }
特殊的宏符号
在宏的替换文本中,可以使用一些特殊的符号,起到特定的解释作用,常见的符号有:- ##:用于连接两个标记。
- #:用于字符串化一个宏参数。
观察下面的程序:
#include <iostream> #define CONCATENATE(a, b) a ## b #define TO_STRING(x) #x #define PRINT_EXPR(expr) std::cout << #expr << " = " << expr << std::endl int main() { // 使用##操作符将两个整数连接在一起 int xy = CONCATENATE(1, 2); // 实际上就是int xy = 12; std::cout << "xy = " << xy << std::endl; // 使用#操作符将标记转换为字符串 const char* str = TO_STRING(Hello World); // 实际上就是const char* str = "Hello World"; std::cout << "str = " << str << std::endl; // 综合使用 int a = 5, b = 10; PRINT_EXPR(a + b); // 输出 "a + b = 15" return 0; }执行结果为:
xy = 12
str = Hello World
a + b = 15