C++中 宏 的学习

一、宏的初识

        宏在代码中是为了确保整个头文件的内容在单个编译单元中只被包含和编译一次。即使在项目中的其他代码文件中没有直接使用 __WORK__ 这个宏,它在头文件中定义和使用也是有意义的,因为它提供了包含保护。

        如果你在项目中的代码文件里没有找到关于 __WORK__ 的使用,这很正常,因为包含保护宏的使用通常是隐式的,不需要在源代码文件中显式引用。只要头文件被正确包含,这个宏会起作用,防止头文件内容被重复包含。

#ifndef __WORK__
#define __WORK__

// 环长度
#define RING_BUFFER_SIZE        (8192)

#if (RING_BUFFER_SIZE & (RING_BUFFER_SIZE - 1)) != 0
#error "RING_BUFFER_SIZE must be a power of two"
#endif

判断是否为2的幂的技巧:与操作& ,( num & num-1 ) != 0,true则是中断编译,输出错误提示"RING_BUFFER_SIZE must be a power of two" ,要求num为2的幂

二、作用域

  1. 文件作用域: 如果宏在文件的全局范围内定义,那么它在整个文件中都可见,直到遇到#undef指令取消宏定义。

  2. 条件编译作用域: 如果宏是在条件编译块中定义的,例如#ifdef#ifndef#if#else#elif#endif,宏的作用域将受限于这些条件编译块的逻辑。

  3. 命令行定义: 在编译时通过编译器命令行使用-D选项定义的宏,将在整个编译单元中有效。

  4. 包含保护: 使用包含保护的头文件中的宏定义,其作用域限于该头文件

  5. 局部宏定义: 在函数或代码块内部定义的宏,其作用域限于该代码块或函数内部。

  6. 作用域嵌套: 宏定义可以嵌套,但内部定义的宏在其外部是不可见的。

  7. 作用域链: 如果在不同的作用域中多次定义同一个宏,预处理器将使用最内层的定义。

  8. 跨文件宏: 若宏在多个文件中定义,且#undef取消,那宏最新定义将覆盖之前同名定义

  9. 编译单元: 一个编译单元(一个源文件及其包含的头文件)的宏定义对整个编译单元有效。

三、包含保护与pragma once比较

   #ifndef __WORK__ 和#pragma once 都是用来防止头文件被多次包含的机制,但二者有差异:

传统 C/C++ 包含保护方法:

  1. 检查宏#ifndef __WORK__ 检查是否未定义。若已定义,编译器将跳过到 #endif 代码块。

  2. 定义宏:如果 __WORK__ 未定义,#define __WORK__ 将定义这个宏。

  3. 结束保护#endif 标记了包含保护的结束。

  4. 可移植:几乎所有的 C/C++ 编译器都支持这种方法。

  5. 兼容:这种方法与 C89 和 C++98 标准兼容。

  6. 宏名称:通常使用独特的标识符来避免与其他宏冲突。

                                        #ifndef       _WORK_        如果没被定义
                                        #define      _WORK_        宏定义
                                        #undef       _WORK_        取消宏定义
                                        #ifdef         _WORK_        
                                      #if defined   _WORK_        如果已被定义,则。。。
                                        #else         
                                     #elif defined _WORK_        可与if配套使用,defined可换
                                        #error        “错误提示”       中断编译,给出错误提示信息

#pragma once:

  1. 一次性包含#pragma once 指示编译器只包含一次头文件,无论是否已定义了包含保护宏。

  2. 简洁性#pragma once 更简洁,不需要编写宏定义和结束标记。

  3. 标准性#pragma once 是 C++11 标准的一部分,但可能在一些旧的编译器中不被支持

  4. 可移植性:虽然 #pragma once 在现代编译器中广泛支持,但在一些非常旧的编译器或非标准的编译环境中可能不可用。

  5. 性能#pragma once 可能比传统的包含保护更快,因为它避免了宏定义和检查的开销。

比较

  • 使用场景#pragma once现代且简洁的防止头文件多次包含的方法;传统包含保护编译器兼容性更好。
  • 性能#pragma once 可能在某些编译器中提供更好的性能,因为它避免了宏处理的开销。
  • 可读性传统的包含保护提供了更好的可读性,因为它明确地显示了宏的名称和作用域。

四、预定义的宏

  1. __LINE__:获取当前代码实际行号。在调试和生成错误消息时非常有用。

  2. __FILE__:获取当前实际文件名。常用于记录日志和错误报告。

  3. __DATE____TIME__:分别用于获取编译日期和时间。
    格式通常为 "Mmm DD YYYY" 和 "HH:MM:SS"

  4. __FUNCTION__(C99及以上标准):获取当前函数或lambda表达式的名字。
    在日志记录和断言检查中很有用。

  5. __COUNTER__(GCC和Clang扩展):生成一个唯一的静态计数器值,通常用于创建多个匿名变量或在模板元编程中。

  6. __STDC__:如果编译器遵循ANSI C标准,则定义此宏。其值通常为 1

  7. __STDC_VERSION__:若编译器遵循特定C标准版本,此宏将被定义,并包含相应标准版本号

五、案例记录分析       

         用于在程序中生成带有文件名和日志级别的信息输出,方便调试和记录程序运行状态。

#define  ___LOG___(fmt,level,path, ...) do{\
    char cache[24];\                              //存储时间信息
    debug_get_time(cache, sizeof(cache));\        //将当前时间格式化为字符串,并存在cache中
    debug_print_time("[%s] [%s] [%s:%d] [%s] " fmt "\n", cache, level, path, __LINE__, __func__, ## __VA_ARGS__);\                       //输出格式化的日志信息
}while(0)

#define print_info(fmt, ...)    ___LOG___(fmt,  "INFO"  ,__FILE__, ## __VA_ARGS__)

        __LOG__分析:

  • fmtlevelpath 是宏的前三个参数,分别用于格式化字符串、日志级别和文件路径
  • __LINE__ 是预定义的宏,在编译时会被替换为当前代码实际行号
  • __func__ 是预定义的宏,在编译时会被替换为当前函数名称

        print_info 分析:

  创建一个自定义的日志记录宏,fmt 是一个参数,用于指定要打印的格式化字符串。... 是C99标准引入的可变参数宏,允许宏接受任意数量的参数。

  • fmt:格式化字符串。
  • "INFO":通常是日志级别或类型,这里指定为 "INFO"。
  • __FILE__预定义的宏,它在编译时会被替换为当前文件名称,记录日志信息的来源文件。
  • ## __VA_ARGS__:宏变量参数的展开,将可变参数列表连接到格式化字符串中。## 是C99标准引入的预处理器操作符,将宏参数转变字符串字面量。__VA_ARGS__ 是可变参数宏的一部分,将宏调用中提供的额外参数作为列表传递给 __LOG___ 宏。

        ... 和 __VA_ARGS__ 一起工作,允许宏定义接受任意数量的参数。

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值