当前位置: 首页 > news >正文

C —— 宏

C —— 宏

  • 基本宏定义
    • 符号常量
    • 函数式宏
  • 宏的特殊用法
    • 多行宏
    • 字符串化操作符
    • 连接操作符
    • 可变参数宏
      • 基本语法
      • 省略可变参数(C99 和 GNU C 扩展)
      • 计算可变参数的个数(GNU C 扩展)**
      • `_Generic` + 可变参数宏(C11)
    • 可变参数宏的常见用途
    • 注意事项
    • 总结
  • 常用预定义宏
      • 1. `__LINE__` - 当前行号
      • 2. `__FILE__` - 当前文件名
      • 3. `__DATE__` - 编译日期(格式 "MMM DD YYYY")
      • 4. `__TIME__` - 编译时间(格式 "HH:MM:SS")
      • 5. `__STDC__` - 是否遵循 ANSI C 标准
      • 6. `__func__` - 当前函数名(C99)
      • 综合示例:调试日志宏
      • 关键点总结
  • 条件编译
  • C语言条件编译详解
    • 基本条件编译指令
      • 1. `#ifdef` / `#ifndef` / `#endif`
      • 2. `#if` / `#elif` / `#else` / `#endif`
    • 常见应用场景
      • 1. 跨平台开发
      • 2. 调试代码
      • 3. 功能开关
    • 预定义宏与条件编译
    • 实用技巧
      • 1. 宏定义检查
      • 2. 防止头文件重复包含
      • 3. 编译器特定指令
    • 注意事项
    • 高级用法
      • 1. 宏参数的条件编译
      • 2. 静态断言(C11之前)

我们在学习C语言时,很早就接触过了宏的概念,今天我们就对宏这一用法进行详细的梳理:

基本宏定义

符号常量

这种用法我们最熟悉,就是简单定义,然后放到代码段中去用:
在这里插入图片描述

#define PI 3.14159
#define MAX_SIZE 100
#define AUTHOR "John Doe"

这里我们就简单介绍一下,不做过多展开。

函数式宏

除了我们自己可以写函数之外,宏也可以定义函数:
在这里插入图片描述
在这里插入图片描述
这里给所有参数都带上括号,是为了保证运算顺序的正确性。如果不带括号,运算结果可能就会大相径庭:

#define FUCNTION(a,b)(a * b)

int main()
{
    printf("%d\n", FUCNTION(10 + 10,20 + 20));
}

在这里插入图片描述
但是如果我加上括号:

#define FUCNTION(a,b)((a) * (b))

int main()
{
    printf("%d\n", FUCNTION(10 + 10,20 + 20));
}

在这里插入图片描述
因为,宏不对本身做任何处理,它只会傻瓜式的展开,我们来看看两个函数展开之后长啥样子:
在这里插入图片描述
所以在编写宏时,如果运算顺序很重要,记得打上括号哦~。

宏的特殊用法

多行宏

使用反斜杠\可以定义多行宏:

# define SWAP(a,b,type){ \
    type temp = a; \
    a = b; \
    b = temp; \
}

int main()
{
    int x = 10;
    int y = 20;
    printf("%d %d\n", x, y);

    SWAP(x, y, int)
    printf("%d %d\n", x, y);
}

在这里插入图片描述

字符串化操作符

将宏参数转换为字符串:

#define STR(x) #x

int main()
{
    printf("%s\n", STR(123456));
}

在这里插入图片描述

连接操作符

将两个标记连接成一个:

#define CONCAT(a,b) a##b

int main()
{
    int xy = 10;
    printf("%d\n", CONCAT(x, y));
}

在这里插入图片描述

可变参数宏

接下来就是一些比较高级的用法了,在 C 语言中,可变参数宏(Variadic Macros)允许宏接受 可变数量的参数,类似于 printf() 这样的可变参数函数。它使用 … 和 VA_ARGS 来实现,是 C99 标准引入的特性。

基本语法

#define MACRO_NAME(fixed_args, ...) replacement_text __VA_ARGS__
... 表示可变参数部分。

__VA_ARGS__ 在宏展开时会被替换为传入的可变参数。
#define LOG(fmt,...) printf(fmt,__VA_ARGS__)

int main()
{
    LOG("Hello, %s! Your score is %d.\n", "Alice", 95);
}

省略可变参数(C99 和 GNU C 扩展)

如果可变参数可能为空,C99 要求至少有一个参数,但 GNU C 允许完全省略:

// C99 标准写法(必须至少有一个可变参数)
#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)

// GNU C 扩展(允许完全省略可变参数)
#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)

int main() {
    LOG("Hello, world!\n");  // ✅ GNU C 允许,C99 会报错
    return 0;
}

在这里插入图片描述
说明:

## 是 标记粘贴运算符,如果 __VA_ARGS__ 为空,它会删除前面的逗号,避免语法错误。

计算可变参数的个数(GNU C 扩展)**

#define COUNT_ARGS(...) sizeof((int[]){__VA_ARGS__}) / sizeof(int)

int main() {
    printf("Number of args: %zu\n", COUNT_ARGS(1, 2, 3, 4));  // 输出 4
    return 0;
}

注意:

  • 这种方法仅适用于 相同类型的参数(如 int)。
  • 不是标准 C,仅适用于 GCC/Clang。

_Generic + 可变参数宏(C11)

结合 _Generic 实现类型安全的可变参数宏:

#include <stdio.h>

#define PRINT_TYPE(x) _Generic((x), \
    int: printf("%d\n", x), \
    float: printf("%f\n", x), \
    char*: printf("%s\n", x) \
)

#define PRINT_ALL(...) do { \
    int arr[] = {__VA_ARGS__}; \
    for (size_t i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { \
        PRINT_TYPE(arr[i]); \
    } \
} while(0)

int main() {
    PRINT_ALL(42, 3.14, "Hello");  // 需要更复杂的实现
    return 0;
}

说明:

  • 这个例子只是示意,实际实现需要更复杂的宏技巧。

可变参数宏的常见用途

  1. 日志系统
    #define LOG_INFO(fmt, ...) fprintf(stderr, "[INFO] " fmt, ##__VA_ARGS__)
    
  2. 调试输出
    #define DBG_PRINT(...) printf("[DEBUG] %s:%d: ", __FILE__, __LINE__); \
                          printf(__VA_ARGS__)
    
  3. 泛型容器操作
    #define FOREACH(item, ...) \
        for (int keep = 1, count = 0; keep && count != sizeof((int[]){__VA_ARGS__})/sizeof(int); \
             keep = !keep, count++) \
            for (item = ((int[]){__VA_ARGS__})[count]; keep; keep = !keep)
    

注意事项

  1. 标准 C (C99) 要求至少一个可变参数,但 GNU C 允许零个。
  2. 避免参数副作用(如 LOG("%d", i++) 可能导致多次求值)。
  3. 调试困难,因为宏在预处理阶段展开,错误信息可能难以理解。
  4. 可读性差,复杂的可变参数宏可能难以维护。

总结

特性说明
...定义可变参数
__VA_ARGS__引用可变参数
##__VA_ARGS__允许省略可变参数(GNU C)
_Generic + 可变宏类型安全可变参数(C11)
do-while(0)包裹多语句宏

最佳实践:

  • 优先使用 函数 替代复杂宏。
  • 如果必须用宏,确保 可读性安全性
  • 在跨平台代码中,避免依赖 GNU C 扩展(如 ##__VA_ARGS__)。

可变参数宏非常强大,但需要谨慎使用!

常用预定义宏

__LINE__      // 当前行号
__FILE__      // 当前文件名
__DATE__      // 编译日期
__TIME__      // 编译时间
__STDC__      // 如果编译器符合ANSI C标准则为1
__func__      // 当前函数名 (C99)

下面是对这些预定义宏的具体用法示例,每个宏都附带一个完整的代码示例和输出说明:


1. __LINE__ - 当前行号

#include <stdio.h>

int main() {
    printf("This line is at: %d\n", __LINE__);  // 输出当前行号
    printf("Now we're at line: %d\n", __LINE__); // 行号+1
    return 0;
}

输出示例:

This line is at: 4
Now we're at line: 5

2. __FILE__ - 当前文件名

#include <stdio.h>

int main() {
    printf("This code is in file: %s\n", __FILE__);
    return 0;
}

输出示例:

This code is in file: example.c

3. __DATE__ - 编译日期(格式 “MMM DD YYYY”)

#include <stdio.h>

int main() {
    printf("This program was compiled on: %s\n", __DATE__);
    return 0;
}

输出示例:

This program was compiled on: Jun 15 2023

4. __TIME__ - 编译时间(格式 “HH:MM:SS”)

#include <stdio.h>

int main() {
    printf("Compilation time: %s\n", __TIME__);
    return 0;
}

输出示例:

Compilation time: 14:30:45

5. __STDC__ - 是否遵循 ANSI C 标准

#include <stdio.h>

int main() {
    #if __STDC__
        printf("This compiler is ANSI C compliant\n");
    #else
        printf("This compiler is NOT ANSI C compliant\n");
    #endif
    return 0;
}

典型输出(现代编译器):

This compiler is ANSI C compliant

6. __func__ - 当前函数名(C99)

#include <stdio.h>

void my_function() {
    printf("Current function: %s\n", __func__);
}

int main() {
    printf("Entering %s\n", __func__);
    my_function();
    return 0;
}

输出:

Entering main
Current function: my_function

综合示例:调试日志宏

#include <stdio.h>

#define LOG(msg) printf("[%s] %s (Line %d in %s): %s\n", \
                      __TIME__, __func__, __LINE__, __FILE__, msg)

int main() {
    LOG("Program started");
    if (1) {
        LOG("Inside conditional block");
    }
    return 0;
}

输出示例:

[14:30:45] main (Line 7 in example.c): Program started
[14:30:45] main (Line 9 in example.c): Inside conditional block

关键点总结

作用输出示例标准支持
__LINE__当前行号42C89
__FILE__源文件名"example.c"C89
__DATE__编译日期"Jun 15 2023"C89
__TIME__编译时间"14:30:45"C89
__STDC__ANSI C 合规1C89
__func__函数名"main"C99

注意事项:

  1. __func__ 是 C99 引入的,不是字符串字面量(不能直接拼接)
  2. 所有带下划线的宏由编译器预定义,不可修改
  3. 在调试和日志系统中特别有用

条件编译

C语言条件编译详解

条件编译是C语言预处理器提供的一项重要功能,它允许程序员在编译时根据不同的条件选择性地包含或排除代码段。条件编译在跨平台开发、调试代码、功能开关等场景中非常有用。

基本条件编译指令

1. #ifdef / #ifndef / #endif

#ifdef MACRO_NAME
    // 如果定义了MACRO_NAME,则编译这部分代码
#endif

#ifndef MACRO_NAME
    // 如果没有定义MACRO_NAME,则编译这部分代码
#endif

示例:

#define DEBUG  // 定义DEBUG宏

#ifdef DEBUG
    printf("Debug information\n");
#endif

#ifndef RELEASE
    printf("Not in release mode\n");
#endif

2. #if / #elif / #else / #endif

#if 表达式
    // 如果表达式为真,编译这部分代码
#elif 其他表达式
    // 如果前面的表达式为假且这个表达式为真,编译这部分代码
#else
    // 如果所有表达式都为假,编译这部分代码
#endif

示例:

#define VERSION 3

#if VERSION == 1
    printf("Version 1\n");
#elif VERSION == 2
    printf("Version 2\n");
#elif VERSION == 3
    printf("Version 3\n");
#else
    printf("Unknown version\n");
#endif

常见应用场景

1. 跨平台开发

#ifdef _WIN32
    // Windows平台专用代码
    #include <windows.h>
#elif __linux__
    // Linux平台专用代码
    #include <unistd.h>
#elif __APPLE__
    // macOS平台专用代码
    #include <TargetConditionals.h>
#endif

2. 调试代码

#define DEBUG_MODE 1

#if DEBUG_MODE
    #define DEBUG_PRINT(fmt, ...) printf("[DEBUG] " fmt, ##__VA_ARGS__)
#else
    #define DEBUG_PRINT(fmt, ...) // 定义为空,不产生任何代码
#endif

int main() {
    DEBUG_PRINT("This is debug info: %d\n", 42);
    return 0;
}

3. 功能开关

#define FEATURE_A_ENABLED 1
#define FEATURE_B_ENABLED 0

#if FEATURE_A_ENABLED
    void feature_a() {
        printf("Feature A is enabled\n");
    }
#endif

#if FEATURE_B_ENABLED
    void feature_b() {
        printf("Feature B is enabled\n");
    }
#endif

int main() {
    #if FEATURE_A_ENABLED
        feature_a();
    #endif
    
    #if FEATURE_B_ENABLED
        feature_b();
    #endif
    
    return 0;
}

预定义宏与条件编译

C语言提供了一些预定义宏,常用于条件编译:

#if __STDC_VERSION__ >= 201112L
    // C11或更高版本代码
#elif __STDC_VERSION__ >= 199901L
    // C99代码
#else
    // C89代码
#endif

实用技巧

1. 宏定义检查

#if defined(MACRO1) && !defined(MACRO2)
    // 如果定义了MACRO1且没有定义MACRO2
#endif

2. 防止头文件重复包含

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// 头文件内容

#endif // MYHEADER_H

3. 编译器特定指令

#ifdef __GNUC__
    // GCC编译器特有代码
    #define likely(x)       __builtin_expect(!!(x), 1)
    #define unlikely(x)     __builtin_expect(!!(x), 0)
#else
    // 其他编译器
    #define likely(x)       (x)
    #define unlikely(x)     (x)
#endif

注意事项

  1. 条件编译指令必须在函数体外使用
  2. 表达式必须是常量表达式
  3. 过度使用条件编译会使代码难以维护
  4. 确保所有条件分支都有正确的#endif匹配

高级用法

1. 宏参数的条件编译

#define CONFIG(opt) (defined(CONFIG_##opt) && CONFIG_##opt)

#if CONFIG(DEBUG)
    printf("Debug mode enabled\n");
#endif

2. 静态断言(C11之前)

#define STATIC_ASSERT(expr, msg) \
    typedef char static_assertion_##msg[(expr) ? 1 : -1]

STATIC_ASSERT(sizeof(int) == 4, int_size_must_be_4_bytes);

条件编译是C语言中非常强大的功能,合理使用可以大大提高代码的灵活性和可移植性。

http://www.dtcms.com/a/122603.html

相关文章:

  • Redis-场景缓存+秒杀+管道+消息队列
  • 保留格式地一键翻译英文ppt
  • etf可以T+0交易吗?
  • 基础知识补充篇:什么是DAPP前端连接中的provider
  • 用网页JS实现数据添加和取出的操作,链表
  • Class 文件和类加载机制
  • 【10】数据结构的矩阵与广义表篇章
  • 聊透多线程编程-线程基础-3.C# Thread 如何从非UI线程直接更新UI元素
  • 学习MySQL的第六天
  • vue+uniapp 获取上一页直接传递的参数
  • 大数据(6)【Kettle入门指南】从零开始掌握ETL工具:基础操作与实战案例解析
  • Spring Boot 自定义配置类(包含字符串、数字、布尔、小数、集合、映射、嵌套对象)实现步骤及示例
  • PHP 表单处理详解
  • docker安装软件汇总(持续更新)
  • 2022年全国职业院校技能大赛 高职组 “大数据技术与应用” 赛项赛卷(2卷)任务书
  • (三)行为模式:12、访问者模式(Visitor Pattern)(C++示例)
  • 家居实用品:生活中的艺术,家的温馨源泉‌
  • skynet.dispatch 使用详解
  • 微信小程序中的openid的作用
  • 对比 redis keys 命令 ,下次面试说用 scan
  • Python-Django+vue宠物服务管理系统功能说明
  • 如何在powerbi使用自定义SQL
  • 自定义控件封装
  • 【QT】QT编译链接 msql 数据库
  • vue用D3.js实现轮盘抽奖
  • AC 自动机 洛谷P3808 P3796 P5357
  • 深度学习篇---LSTMFFTGCT
  • CSV文件读取文件表头字符串含ZWNBSP(零宽度空白字符)
  • Python第八章02:数据可视化Pyecharts包无法使用
  • 【scikit-learn基础】--『预处理』之 数据缩放