在写代码时,不可避免需要打印提示、警告、错误等信息,且要灵活控制打印信息的级别。另外,还有可能需要使用宏来控制代码段(主要是调试代码段)是否执行。为此,本文提供一种调试宏定义方案,包括打印字符串信息log1
宏和格式化打印log2
宏,且能通过宏控制代码段执行。完整代码如下:
#ifndef __debug_h__
#define __debug_h__
#include
#include
#include
// 定义日志级别枚举
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
在main文件进行宏定义测试,需要定义全局日志级别,以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");
)
}
2.1 #和##
两者都是预处理运算符
- #是字符串化运算符,将其后的宏参数转换为用双括号括起来的字符串。
- ##是符号连接运算符,用于连接两个标记(标记不一定是宏变量,可以是标识符、关键字、数字、字符串、运算符)为一个标记。
在第一章中使用#把日志级别变量转为字符串,##的作用是在可变参数为0是,删除前面的逗号,只输出字符串。
2.2 do while(0)
do while常用来做循环,而while参数为0,表示这样的代码肯定不是做循环用的,它有什么用呢?
- 辅助定义复杂宏,避免宏替换出错
假如你定义一个这样宏,本意是调用dosomething
时执行两个函数。
#define dosomething() \
func1(); \
func2();
但在类似如下使用宏的代码,宏展开时func2
无视判断条件都会执行。
if (0 < a)
dosomething();
// 宏展开后
if (0 < a)
func1();
func2();
优化一下,用{}
包裹宏是否可行呢?如下:
#define dosomething() { \
func1(); \
func2();}
由于我们写代码习惯在语句后加分号,你可能会有如下的展开后编译错误。
if(0 < a)
dosomething();
else
...
// 宏展开后
if(0 < a)
{
func1();
func2();
}; // 错误处
else
...
而do while (0)则能避免这些错误,所以复杂宏定义经常使用它。
- 消除分支语句或者goto语句,提高代码的易读性
如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:
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;
}
- 使用代码块,代码块内定义变量,不用考虑变量重复问题
显而易见。