学堂在线《面向对象程序设计》编程题:按调试级别输出调试信息

题目要求:
在程序中输出一些信息,是一种很有效的调试方法。请设计实现一个名为Log的类,能方便输出调试信息,要求满足如下附件所写的要求:
在这里插入图片描述


关键点:
利用重载流运算符、重写属于自定义类(Log)的endl流运算符

解题思路:
依题目要求已知类Log有成员函数set_level、全局函数level和运算符重载函数。等级比较可以转化成int型数据,故这里的level建议使用int类型。
先判断有无调用全局函数set_level(“message”)

  1. 没有调用,则调用重载流运算符函数operator<<() 输出后面的信息,此功能对应第一条支持使用流运算符输出信息.

  2. 调用过,则首先储存自定义的信息(即记录operator<<后的字符串,如题目最后一行的 “BROKEN” ),暂时不输出,并在最后重载流运算符输出endl时满足对应情况输出自定义的信息。此功能对应调试信息的分级控制,注意到有全局等级和每一次调试的当前等级,故又分两种情况:

    ①全局等级>当前信息等级,则直跳过不输出任何信息(返回自身)
    ②全局等级<=当前信息等级,则输出在定义该信息时传入的调试string字符串

Tip:全局等级为调用成员函数set_level()设置的等级.


代码(运行环境VS2019):

#include <iostream>
#include <string>
using namespace std;

int level(char* gloStr);    //全局成员函数(输出时保证为三种定义的异常字符串中的一种)

class Log {
private:
	char* str = 0;       //用于保存本类的错误信息
	string message;       //若允许,则可以修改当前的错误信息,用于在重载endl时输出
	bool hasSet = false; //已经调用set_level()全局函数,调用则修改为true,方便后续输出信息
	int globLevel;       //全局等级,只能用set_level修改,与currentLelel对比用
	int currentLevel;    //当前等级,用于level,只能用level修改
public:
	//Log();             //构造函数
	int set_level(const char* str);    //用于设置全局信息
	Log& operator<< (int levelNum);    //根据当前的错误等级输出,若调用则默认已设置全局函数
	Log& operator<< (const char* midStr);    //传入临时的字符串,若是对应的错误信息则输出
									   //该重载函数分已调用set_level和未调用set_level两种情况
	//分别对应判断(不输出)和输出信息

	//重载endl函数
	//typedef ostream& (*__omanip)(ostream&);
	//ostream& operator<<(__omanip func);
	Log& operator<<(ostream& (*op)(ostream&));
};

//-------------------------------------------------------------------------------------------

Log& Log::operator<<(ostream& (*op)(ostream&)) {
	//当全局函数未调用时,则直接返回endl换行
	if (!hasSet) {
		cout << endl;
		return *this;
	}
	else {
		if (this->currentLevel >= this->globLevel) {
			cout << this->message << endl;
		}
		else {
			return *this;  //注意返回的类型
		}
	}
}

int level(const char* Str) {
	string str = Str;
	if (str.compare("warning") == 0)
		return 1;

	if (str.compare("error") == 0)
		return 2;

	if (str.compare("fatal") == 0)
		return 3;
}

int Log::set_level(const char* setStr) { //1 2 3分别对应warning、error、fatal
	string str1 = setStr;
	this->hasSet = true;

	if (str1.compare("warning") == 0) {
		this->globLevel = 1;
		return 1;
	}
	if (str1.compare("error") == 0) {
		this->globLevel = 2;
		return 2;
	}
	if (str1.compare("fatal") == 0) {
		this->globLevel = 3;
		return 3;
	}
}

Log& Log::operator<<(int levelNum) {  //调用此函数时已经储存类属性num的信息
	this->currentLevel = levelNum;
	return *this;
}

Log& Log::operator<<(const char* str) { //返回地址
	//1.已经调用set_level的情况
	if (this->hasSet) {
		this->message = str;           //先传入,最后在重载endl再判断是否输出,先修改
	}
	//2.未调用set_level的情况:类似cout输出
	else {
		cout << str;
		return *this;                 //返回自身
	}
}

int main() {
	Log obj;
	obj << "debug message" << endl;

	//注意理解流操作符并不等同于直接输出信息,也可以用来保存信息,要正确理解本质
	obj.set_level("warning");
	obj << level("warning") << "WARNING MESSAGE" << endl;
	obj << level("fatal") << "BROKEN" << endl;

	return 0;
}

运行结果:
在这里插入图片描述


当主函数改为如图:
在这里插入图片描述

此时的运行结果:
在这里插入图片描述
Tip:在调用重载流运算符函数时要理解这个函数的返回类型是什么.


总结:
受operator<<在ostream类中作为成员函数重载的启发:

ostream& ostream::operator<<(ostream& (*op)(ostream&))
{
	return (*op)(*this);
}

我们可以自己定义一个属于自己的endl重载函数,即把返回类型改为Log,并修改实现方法即可:

Log& Log::operator<<(ostream& (*op)(ostream&)) {…}

如果转到endl的定义,会发现endl并不是换行符\n,而是一个函数模板:

template <class _Elem, class _Traits>
basic_ostream<_Elem, _Traits>& __CLRCALL_OR_CDECL endl(
    basic_ostream<_Elem, _Traits>& _Ostr) { // insert newline and flush stream
    _Ostr.put(_Ostr.widen('\n'));
    _Ostr.flush();
    return _Ostr;
}

参考文章(强烈推荐阅读):
C++中endl的本质是什么


题外话

在逛论坛时也发现一个就如何重载endl换行符讨论的帖子,链接,其中有一位大佬的回复:

如下例子定义ClassA的<<操作:
typedef ostream& (*__omanip)(ostream&) ;
ostream& operator<<(_omanip func) {(*func)(*this); return *this;}

经尝试将上述内容替代Log& Log::operator<<(ostream& (*op)(ostream&));函数体部分,也不会出现编译错误。但本人在目前学习阶段不了解该如何使用该方法实现。

如果本文中有用词不当处,或者您有更好的解决方法,欢迎大家在评论区批评指正和一起交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cloud Stream

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值