HOWTO · C++

在 C++ 中打印布尔值

本文讨论了在 C++ 中打印布尔值的文本表示。

本页内容

默认情况下,C++ 会把布尔值打印为 1(表示 true)和 0(表示 false)。对机器来说这没有问题,但当你在浏览日志或调试输出时,一个孤零零的 1 或 0 很容易被误读,而 true 和 false 能直接告诉读者程序想表达什么。

本文介绍在 C++ 中以文本形式打印布尔值的三种方法:配合 printf 使用条件表达式、使用 std::boolalpha 流操纵符,以及通过自定义 std::numpunct facet 输出 true 和 false 以外的文字。

为什么 C++ 把布尔值打印为 0 和 1

std::cout 使用与格式化整数相同的数值机制来格式化 bool。除非你更改流的格式化标志,否则 true 会被写成 1,false 会被写成 0。printf 系列函数的行为也类似:bool 在通过 ... 传递时会被提升为 int,所以 %d 同样打印 1 或 0。

数值输出虽然紧凑,但单个数字很容易淹没在大量数字和日志行之中。把 true 和 false 这样的单词打印出来能让程序状态一目了然,这也是下面每种方法都以可读文本为目标的原因。

用 printf 和条件表达式打印布尔文本

printf 没有布尔格式说明符,而且实参会被提升为 int,所以 %d 只能产生 1 或 0。标准的变通方法是把说明符换成 %s,再用条件(三元)表达式自己选择文本:value ? "true" : "false"。

下面的程序用两种方式打印同一个值:

本节中的代码以及显示的标签或输出仍为英文。

#include <cstdio>

int main() {
  bool ready = true;

  std::printf("numeric: %d\n", ready);
  std::printf("text: %s\n", ready ? "true" : "false");
  std::printf("text: %s\n", false ? "true" : "false");

  return 0;
}
numeric: 1
text: true
text: false

这种方法在任何使用 printf 的地方都有效,也不依赖流状态;但在大型源文件中,每个调用点都重复一次条件表达式会增加噪音,而且没有任何机制保证各次调用选用的字符串保持一致。

在 C++ 流中使用 std::boolalpha

std::boolalpha 是一个流操纵符,它设置流的布尔格式化标志,使 bool 值打印为 true 和 false,而不是 1 和 0。它对输入流和输出流都有效,声明在 <ios> 中,而 <iostream> 已经包含了它。

实际使用中有两个特性需要注意:

  • 该标志会持续生效。一旦插入 std::boolalpha,之后插入该流的每个 bool 都会以文本形式打印,直到你把它改回来。
  • std::noboolalpha 是与之配对的操纵符,用于恢复数值表示。

下面的程序展示了这两种状态:

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>

int main() {
  std::cout << "default: " << true << " / " << false << "\n";

  std::cout << std::boolalpha;
  std::cout << "boolalpha: " << true << " / " << false << "\n";

  std::cout << std::noboolalpha;
  std::cout << "restored: " << true << " / " << false << "\n";

  return 0;
}
default: 1 / 0
boolalpha: true / false
restored: 1 / 0

由于该标志在流上持续生效,请记住它会影响之后打印的每个 bool——当后续输出需要保持数值形式时,插入 std::noboolalpha。还要注意,std::boolalpha 只输出语言环境为这两个值提供的名称,默认是 true 和 false。如果需要不同的文字,就要使用下一节介绍的 facet 方法。

用 std::numpunct facet 自定义布尔文本

流为 true 和 false 使用的文字来自流的语言环境中的 std::numpunct facet。流的输出操作经由 std::numput,后者通过该 facet 的 do_truename() 和 do_falsename() 成员获取布尔名称。派生你自己的 facet,并通过对流调用 imbue 注入包含它的 locale,就能在 std::boolalpha 生效时替换这些名称。

下面的程序安装了一个提供自定义文字的 facet:

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <locale>
#include <string>

class named_bool : public std::numpunct<char> {
 protected:
  std::string do_truename() const override { return "Hai"; }
  std::string do_falsename() const override { return "NAI"; }
};

int main() {
  std::cout.imbue(std::locale(std::locale(), new named_bool));
  std::cout << std::boolalpha;

  std::cout << "custom true: " << true << "\n";
  std::cout << "custom false: " << false << "\n";

  return 0;
}
custom true: Hai
custom false: NAI

字符串 Hai 和 NAI 是 facet 自身提供的字面量——没有任何内容会被自动翻译;流只是原样打印 do_truename() 和 do_falsename() 返回的结果。new named_bool 的分配也不会造成泄漏:facet 对象是引用计数的,当最后一个引用它的 std::locale 离开作用域时就会被销毁。

这种方法只在设置了 std::boolalpha 时才生效;没有它,流仍然会打印 1 和 0。它也是三种方法中机制最重的一种——需要一个派生类、两个重写和一次 imbue 调用——所以只在确实需要自定义文字时才使用它。

选择合适的方法

  • 在 C 风格代码中处理一两个值时,配合 value ? "true" : "false" 使用 printf 是改动最小的方案,但它会在每个调用点重复选择逻辑。
  • 对于 iostream 输出,std::boolalpha 是符合惯用法的解决方案;当后续 bool 输出需要恢复为 1/0 时,搭配 std::noboolalpha 使用。
  • 当文字本身必须改变时——比如改成大写的 True/False,或 Hai/NAI 这样的自定义词对——std::numpunct facet 加上注入的 locale 是三种方法中唯一的选择,因为它重新定义了 std::boolalpha 打印的内容。

选择能满足你格式化需求的最简单工具:单个值用条件表达式,流输出用 std::boolalpha,只有需要自定义布尔名称时才使用 facet。