在编写代码时,通常会需要打印提示、警告、错误等信息,并且需要灵活控制打印信息的级别。此外,有时可能需要使用宏来控制代码段(主要是调试代码段)是否执行。本文将介绍一种调试宏定义方案,包括打印字符串信息的
LOG1
宏和格式化打印的
LOG2
宏,同时可以通过宏来控制代码段的执行。以下是完整的代码示例:
#ifndef __DEBUG_H__
#define __DEBUG_H__
#include <iostream>
#include <string>
#include <stdio.h>
// 定义日志级别枚举
enum LogLevel
{
DEBUG,
INFO,
WARN,
ERROR,
FATAL
};
// 全局日志级别变量声明
extern LogLevel globalLogLevel;
// 定义日志宏1
#define LOG1(level, message) do { \
if (level >= globalLogLevel) { \
std::cout << "[" #level "] " << __func__ << ":" << __LINE__ << " " << message << std::endl; \
} \
} while (0)
// 定义日志宏2
// stdout带缓冲,按行刷新,fflush(stdout)强制刷新
// stderr不带缓冲,立刻刷新到屏幕
#define LOG2(level, format, args...) do { \
if (level >= globalLogLevel) { \
fprintf(stderr, "[" #level "] %s:%d " format "\r\n", __func__, __LINE__, ##args); \
} \
} while (0)
// 通过宏控制调试代码是否执行
#define EXECUTE
#ifdef EXECUTE
#define DEBUG_EXECUTE(code) {code}
#else
#define DEBUG_EXECUTE(code)
#endif
#endif
为了测试宏定义,需要在主文件中定义全局日志级别。以
INFO
为例,
DEBUG
信息不会被打印。下面是测试文件的示例:
#include "debug.h"
// 全局日志级别变量定义
LogLevel globalLogLevel = INFO;
int main(void)
{
LOG1(DEBUG, "DEBUG message");
LOG1(INFO, "INFO message");
LOG1(WARN, "WARN message");
LOG1(ERROR, "ERROR message");
LOG1(FATAL, "FATAL message");
int num = 10;
LOG2(INFO, "num: %d", num);
DEBUG_EXECUTE(
LOG2(ERROR, "debug execute");
)
}
在宏中,#和##都是预处理运算符。
在第一章中使用#将日志级别变量转换为字符串,而##的作用是在可变参数为0时,删除前面的逗号,只输出字符串。
do while通常用于循环结构,而while参数为0时,表示此代码肯定不是用于循环,那么它有什么用呢?
假设你定义一个这样的宏,本意是调用
DOSOMETHING
时执行两个函数。
#define DOSOMETHING() \
func1(); \
func2();
但在如下代码中使用宏时,宏展开后
func2
无论判断条件如何都会执行。
if (0 < a)
DOSOMETHING();
// 宏展开后
if (0 < a)
func1();
func2();
优化一下,用
{}
包裹宏是否可行呢?如下:
#define DOSOMETHING() { \
func1(); \
func2();}
由于我们写代码习惯在语句后加分号,宏展开后可能会出现编译错误。
而
do while (0)
则能避免这些错误,因此复杂宏定义经常使用它。
如果在一个函数中开始要分配一些资源,然后在执行过程中遇到错误则退出函数,退出前先释放资源,则我们的代码可能是这样:
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
// 执行并进行错误处理
bOk = func1();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
bOk = func2();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
// 执行成功,释放资源并返回
delete p;
p = NULL;
return true;
}
这里最大的问题是代码冗余,每增加一个操作,都需要进行相应的错误处理,非常不灵活。因此我们引入
goto
:
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
// 执行并进行错误处理
bOk = func1();
if(!bOk) goto errorhandle;
bOk = func2();
if(!bOk) goto errorhandle;
// 执行成功,释放资源并返回
delete p;
p = NULL;
return true;
errorhandle:
delete p;
p = NULL;
return false;
}
尽管消除了代码冗余,却引入了
C++
中身份比较微妙的
goto
语句。尽管正确使用
goto
可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会使程序变得不确定。那么怎么才能避免使用
goto
语句,又能消除代码冗余呢?请看
do...while(0)
:
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
do
{
// 执行并进行错误处理
bOk = func1();
if(!bOk) break;
bOk = func2();
if(!bOk) break;
}while(0);
// 释放资源
delete p;
p = NULL;
return bOk;
}
显而易见。
https://blog.csdn.net/keep_contact/article/details/127838298