服务器设计细节 之 【日志宏】(宏、条件编译、枚举)
·
目录
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, <m_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;
}
- 枚举就是给数字起个好记的名字,让代码读起来像句子一样自然
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)