1 条题解

  • 1
    @ 2025-10-9 13:19:21

    这是一篇关于 C 语言中宏定义的笔记.

    在编译时,预处理器会首先将宏定义的内容作文本替换,随后对代码进行编译.

    用法

    定义常量

    用于替代程序中重复出现的常量,提升可读性和维护性:

    #define PI 3.14
    

    带参数的宏(类函数宏)

    宏可以接受参数并生成表达式,但需注意运算符优先级问题.

    其形式如下:

    #define name( parament-list ) stuff
    

    例如:

    #define MAX(x, y) ((x) > (y) ? (x) : (y))
    #define SQUARE(x, x) ((x) * (x))
    

    注:若第二个宏定义为 #define SQUARE(x, x) (x * x),则 SQUARE(x + 1, x + 1) 将被展开为 x + 1 * x + 1,其值为 2 * x + 1,而不是期望的 (x + 1) * (x + 1),因此该宏定义应该写成 #define SQUARE(x, x) ((x) * (x)).

    常用的功能是用于获取结构体成员中成员相对结构体指针的基址偏移量

    #define OFFSET(type, member) (int)&(((type*)0)->member)
    

    其中,type 表示结构体的类型,member 表示结构体中的某个元素. 该计算过程如下:

    1. 首先使用 ((type*)0) 获取一个起始地址为 0type 类型的结构体指针.
    2. 随后使用 ->member 引用结构体中的 member 成员.
    3. 最后用 & 获取 member 的地址,并用 (int) 将获取的地址强制转换成 int 类型.

    由于起始地址为 0,取到结构体成员的绝对地址就是该成员在结构体中的偏移值.

    对于一个如下的结构体:

    struct Test {
    	int a;
    	int b;
    	int c;
    };
    

    OFFSET(struct Test, c) 的值为 8.

    多行宏定义

    使用反斜杠 \​ 实现多行代码的宏:

    #define LOG(msg) do { \
        printf("[LOG] %s\n", msg); \
        write_to_file(msg); \
    } while(0)
    

    条件编译与预定义宏

    利用宏实现跨平台或调试代码:

    #ifdef DEBUG
        printf("Debug info: x=%d\n", x);  // 仅在调试模式生效
    #endif
    

    预定义宏(自动提供编译信息):

    printf("Error in %s at line %d", __FILE__, __LINE__);  // 输出文件名和行号
    

    其他预定义宏:__DATE__(编译日期)、__TIME__(编译时间).

    特殊符号

    #​ ​ 运算符(字符串化)

    将宏参数转换为字符串:

    #define STR(s) #s
    

    ##​ ​ 运算符(连接符)

    拼接标识符生成新变量或函数名:

    #define VAR_NAME(n) var_##n
    

    int VAR_NAME(1) = 10; 会被展开为 int var_1 = 10;.

    可变参数宏 __VA_ARGS__

    ​ 支持可变数量的参数:

    #define LOG(fmt, ...) printf(fmt, __VA_ARGS__)
    

    编译器在将宏展开时,会用变参列表替换掉宏定义中的所有 __VA_ARGS__ 标识符.

    但是,当我们运行如下代码行时:

    LOG("hello\n");
    

    会产生语法错误,原因在于我们只给 LOG 传递了一个参数,而变参为空. 当宏展开后,会变成如下模样:

    printf("hello\n", );
    

    宏展开后,在第一个字符串参数的后面还有一个逗号,所以就产生了一个语法错误.

    为了避免这个错误,我们使用 ## 运算符改进这个宏:

    #define LOG(fmt,...) printf(fmt, ##__VA_ARGS__)
    

    当变参列表为空时,## 有一个特殊的用处:它会将固定参数 fmt 后面的逗号删除掉,这样宏也就可以正常使用了.

    注意事项

    宏定义与 typedef 的区别在于:宏定义是文本替换,而 typedef​ 是类型别名.

    信息

    ID
    110
    时间
    1000ms
    内存
    256MiB
    难度
    6
    标签
    (无)
    递交数
    117
    已通过
    32
    上传者