关键词

C++ #define用法详解

C++ 提供了很多预处理指令,本节要讲的 #define 就是其中之一,它的功能是定义宏。

所谓宏,是程序中定义的用于替换复杂文本的简短文本。在程序的预编译期,预处理器会解析源代码文本,执行一个替换源程序的动作,把宏引用的地方替换成定义处的文本。这个动作叫做宏的展开。

下面是 #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://task.lmcjl.com" << std::endl;

int main() {
    MESSAGE
    return 0;
}
执行结果为:

C语言中文网
url:http://task.lmcjl.com

带参数的宏

前面给大家讲解的是简单的宏定义,只能进行简单的文字替换,扩展能力有限。如果宏能够像函数那样带有参数,并根据参数的不同自动展开成不同的文本,则宏的扩展能力将大大提高。

在 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

注意,程序中 LESS(a,b) 要替换的文本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

本文链接:http://task.lmcjl.com/news/17081.html

展开阅读全文