当前位置: 首页 > news >正文

c++日志宏 INFO(...)

🧱 核心功能设计

我们希望 INFO(…) 具备以下能力:

✅ 自动记录当前文件名(__FILE__)
✅ 自动记录当前行号(__LINE__)
✅ 支持 printf 风格的格式化(如 %s, %d)
✅ 输出带颜色(绿色)
✅ 封装在命名空间中,避免命名冲突

💡 实现原理

我们将使用 C++ 的三大“黑科技”:

#define 宏:简化调用
__FILE__ 和 __LINE__:自动插入源码位置
va_list / va_start / vprintf:处理可变参数
// main.cpp#include <cstdio>  // 必须包含,因为用了 printf, va_list 等
#include <cstdarg> // 有些编译器需要这个头文件来支持 va_list// ========================
// 1. 定义命名空间 logger
// ========================
namespace Application {
namespace logger {// 宏定义:INFO(...)// 当你写 INFO(...) 时,它会被自动替换为 __printf,并带上文件名和行号#define INFO(...)  Application::logger::__printf(__FILE__, __LINE__, __VA_ARGS__)// 实际的日志打印函数void __printf(const char* file, int line, const char* fmt, ...) {va_list vl;           // 声明一个可变参数列表变量va_start(vl, fmt);    // 初始化,告诉它参数从 fmt 开始// 打印绿色的 [文件名:行号]:printf("\e[32m[%s:%d]:\e[0m ", file, line);// 打印用户自定义的消息,比如 "Hello %s"vprintf(fmt, vl);// 换行printf("\n");// 清理va_end(vl);}} // namespace logger
} // namespace Application// ========================
// 2. 主函数:使用 INFO
// ========================
int main() {// 这是我们想打印的日志INFO("Hello %s", "World");// 再打印一条INFO("User %s is %d years old", "Alice", 25);return 0;
}

我们想实现一个像 printf 一样的函数,但能自动加上 [文件名:行号] 前缀:

INFO("Hello %s, age: %d", "Alice", 25);输出结果为:
[main.cpp:42]: Hello World
[main.cpp:45]: User Alice is 25 years old

这需要函数能接收任意数量和类型的参数,这就是“可变参数”。

🔧 三大法宝:va_list, va_start, vprintf

✅ 1. va_list vl;

作用:声明一个“可变参数列表”的指针变量,用来遍历后面的参数。

类比:就像 int *p; 是指向整数的指针,va_list vl; 是指向“可变参数”的指针。

void __printf(const char* file, int line, const char* fmt, ...) {va_list vl;  // 声明一个“参数指针”// ...
}

此时 vl 还没有初始化,不能用。

✅ 2. va_start(vl, fmt);

作用:初始化 va_list,告诉它“可变参数从哪个参数后面开始”。是后面!!!!!!!!!!!!!!!

参数:
vl:你声明的 va_list 变量
fmt:最后一个固定参数的名字(这里是 fmt)

va_start(vl, fmt);

✅ 执行后,vl 就指向了第一个可变参数 “Alice”。

🔍 为什么是 fmt?

因为函数参数是这样排列的:

file     → "main.cpp"
line     → 18
fmt      → "Hello %s, age: %d"
..."Alice", 25   ← 可变参数从这里开始

所以 va_start(vl, fmt) 的意思是:“vl 从 fmt 后面的第一个参数开始读”。
✅ 3. vprintf(fmt, vl);

作用:打印格式化字符串,但使用 va_list 而不是直接的参数列表。参数:fmt:格式化字符串,比如 "Hello %s, age: %d"vl:已经初始化的 va_list,指向可变参数
vprintf(fmt, vl);

它会:

看 fmt 中有 %s 和 %d
从 vl 指向的位置读取第一个参数 "Alice"(匹配 %s)
继续读取下一个参数 25(匹配 %d)
输出:Hello Alice, age: 25

假设我们调用:

INFO("Hello %s, age: %d", "Alice", 25);

等价于:

Application::logger::__printf("main.cpp", 18, "Hello %s, age: %d", "Alice", 25);

进入函数:

void __printf(const char* file, int line, const char* fmt, ...) {

fmt 对应于 “Hello %s, age: %d”

vl 现在指向第一个可变参数 “Alice”
内部机制:vl 是一个指针,它知道如何从栈中一步步读取 “Alice”(const char*),然后是 25(int)
vprintf 内部会:

解析 fmt = “Hello %s, age: %d”
遇到 %s → 从 vl 读取一个 const char* → 得到 “Alice”
遇到 %d → 从 vl 读取一个 int → 得到 25
输出:Hello Alice, age: 25

栈(Stack)中的参数布局:+------------------+
| "main.cpp"       |  ← file
+------------------+
| 18               |  ← line
+------------------+
| "Hello %s..."    |  ← fmt
+------------------+
| "Alice"          |  ← 第一个可变参数
+------------------+
| 25               |  ← 第二个可变参数
+------------------+↑vl 经过 va_start 后指向这里

在这里插入图片描述

http://www.dtcms.com/a/339600.html

相关文章:

  • Webpack 5 配置完全指南:从入门到精通
  • Docker镜像--镜像分层、UnionFS、镜像发布、私有库Registry
  • Java -- 用户线程和守护线程--线程同步机制
  • 大模型问题:幻觉分类+原因+各个训练阶段产生幻觉+幻觉的检测和评估基准
  • OpenSCA开源社区每日安全漏洞及投毒情报资讯|18th Aug. , 2025
  • 【GNSS定位原理及算法杂记6】​​​​​​PPP(精密单点定位)原理,RTK/PPK/PPP区别讨论
  • usb通信中工作模式:主机模式和设备模式
  • 2025年渗透测试面试题总结-21(题目+回答)
  • 水闸安全监测的主要核心内容
  • Java NIO 核心精讲(上):Channel、Buffer、Selector 详解与 ByteBuffer 完全指南
  • 数字政务安全实战:等保2.0下OA系统的身份认证与数据防护
  • 微软AD国产化替换倒计时——不是选择题,而是生存题
  • 三次握手四次挥手
  • 决策树算法详解
  • Orange的运维学习日记--47.Ansible进阶之异步处理
  • ESP32应用——HTTP client(ESP-IDF框架)
  • STM32之MCU和GPIO
  • AT_abc397_f [ABC397F] Variety Split Hard
  • 高速传输的关键:8B/10B编码学习记录
  • 应用控制技术与内容审计技术
  • 系统架构设计师-操作系统-避免死锁最小资源数原理模拟题
  • 寻找旋转排序数组中的最小值
  • 黄金本周想法
  • 给类或实例打上标识即类的元数据标签方便程序在运行时对其进行分类、识别、筛选
  • 32K上下文开源语音理解、40分钟深度交互——Voxtral-Small-24B-2507本地部署教程
  • GCC编译输出中text,data,bss和dec的含义
  • 构建自主企业:AgenticOps 的技术蓝图
  • 基于 STM32 单片机的远程老人监测系统设计
  • 科大讯飞语音服务之:BNF文件
  • 基于用户画像的个性化匹配模型