学懂C++ (十八):高级教程——C++预处理器及宏定义深入详解

目录

1. 文件包含

2. 宏定义

2.1 简单宏定义

2.2 参数宏

2.3 宏定义注意点(重点)

3. 条件编译

4. # 和 ## 运算符

5. 预定义宏

6. 文件包含保护

总结


C++预处理器是编译器在实际编译之前所执行的一个步骤,它处理代码中的预处理指令,并生成一个已预处理的源文件供编译器使用。预处理器指令都是以井号(#)开头,这些指令不是C++语句,因此它们不以分号(;)结尾。以下是对C++预处理器及其指令的深入详解。

1. 文件包含

#include指令用于将另一个文件的内容插入到当前文件中。它有两种形式: 

示例

  • 尖括号 < >:用于包含标准库头文件。例如:

    #include <iostream>
    
  • 编译器会在标准库路径中查找这个头文件。

  • 双引号 " ":用于包含用户自定义头文件。例如:

    #include "myfile.h"
    

  • 编译器会首先在当前目录查找这个头文件,然后才会去标准库路径查找。
  • 2. 宏定义

    2.1 简单宏定义

    #define指令用于创建符号常量或简单的文本替换。例如:

    #define PI 3.14159
    
    int main() {
        std::cout << "Value of PI: " << PI << std::endl;
        return 0;
    }
    

    编译器会将所有的PI替换为3.14159

    2.2 参数宏

    可以定义带参数的宏,实现类似函数的功能。例如:

    #define MIN(a,b) ((a) < (b) ? (a) : (b))
    
    int main() {
        int i = 100, j = 30;
        std::cout << "The minimum is " << MIN(i, j) << std::endl;
        return 0;
    }
    

    编译器会将MIN(i, j)替换为((i) < (j) ? (i) : (j))

  • 最终输出的结果为:

    The minimum is 30
    

    2.3 宏定义注意点(重点)

  •  由于宏定义的本质就是简单的文本替换,所以#define定义表达式时一定要注意使用括号来避免运算优先级带来的潜在错误,以下举例说明。
  • 以 #define SQUARE(x) (x * x) 与 #define SQUARE(x) ((x) * (x))的区别进行讲解:

  • 定义1:#define SQUARE(x) (x * x)

    这个宏定义没有对参数和整个表达式使用足够的括号,这可能导致运算优先级的问题。例如:

    #define SQUARE(x) (x * x)
    
    int main() {
        int a = 5;
        int result = SQUARE(a + 1); // 实际结果是11,而不是预期的36
        std::cout << result << std::endl;
        return 0;
    }
    

    在预处理阶段,代码变为

    int result = (a + 1 * a + 1);
    

    由于运算优先级的关系,乘法*比加法+优先级高,实际执行的顺序是1 * a,然后a + (结果),最后加1,结果为:

    int result = (a + (1 * a) + 1); // 实际结果是11,而不是预期的36
    

    定义2:#define SQUARE(x) ((x) * (x))

    这个宏定义在参数和整个表达式上都使用了括号,避免了运算优先级的问题。例如:

    #define SQUARE(x) ((x) * (x))
    
    int main() {
        int a = 5;
        int result = SQUARE(a + 1); // 预期结果是36
        std::cout << result << std::endl;
        return 0;
    }
    

    在预处理阶段,代码变为:

    int result = ((a + 1) * (a + 1));
    

    由于括号强制了运算顺序,先计算a + 1,然后再平方,结果为36。

  • 总结

    使用括号来定义宏的参数和整个表达式,可以避免因运算优先级和参数中的副作用导致的意外结果。推荐的做法是总是使用足够的括号来包裹宏定义中的参数和表达式,以确保宏展开后符合预期逻辑。

  • 3. 条件编译

    条件编译指令用于有选择地编译代码。主要的条件编译指令包括:

    #ifdef / #ifndef / #endif

    #ifdef DEBUG
        std::cerr << "Debug mode is on" << std::endl;
    #endif
    

    如果在此之前定义了DEBUG,则编译#ifdef#endif之间的代码。

    #if / #elif / #else / #endif

    #define VALUE 10
    
    #if VALUE > 10
        std::cout << "Value is greater than 10" << std::endl;
    #elif VALUE == 10
        std::cout << "Value is 10" << std::endl;
    #else
        std::cout << "Value is less than 10" << std::endl;
    #endif
    

    #if 0 语句块

    可以用来注释掉代码块。

    #if 0
        // 这段代码不会被编译
        std::cout << "This is commented out" << std::endl;
    #endif
    

    4. # 和 ## 运算符

    # 运算符

    # 运算符用于将宏参数转换为字符串。

    #define MKSTR(x) #x
    
    int main() {
        std::cout << MKSTR(HELLO C++) << std::endl;
        return 0;
    }
    

    会被预处理为:

    std::cout << "HELLO C++" << std::endl;
    

    ## 运算符

    ## 运算符用于连接两个令牌。

    #define CONCAT(x, y) x ## y
    
    int main() {
        int xy = 100;
        std::cout << CONCAT(x, y) << std::endl;
        return 0;
    }
    

    会被预处理为:

    std::cout << xy << std::endl;
    

    5. 预定义宏

    C++提供了几个预定义宏,这些宏在编译时会被替换为特定的信息。

  • __LINE__:当前行号。
  • __FILE__:当前文件名。
  • __DATE__:文件编译的日期。
  • __TIME__:文件编译的时间。
    #include <iostream>
    
    int main() {
        std::cout << "Value of __LINE__: " << __LINE__ << std::endl;
        std::cout << "Value of __FILE__: " << __FILE__ << std::endl;
        std::cout << "Value of __DATE__: " << __DATE__ << std::endl;
        std::cout << "Value of __TIME__: " << __TIME__ << std::endl;
        return 0;
    }
    

    6. 文件包含保护

    为了防止头文件被多次包含,通常使用包含保护。

    1、传统方法

    #ifndef MYFILE_H
    #define MYFILE_H
    
    // 文件内容
    
    #endif // MYFILE_H
    

    2、#pragma once

    #pragma once
    
    // 文件内容
    

    #pragma once 是一种非标准但广泛支持的方法,防止文件多次包含。

    总结

    C++预处理器是一个强大的工具,允许在实际编译之前对代码进行处理。它提供了文件包含、宏定义、条件编译等功能,极大地增强了代码的灵活性和可维护性。然而,使用预处理器指令时也需注意其可能带来的可读性和调试问题。合理使用预处理器功能,可以使代码更加简洁、高效和易于维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿享天开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值