在操作之后恢复 std: : cout 的状态

假设我有这样一个代码:

void printHex(std::ostream& x){
x<<std::hex<<123;
}
..
int main(){
std::cout<<100; // prints 100 base 10
printHex(std::cout); //prints 123 in hex
std::cout<<73; //problem! prints 73 in hex..
}

我的问题是,是否有任何方法可以“恢复”的状态的 cout的原来的一个从函数返回?(有点像 std::boolalphastd::noboolalpha。.)?

谢谢。

55647 次浏览

提升 IO 流状态保护程序看起来正是你所需要的。 : -)

基于代码片段的示例:

void printHex(std::ostream& x) {
boost::io::ios_flags_saver ifs(x);
x << std::hex << 123;
}

你需要 #include <iostream>#include <ios>,然后在需要时:

std::ios_base::fmtflags f( cout.flags() );


//Your code here...


cout.flags( f );

您可以将它们放在函数的开头和结尾,或者查看 这个答案了解如何在 拉尔中使用它。

稍作修改,使输出更具可读性:

void printHex(std::ostream& x) {
ios::fmtflags f(x.flags());
x << std::hex << 123 << "\n";
x.flags(f);
}


int main() {
std::cout << 100 << "\n"; // prints 100 base 10
printHex(std::cout);      // prints 123 in hex
std::cout << 73 << "\n";  // problem! prints 73 in hex..
}

我使用这个答案中的示例代码创建了一个 RAII 类。如果在 iostream 上设置标志的函数有多个返回路径,那么这种技术的最大优势就来了。无论使用哪个返回路径,总是会调用析构函数,标志总是会被重置。当函数返回时,不会忘记恢复标志。

class IosFlagSaver {
public:
explicit IosFlagSaver(std::ostream& _ios):
ios(_ios),
f(_ios.flags()) {
}
~IosFlagSaver() {
ios.flags(f);
}


IosFlagSaver(const IosFlagSaver &rhs) = delete;
IosFlagSaver& operator= (const IosFlagSaver& rhs) = delete;


private:
std::ostream& ios;
std::ios::fmtflags f;
};

然后,当您想要保存当前标志状态时,可以通过创建 IosFlagSaver 的本地实例来使用它。当此实例超出范围时,将还原标志状态。

void f(int i) {
IosFlagSaver iosfs(std::cout);


std::cout << i << " " << std::hex << i << " ";
if (i < 100) {
std::cout << std::endl;
return;
}
std::cout << std::oct << i << std::endl;
}

注意,这里给出的答案不会恢复 std::cout的完整状态。例如,即使在调用 .flags()之后,std::setfill也会“粘住”。一个更好的解决方案是使用 .copyfmt:

std::ios oldState(nullptr);
oldState.copyfmt(std::cout);


std::cout
<< std::hex
<< std::setw(8)
<< std::setfill('0')
<< 0xDECEA5ED
<< std::endl;


std::cout.copyfmt(oldState);


std::cout
<< std::setw(15)
<< std::left
<< "case closed"
<< std::endl;

将印刷:

case closed

而不是:

case closed0000

您可以围绕 stdout 缓冲区创建另一个包装器:

#include <iostream>
#include <iomanip>
int main() {
int x = 76;
std::ostream hexcout (std::cout.rdbuf());
hexcout << std::hex;
std::cout << x << "\n"; // still "76"
hexcout << x << "\n";   // "4c"
}

在一个函数中:

void print(std::ostream& os) {
std::ostream copy (os.rdbuf());
copy << std::hex;
copy << 123;
}

当然,如果性能是一个问题,那么开销会更大一些,因为它复制的是整个 ios对象(但不是缓冲区) ,其中包括一些您花钱购买但不太可能使用的内容,比如语言环境。

否则,我觉得如果您要使用 .flags(),最好是一致的,并且也使用 .setf(),而不是使用 <<语法(纯粹的风格问题)。

void print(std::ostream& os) {
std::ios::fmtflags os_flags (os.flags());
os.setf(std::ios::hex);
os << 123;
os.flags(os_flags);
}

正如其他人所说的那样,为了方便起见,你可以把上面提到的(还有 .precision().fill(),但是通常不会把通常不会被修改而且更重的语言环境和词语相关的东西)放到一个类中,使它变得异常安全; 构造函数应该接受 std::ios&

我想概括一下 qbert220的答案:

#include <ios>


class IoStreamFlagsRestorer
{
public:
IoStreamFlagsRestorer(std::ios_base & ioStream)
: ioStream_(ioStream)
, flags_(ioStream_.flags())
{
}


~IoStreamFlagsRestorer()
{
ioStream_.flags(flags_);
}


private:
std::ios_base & ioStream_;
std::ios_base::fmtflags const flags_;
};

这应该对输入流和其他流也适用。

PS: 我本想把这个简单的评论作为上面的回答,但是堆栈溢出不允许我这样做,因为缺少声誉。因此,让我在这里的答案杂乱无章,而不是一个简单的评论..。

在大多数情况下,C + + 20 std::format将是保存还原的优越选择

一旦你能够使用它,你将能够简单地写出十六进制:

#include <format>
#include <string>


int main() {
std::cout << std::format("{:x} {:#x} {}\n", 16, 17, 18);
}

预期产出:

10 0x11 18

因此,这将完全克服修改 std::cout状态的疯狂。

现有的 fmt库在获得官方支持之前实现了它:

sudo apt install libfmt-dev

修改源代码以替换:

  • <format><fmt/core.h>
  • std::format呼叫 fmt::format

Main.cpp

#include <iostream>


#include <fmt/core.h>


int main() {
std::cout << fmt::format("{:x} {:#x} {}\n", 16, 17, 18);
}

编译并运行:

g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out

产出:

10 0x11 18

相关阅读: 字符串格式,如 sprintf

<<方式采用 setfunsetf可能是一种更清晰的解决方案,而不是将格式注入到 cout 中。

void printHex(std::ostream& x){
x.setf(std::ios::hex, std::ios::basefield);
x << 123;
x.unsetf(std::ios::basefield);
}

IOS _ base 名称空间也可以正常工作

void printHex(std::ostream& x){
x.setf(std::ios_base::hex, std::ios_base::basefield);
x << 123;
x.unsetf(std::ios_base::basefield);
}

参考资料: http://www.cplusplus.com/reference/ios/ios_base/setf/