目录

1.宏

1.1.宏的类型

1.2.特殊符号

1.3.常见问题和解决方案

1.4.标准预定义宏

2.条件编译

2.1.核心指令

2.2.支持的运算符

3.示例:日志宏

##__VA_ARGS__

字符串字面量自动拼接

4.enum(枚举)

4.1枚举的基本语法

4.2. 枚举的值规则


1.宏

宏是预处理器指令,在编译前进行文本替换

1.1.宏的类型

  • 对象式宏(无参)
#define BUFFER_SIZE 1024
#define PATH "C:\\temp"
#define DEBUG true

char buffer[BUFFER_SIZE];  // char buffer[1024];
  • 函数式宏(带参)
// 基本形式
#define MIN(a, b) ((a) < (b) ? (a) : (b))

// 多语句宏
#define LOG_ERROR(msg) do { \
    std::cerr << "Error: " << msg << std::endl; \
    error_count++; \
} while(0)

// 可变参数宏(C99/C++11)
#define PRINT(...) printf(__VA_ARGS__)
#define LOG(fmt, ...) printf("[LOG] " fmt, __VA_ARGS__)
  • 如果把宏定义写成多行,需要在每行末尾加上反斜杠 \ 进行转义行(行延续),表示下一行属于同一个宏定义
  • 反斜杠 \ 后面必须直接换行,不能有空格或注释
  • 最后一个 while (0) 那一行不需要反斜杠,表示宏定义结束

1.2.特殊符号

#define STR(x) #x          // 字符串化:STR(hello) -> "hello"
#define CONCAT(a, b) a##b  // 连接:CONCAT(var, 1) -> var1

// 示例
const char* str = STR(test);           // "test"
int var1 = 100;
int value = CONCAT(var, 1);            // var1

1.3.常见问题和解决方案

  • 运算符优先级
// 错误
#define SQUARE(x) x * x
int result = SQUARE(3+2);  // 3+2*3+2 = 11

// 正确
#define SQUARE(x) ((x) * (x))
int result = SQUARE(3+2);  // ((3+2)*(3+2)) = 25

能加括号,加括号

  • 副作用

// 参数被多次求值
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int x = 1, y = 1;
int z = MAX(++x, y);  // ((++x) > (y) ? (++x) : (y))
// x被增加了两次!结果x=3

// 对于有副作用的表达式,使用内联函数
template<typename T>
inline T max(T a, T b) {
    return a > b ? a : b;
}

调用 max(++x, y)时:

  • 函数参数的求值,发生在函数体执行之前(调用点)
  • ++x 只执行一次,结果(值)传给 a
  • 函数内部 a 和 b 只是普通的变量,再比较 a > b 不会重复副作用

1.4.标准预定义宏

宏名称 说明 示例输出
__FILE__ 当前源文件名(字符串) "main.cpp"
__LINE__ 当前行号(整数) 42
__DATE__ 编译日期(Mmm dd yyyy) "Jan 15 2026"
__TIME__ 编译时间(hh:mm:ss) "14:30:25"
__FUNCTION__ 当前函数名(C++) "main"
__func__ 当前函数名(C99/C++11) "main"
__cplusplus C++标准版本 201703L(C++17)
__STDC__ 是否遵循ANSI C 1

2.条件编译

条件编译是预处理器根据条件决定哪些代码参与编译的机制

常用于跨平台、调试、特性开关等场景

2.1.核心指令

#if condition        // 如果条件为真
#elif condition      // 否则如果
#else                // 否则
#endif               // 结束标志

#ifdef macro         // 如果宏已定义
#ifndef macro        // 如果宏未定义
#endif

#if defined(macro)   // 同#ifdef
#if !defined(macro)  // 同#ifndef

2.2.支持的运算符

#define LEVEL 3
#define FEATURE_A 1
#define FEATURE_B 0

#if LEVEL > 2                    // 比较
#if LEVEL == 3 && FEATURE_A      // 逻辑与
#if LEVEL == 2 || FEATURE_B      // 逻辑或
#if !FEATURE_B                   // 逻辑非
#if defined(DEBUG) && LEVEL > 1  // 组合条件

//算术运算
#define BASE 10
#define MULTIPLIER 2

#if BASE * MULTIPLIER > 15
    // 20 > 15,条件为真
    const int VALUE = BASE * MULTIPLIER;
#endif

3.示例:日志宏

#define INF 0
#define DBG 1
#define ERR 2
#define DEFAULT_LEVEL INF

#define LOG(level, format, ...)do{\
    if(level < DEFAULT_LEVEL) break;\
    time_t t = time(nullptr);\
    struct tm ltm_buf;\
    struct tm* ltm = localtime_r(&t, &ltm_buf);\
    char tmp[32] = {0};\
    strftime(tmp, 31, "%H:%M:%S", ltm);\
    fprintf(stdout, "[%s:%d %s] " format "\n", __FILE__, __LINE__, tmp, ##_VA_ARGS_);\
}while(0)

#define INF_LOG(format, ...) LOG(INF, format, ##_VA_ARGS_)
#define DBG_LOG(format, ...) LOG(DBG, format, ##_VA_ARGS_)
#define ERR_LOG(format, ...) LOG(ERR, format, ##_VA_ARGS_)
  • #define LOG(level, format, ...)

level:日志等级数值      format:格式化字符串     ...:可变参数,对应 format 中的占位符

  • do { ... } while(0)

宏展开后等价于执行一次就退出的"一次性循环",作用是打包多条语句为一个整体

目的 说明
强制加分号 调用 LOG(...); 时分号会补全 while(0);,语法像一个普通语句
防止悬挂 如果不用 do-while(0),直接 {...} 在某些 if-else 场景会导致语法错误
作为跳出点 break 可以跳出 do-while(0),实现提前退出宏
//错误: 用花括号的宏
#define LOG_BAD() { \
    time_t t = time(nullptr); \
    printf("[%ld]", t); \
}

// 使用时出现语法错误
if (flag)
    LOG_BAD();  // 展开为 { ... }; —— 分号导致后面的 else 孤立
else
    do_something();  // 编译错误!
  • ##__VA_ARGS__

情况 展开结果
有可变参数 , ##__VA_ARGS__ → , arg1, arg2
无可变参数 , ##__VA_ARGS__ → 前面的逗号被 ## 删除,fprintf 只收到前缀
LOG(1, "hello");
// 展开 → fprintf(..., "[时间 文件:行号] hello\n");

LOG(1, "value=%d", 42);
// 展开 → fprintf(..., "[时间 文件:行号] value=%d\n", 42);
维度 __VA_ARGS__ ##__VA_ARGS__
来源 C99 标准 GNU 扩展(GCC/Clang 支持,MSVC 自动兼容)
功能 展开可变参数列表 展开可变参数列表,并在空参数时自动删除前面逗号
标准性  标准 C/C++  非标准(C++20 前),但主流编译器都支持
  • 字符串字面量自动拼接

// 这三种写法完全等价:
"Hello" " " "World"     // → "Hello World"
"Hello World"           
"Hello" " World"        // → "Hello World"

C/C++ 编译器会自动将相邻的字符串字面量拼接成一个字符串

4.enum(枚举)

enum(枚举)是一种特殊的数据类型,它允许变量只能取一组预定义的常量值
枚举就是为了给一组相关的整数常量起个有意义的名字,让代码更易读、更安全、更易维护

4.1枚举的基本语法

// 方式1:匿名枚举(最常用,简单场景)
enum {
    RED,
    GREEN,
    BLUE
};
// 可以直接用 RED, GREEN, BLUE

// 方式2:有名枚举(可以声明变量)
enum Color {
    RED,
    GREEN,
    BLUE
};
// 使用:enum Color myColor = RED;

// 方式3:typedef简化(C语言最推荐)
typedef enum {
    RED,
    GREEN,
    BLUE
} Color;
// 使用:Color myColor = RED;  // 不用写enum关键字

4.2. 枚举的值规则

#include <stdio.h>

int main() {
    // 规则1:默认从0开始,依次+1
    enum {
        A,  // 0
        B,  // 1
        C   // 2
    };
    printf("A=%d, B=%d, C=%d\n", A, B, C);  // 输出:0,1,2
    
    // 规则2:可以手动赋值
    enum {
        X = 10,
        Y,    // 自动是11
        Z = 5,
        W     // 自动是6
    };
    printf("X=%d, Y=%d, Z=%d, W=%d\n", X, Y, Z, W);  // 10,11,5,6
    
    return 0;
}
  • 枚举就是给数字起个好记的名字,让代码读起来像句子一样自然

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐