题目要求:
在程序中输出一些信息,是一种很有效的调试方法。请设计实现一个名为Log的类,能方便输出调试信息,要求满足如下附件所写的要求:
关键点:
利用重载流运算符、重写属于自定义类(Log)的endl流运算符
解题思路:
依题目要求已知类Log有成员函数set_level、全局函数level和运算符重载函数。等级比较可以转化成int型数据,故这里的level建议使用int类型。
先判断有无调用全局函数set_level(“message”)
-
没有调用,则调用重载流运算符函数operator<<() 输出后面的信息,此功能对应第一条支持使用流运算符输出信息.
-
调用过,则首先储存自定义的信息(即记录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&));函数体部分,也不会出现编译错误。但本人在目前学习阶段不了解该如何使用该方法实现。
如果本文中有用词不当处,或者您有更好的解决方法,欢迎大家在评论区批评指正和一起交流。