久趣下载站

当前位置: 首页 » 游戏攻略 » 调试宏以及测试

调试宏以及测试

在编写代码时,通常会需要打印提示、警告、错误等信息,并且需要灵活控制打印信息的级别。此外,有时可能需要使用宏来控制代码段(主要是调试代码段)是否执行。本文将介绍一种调试宏定义方案,包括打印字符串信息的

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");
    )
}

宏定义小细节

2.1 #和##

在宏中,#和##都是预处理运算符。

  • #是字符串化运算符,将其后的宏参数转换为用双引号括起来的字符串。
  • ##是符号连接运算符,用于连接两个标记(标记不一定是宏变量,可以是标识符、关键字、数字、字符串、运算符)为一个标记。

在第一章中使用#将日志级别变量转换为字符串,而##的作用是在可变参数为0时,删除前面的逗号,只输出字符串。

2.2 do while(0)

do while通常用于循环结构,而while参数为0时,表示此代码肯定不是用于循环,那么它有什么用呢?


  1. 辅助定义复杂宏,避免宏替换出错

假设你定义一个这样的宏,本意是调用

DOSOMETHING

时执行两个函数。

#define DOSOMETHING() \
    func1(); \
    func2();

但在如下代码中使用宏时,宏展开后

func2

无论判断条件如何都会执行。

if (0 < a)
    DOSOMETHING();
// 宏展开后
if (0 < a)
    func1();
func2();

优化一下,用

{}

包裹宏是否可行呢?如下:

#define DOSOMETHING() { \
    func1(); \
    func2();}

由于我们写代码习惯在语句后加分号,宏展开后可能会出现编译错误。



do while (0)

则能避免这些错误,因此复杂宏定义经常使用它。


  1. 消除分支语句或者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;
}

  1. 使用代码块,代码块内定义变量,不用考虑变量重复问题

显而易见。

参考博文

https://blog.csdn.net/keep_contact/article/details/127838298

猜你喜欢
本类排行