【发布时间】:2023-03-11 18:33:01
【问题描述】:
我想扩展std::cout 的用法以使用我自己的控制台/cout 包装类。
理想情况下,我会有 2 个 ostream,一个用于常规打印,一个用于附加新行。
std::ostream Write;
Write << "Hello, I am " << 99 << " years old.";
打印Hello, I am 99 years old.
std::ostream WriteLine;
WriteLine << "Hello, I am " << 99 << " years old.";
打印Hello, I am 99 years old.\n(实际的新行,而不仅仅是转义)
然后我想将其扩展为具有错误流(例如Error 和ErrorLine),它们在消息之前添加前缀"ERROR: " 并以不同的颜色打印。
我知道我必须创建自己的流来添加此功能,我按照C++ cout with prefix 为std::cout 加上前缀,这几乎是我想要的,但不完全是。我不知道如何在流的末尾添加新行,而且前缀不可靠,尤其是当我要执行多个打印语句时。
我还应该提到我不想使用重载的运算符来实现这种效果,因为我希望能够以菊花链方式连接。
什么不起作用
如果我做了WriteLine >> "First"; 然后WriteLine << "Second"; 我会得到像SecondFirst\n 或Second\nFirst 这样的奇怪结果。我的理想输出是First\nSecond\n。我认为这是由于没有正确关闭/刷新/重置流,但我没有尝试让它可靠地工作。
我可以让它为单个语句工作,但是一旦我添加另一个打印语句,我尝试打印的内容就会改变顺序,后/前修复不会被添加到正确的位置,或者我最终会变成垃圾。
我不关心 wchars,因为对于单个字符,我们总是只需要一个字节。此外,我们只会在 Windows 10 上工作。
这是我目前所拥有的:
控制台.h
#include <windows.h>
#include <iostream>
#include <sstream>
#include <string>
class Console {
using Writer = std::ostream;
Console() {}
static const char newline = '\n';
class error_stream: public std::streambuf {
public:
error_stream(std::streambuf* s) : sbuf(s) {}
~error_stream() { overflow('\0'); }
private:
typedef std::basic_string<char_type> string;
int_type overflow(int_type c) {
if(traits_type::eq_int_type(traits_type::eof(), c))
return traits_type::not_eof(c);
switch(c) {
case '\n':
case '\r':
{
SetColor(ConsoleColor::Red);
prefix = "ERROR: ";
buffer += c;
if(buffer.size() > 1)
sbuf->sputn(prefix.c_str(), prefix.size());
int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
buffer.clear();
SetColor(ConsoleColor::White);
return rc;
}
default:
buffer += c;
return c;
}
}
std::string prefix;
std::streambuf* sbuf;
string buffer;
};
class write_line_stream: public std::streambuf {
public:
write_line_stream(std::streambuf* s) : sbuf(s) {}
~write_line_stream() { overflow('\0'); }
private:
typedef std::basic_string<char_type> string;
int_type overflow(int_type c) {
if(traits_type::eq_int_type(traits_type::eof(), c))
return traits_type::not_eof(c);
switch(c) {
case '\n':
case '\r':
{
buffer += c;
int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
sbuf->sputn(&newline, 1);
buffer.clear();
return rc;
}
default:
buffer += c;
return c;
}
}
std::streambuf* sbuf;
string buffer;
};
static output_stream outputStream;
static error_stream errorStream;
static write_line_stream writeLineStream;
public:
static void Setup();
static Writer Write;
static Writer WriteLine;
static Writer Err;
};
控制台.cpp
#include "Console.h"
Console::Writer Console::Write(nullptr);
Console::Writer Console::WriteLine(nullptr);
Console::Writer Console::Err(nullptr);
Console::error_stream Console::errorStream(std::cout.rdbuf());
Console::write_line_stream Console::writeLineStream(std::cout.rdbuf());
void Console::Setup() {
Write.rdbuf(std::cout.rdbuf());
Err.rdbuf(&errorStream);
WriteLine.rdbuf(&writeLineStream);
}
Main.cpp
int main() {
Console::Setup();
Console::Write << "First" << "Second";
Console::WriteLine << "Third";
Console::WriteLine << "Fourth";
Console::Write << "Fifth";
Console::Error << "Sixth";
Console::ErrorLine << "Seventh";
Console::WriteLine << "Eighth";
}
应该给出的输出
FirstSecondThird
Fourth
FifthERROR: SixthERROR: Seventh
Eighth
Press any key to continue...
感谢任何帮助和/或建议。
【问题讨论】:
-
输出总是不正确的。我可以让它为单个语句工作,但是一旦我添加了另一个打印语句,我尝试打印的东西就会改变顺序,后/前修复不会被添加到正确的位置,或者我会结束垃圾。
-
Edit您的问题,请澄清并添加更多信息。
-
你读过第 6 段吗?
-
"我知道我必须创建自己的流来添加此功能" - 实际上没有。改为编写自定义
std::streambuf类会更容易,例如从std::basic_stringbuf派生(这是std::(i|o)stringstream使用的),覆盖sync()以在将打印输出刷新到任何设备时添加任何你想要的前缀/附加你想要的(控制台,磁盘等)。然后,您可以将streambuf类的实例附加到标准std::ostream对象。 -
@anon:您可以设置
std::ios_base::unitbuf(例如,使用std::cout << std::unitbuf;)。这会导致在每次输出操作后调用刷新。虽然这会在语句末尾调用flush(),但它也会在其他地方调用它。例如,flush()将被调用 3 次std::cout << std::unitbuf << "hello " << 1 << " world\n";。另一种方法是使用在语句开头创建的临时文件,该临时文件在销毁时刷新。然而,这整个临时的玩弄有点烦人。
标签: c++ winapi cout ostream streambuf