【C++编程】__VA_ARGS__和__VA_OPT__

__VA_ARGS__和__VA_OPT__ 

1. #用来把参数转换成字符串:

#include <stdio.h>

#define P(A) printf("%s: %d\n", #A, A);

int main(int argc, char **argv)
{
  int a = 1, b = 2;
  P(a);
  P(b);
  P(a+b);
  return 0;
}

 

2. ##运算符可以用于宏函数的替换部分。这个运算符把两个语言符号组合成单个语言符号,为宏扩展提供了一种连接实际变元的手段。##就是个粘合剂,将前后两部分粘合起来,也就是有“字符化”的意思。但是“##”不能随意粘合任意字符,必须是合法的C语言标示符。在单一的宏定义中,最多可以出现一次“#”或“##”预处理操作符。如果没有指定与“#”或“##”预处理操作符相关的计算次序,则会产生问题。为避免该问题,在单一的宏定义中只能使用其中一种操作符(即,一份“#”或一个“##”,或都不用)。除非非常有必要,否则尽量不要使用“#”和“##”。

#include <stdio.h>

#define XNAME(n) x##n
#define PRINT_XN(n) printf("x" #n " = %d/n", x##n);

int main(void)
{
    int XNAME(1) = 14; // becomes int x1 = 14;
    int XNAME(2) = 20; // becomes int x2 = 20;
    PRINT_XN(1);       // becomes printf("x1 = %d,", x1);
    PRINT_XN(2);       // becomes printf("x2 = %d/n", x2);
    return 0;
}

 

3. __VA_ARGS__ 是 C 预处理器的一个特性,用于处理可变参数宏。它允许你在宏定义中传递不定数量的参数。这里“…”指可变参数。这类宏在被调用时,它(这里指“…”)被表示成零个或多个符号,包括里面的逗号,一直到到右括弧结束为止。当被调用时,在宏体中,那些符号序列集合将代替里面的__VA_ARGS__标识符,用法示例如下:

#include <stdio.h>

#define PRINTF(format, ...) printf(format, __VA_ARGS__)

int main() {
    PRINTF("Hello, %s! You have %d new messages.\n", "Alice", 5);
    return 0;
}

假设你有这样一个宏:

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

当这样用:

LOG("Hello\n");  // 编译错误! __VA_ARGS__ 为空

会变成:

printf("Hello\n", );  // 错误!因为多了一个逗号

 

解决方法:

1. 使用__VA_OPT__ :

#define MACRO(args...) something __VA_OPT__(,) args

示例安全加逗号:

#include <stdio.h>

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

int main() {
    LOG("Hello\n");                    // 展开为:printf("Hello\n");
    LOG("Value: %d\n", 42);            // 展开为:printf("Value: %d\n", 42);
}

 

2. GCC扩展 ##__VA_ARGS__

#define MACRO(fmt, ...) printf(fmt, ##__VA_ARGS__)
  • ##__VA_ARGS__ 是 GCC 和 Clang 支持的扩展,当没有参数时自动去掉前面的逗号。
  • 这是非标准的,但实用。

 

#include <stdio.h>

#define LOG(level, fmt, ...) \
    do { \
        printf("[%s] ", level); \
        printf(fmt, ##__VA_ARGS__); \
    } while (0)

int main() {
    LOG("INFO", "Simple message\n");
    LOG("DEBUG", "x=%d, y=%d\n", 10, 20);
    return 0;
}

2. 在宏__VA_ARGS__中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组,简化理解:#是“字符串化”的意思,出现在宏定义中的#是把跟在后面的参数转换成一个字符串

#define ERROR_LOG(module)   fprintf(stderr,"error: "#module"\n")

即:

ERROR_LOG("add"); 转换为 fprintf(stderr,"error: "add"\n");
ERROR_LOG(devied=0); 转换为 fprintf(stderr,"error: devied=0\n");

“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。

#define TYPE1(type,name)   type name_##type##_type
#define TYPE2(type,name)   type name##_##type##_type

即:

TYPE1(int, c); 转换为:int  name_int_type ; (因为##号将后面分为 name_ 、type 、 _type三组,替换后强制连接)
TYPE2(int, d);转换为: int  d_int_type ; (因为##号将后面分为 name、_、type 、_type四组,替换后强制连接)

 

__VA_ARGS__ 是 C 预处理器的一个特性,用于处理可变参数宏。它允许你在宏定义中传递不定数量的参数。这里“…”指可变参数。这类宏在被调用时,它(这里指“…”)被表示成零个或多个符号,包括里面的逗号,一直到到右括弧结束为止。当被调用时,在宏体中,那些符号序列集合将代替里面的__VA_ARGS__标识符,用法示例如下:

#include <stdio.h>

#define PRINTF(format, ...) printf(format, __VA_ARGS__)

int main() {
    PRINTF("Hello, %s! You have %d new messages.\n", "Alice", 5);
    return 0;
}

如果没有提供可变参数,使用 __VA_ARGS__ 时可能会导致编译错误。可以使用 GNU C 扩展来避免这种情况。例如:

#define LOG(level, ...) \
    do { \
        printf("[%s] ", level); \
        printf(__VA_ARGS__); \
    } while (0)

#define LOG_INFO(msg) LOG("INFO", msg)

int main() {
    LOG_INFO("This is an info message\n");
    LOG("ERROR", "An error occurred: %s\n", "File not found");
    return 0;
}

注意事项

  1. 参数分隔符:如果可变参数为空,__VA_ARGS__ 在某些情况下可能会导致编译错误,尤其是在没有传递任何参数时。确保在使用之前进行适当的检查。

  2. 格式化字符串:确保格式化字符串和后续参数数量匹配,以避免未定义的行为。

  3. 标准兼容性:__VA_ARGS__ 是 C99 及更高版本的特性,确保你的编译器支持。

 

可以结合其他宏来实现更复杂的功能,例如动态生成多个日志记录:

#define LOG(level, count, ...) \
    do { \
        printf("[%s] ", level); \
        for (int i = 0; i < count; i++) { \
            printf(__VA_ARGS__); \
        } \
    } while (0)

int main() {
    LOG("DEBUG", 3, "This is a debug message\n");
    return 0;
}

 

对于可变参数为空的情况,可以使用 ##__VA_ARGS_ : 宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错。

#include <stdio.h>

#define DEBUG2(format, ...) do { \
    printf(format, ##__VA_ARGS__); \
} while (0)

int main() {
    int value = 42;

    DEBUG2("Debug: The value is %d\n", value);  // 输出: Debug: The value is 42
    DEBUG2("Debug: No additional info.\n");      // 输出: Debug: No additional info.

    return 0;
}

1. #用来把参数转换成字符.

2. ##这个运算符把两个语言符号组合成单个语言符号

3.  __VA_ARGS__ 是一个可变参数的宏,实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。

 

 

参考资料

1. C语言 ## __VA_ARGS__ 宏

2. 整理:C/C++可变参数,“## __VA_ARGS__”宏的介绍和使用

posted @ 2022-05-21 15:06  苏格拉底的落泪  阅读(91)  评论(0)    收藏  举报