跳转至

1 宏对象 (object-like-macro)

  • 使用场景:
  • 宏对象常用于定义常量和类型别名
  • 宏函数也能定义不带参数的函数,结构 (但这意义不大)
#define PI 3.14
#define FLOAT_64 double
#define CLASS_B class B{};
CLASS_B
#undef CLASS_B
int main(){
    B b;
    FLOAT_64 f64=PI;
}

2 宏函数 (Function-like-macro)

  • 使用场景:
  • 宏函数常用于定义表达式
  • 宏函数也可定义带参数函数,甚至用来定义模板结构体
//宏函数定义表达式
#define EQUAL(a,b) ((a)==(b))
//宏函数定义模板类
#define CLASS_A(X) template<class T=X> \
class A{\
using VALUE_TYPE=T;\
public:\
VALUE_TYPE value;\
};
CLASS_A(int)
#undef CLASS_A

int main(){
    A<> as1{2};
    A<double> as2{ 1.1 };
    EQUAL(1,2);
}

3 可变函数宏

3.1 使用__VA_ARGS__ 宏

  • 可变参数表示,有 2 种方式,分为无名、别名:
    • 无名可变参数,用 ... 表示,如: #define debug(...) printf(__VA_ARGS__)
    • 可变参数别名,如:#define debug(args...) printf(args)
  • 可变参数展开,也按无名、别名来区分:
    • 无名可变参数展开,用 __VA_ARGS__ 展开。(示例见上面)
    • 可变参数别名, 直接使用参数别名即可。(示例见上面)
  • 拼接符 ## 解决可变参数无参传入,可变参数参数前逗号 , 导致报错问题,如:
#include <stdio.h>
// 没有##arg,当使用debug("hello, world");时,会展开为printf("hello, world",);导致报错
#define debug(format, args...) printf(format, ##args)
int main(void)
{
    int year = 2018
    debug("hello, world");  //只有format参数,没有args可变参数
}
  • 示例:利用宏函数处理 jni 种 C 和 C++相同接口不同调用规则问题
#ifdef __cplusplus
#define JNIEnv_FUNCTION_ADAPTER(fun, env, args...) env->fun(args);
#else
#define JNIEnv_FUNCTION_ADAPTER(fun, env, args...) (*env)->fun(env, ##args);

4 字符串化 (Stringizing)

  • 注意事项:
  • 当宏使用 # 时,不能将宏展开,如下例中 str(foo) 不会展开宏 foo,因为 # 会直接将 foo 字符串化。当这种情况时,可以借助另一层宏 (不含 #) 来展开,如下例中 xstr 宏可以先将 foo 展开。
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)
      "foo"
xstr (foo)
      xstr (4)
      str (4)
      "4"

5 拼接 (Concatenation)

  • 注意事项
  • ## 先起到分割标识的作用,再拼接其它符号,如下例中 CON_A2NAME_ 当成固定编码,而 ##NAME 会被识别为 a 且和前面 NAME_ 拼接在一起,转换为 NAME_a
#define CON_A1(NAME) int NAME_NAME
#define CON_A2(NAME) int NAME_##NAME
#define CON_A3(NAME) int NAME
#define CON_A4(NAME) int NAME##_NAME
#define CON_A5(NAME) int NAME##_##NAME

int main() {
    CON_A1(a);//->NAME_NAME
    CON_A2(a);//->NAME_a
    CON_A3(a);//->a
    CON_A4(a);//->a_NAME
    CON_A5(a);//->a_a
}

6 预设宏

7 用于调试日志相关宏

  • __FILE__ : 显示当前文件名,如 /usr/local/include/myheader.h
  • __LINE__ : 显示所在行号, 如 100
  • __FUNCTION____func__ : 显现所在函数名
  • __DATE__ : 显示当前时期,如 Feb 12 1996
  • __TIME__ : 显示当前时间,如 23:59:01

7.1 版本相关宏

__cplusplus : 使用 c++编译器,就会定义这个宏,不同版本值如下 - 199711L(C++11 前) - 201103L(C++11) - 201402L(C++14) - 201703L(C++17),或 - 202002L(C++20)
(宏常量)

8 修改行号或文件名

  • 语法:#line linenum filename
  • 示例
#include <stdio.h>

#line 100 "macro-new.c" // 更改下一行的行号和源文件名
int main()              // line 100
{
    printf("%d\n", __LINE__); // line 102
    printf("%d\n", __LINE__); // line 103
    printf("%s\n", __FILE__);
    return 0;
}
  • 输出
102
103
macro-new.c

8 官方文档