下次当你为控制台输出的格式而苦恼时,请参考这篇文章及其速查表。
我写文章主要是为了给自己写文档。我在编程时非常健忘,所以我经常会写下有用的代码片段、特殊的特性,以及我使用的编程语言中的常见错误。这篇文章完全切合我最初的想法,因为它涵盖了从 C++ 控制台格式化打印时的常见用例。
像往常一样,这篇文章带有大量的例子。除非另有说明,代码片段中显示的所有类型和类都是 std
命名空间的一部分。所以当你阅读这段代码时,你必须在类型和类的前面加上using namespace std;
。当然,该示例代码也可以在 GitHub 上找到。
面向对象的流 如果你曾经用过 C++ 编程,你肯定使用过 cout 。当你包含 <iostream>
时,ostream 类型的 cout
对象就进入了作用域。这篇文章的重点是 cout
,它可以让你打印到控制台,但这里描述的一般格式化对所有 ostream 类型的流对象都有效。ostream
对象是 basic_ostream
的一个实例,其模板参数为 char
类型。头文件 <iosfwd>
是 <iostream>
的包含层次结构的一部分,包含了常见类型的前向声明。
类 basic_ostream
继承于 basic_ios
,该类型又继承于 ios_base
。在 cppreference.com 上你可以找到一个显示不同类之间关系的类图。
ios_base
类是所有 I/O 流类的基类。basic_ios
类是一个模板类,它对常见的字符类型进行了 模板特化 specialization ,称为 ios
。因此,当你在标准 I/O 的上下文中读到 ios
时,它是 basic_ios
的 char
类型的模板特化。
格式化流 一般来说,基于 ostream
的流有三种格式化的方法。
使用 ios_base
提供的格式标志。
在头文件 <iomanip>
和 <ios>
中定义的流修改函数。
通过调用 <<
操作符的 特定重载 。
所有这些方法都有其优点和缺点,通常取决于使用哪种方法的情况。下面显示的例子混合使用所有这些方法。
右对齐 默认情况下,cout
占用的空间与要打印的数据所需的空间一样大。为了使这种右对齐的输出生效,你必须定义一个行允许占用的最大宽度。我使用格式标志来达到这个目的。
右对齐输出的标志和宽度调整只适用于其后的行。
1 2 3 4 5 cout.setf (ios::right, ios::adjustfield); cout.width (50 ); cout << "This text is right justified" << endl; cout << "This text is left justified again" << endl;
在上面的代码中,我使用 setf
配置了右对齐的输出。我建议你将位掩码 ios::adjustfield
应用于 setf
,这将使位掩码指定的所有标志在实际的 ios::right
标志被设置之前被重置,以防止发生组合碰撞。
填充空白 当使用右对齐输出时,默认情况下,空的地方会用空白字符填充。你可以通过使用 setfill
指定填充字符来改变它:
1 2 3 4 cout << right << setfill ('.') << setw (30 ) << 500 << " pcs" << endl; cout << right << setfill ('.') << setw (30 ) << 3000 << " pcs" << endl; cout << right << setfill ('.') << setw (30 ) << 24500 << " pcs" << endl;
代码输出如下:
1 2 3 4 ... ... ... ... ... ... ... ... ... 500 pcs... ... ... ... ... ... ... ... ..3000 pcs... ... ... ... ... ... ... ... .24500 pcs
组合 想象一下,你的 C++ 程序记录了你的储藏室库存。不时地,你想打印一份当前库存的清单。要做到这一点,你可以使用以下格式。
下面的代码是左对齐和右对齐输出的组合,使用点作为填充字符,可以得到一个漂亮的列表:
1 2 3 4 5 cout << left << setfill ('.') << setw (20 ) << "Flour" << right << setfill ('.') << setw (20 ) << 0.7 << " kg" << endl; cout << left << setfill ('.') << setw (20 ) << "Honey" << right << setfill ('.') << setw (20 ) << 2 << " Glasses" << endl; cout << left << setfill ('.') << setw (20 ) << "Noodles" << right << setfill ('.') << setw (20 ) << 800 << " g " << endl; cout << left << setfill ('.') << setw (20 ) << "Beer" << right << setfill ('.') << setw (20 ) << 20 << " Bottles" << endl;
输出:
1 2 3 4 5 Flour... ... ... ... ... ... ... ... ... ... .0 .70 kg Honey... ... ... ... ... ... ... ... ... ... ... .2 Glasses Noodles... ... ... ... ... ... ... ... ... ... 800 g Beer... ... ... ... ... ... ... ... ... ... ... .20 Bottles
打印数值 当然,基于流的输出也能让你输出各种变量类型。
布尔型 boolalpha
开关可以让你把布尔型的二进制解释转换为字符串:
1 2 cout << "Boolean output without using boolalpha: " << true << " / " << false << endl;cout << "Boolean output using boolalpha: " << boolalpha << true << " / " << false << endl;
以上几行产生的输出结果如下:
1 2 3 Boolean output without using boolalpha: 1 / 0 Boolean output using boolalpha: true / false
地址 如果一个整数的值应该被看作是一个地址,那么只需要把它投到 void*
就可以了,以便调用正确的重载。下面是一个例子:
1 2 3 unsigned long someAddress = 0 x0000ABCD cout << "Treat as unsigned long: " << someAddress << endl cout << "Treat as address: " << (void*)someAddress << endl
该代码产生了以下输出:
1 2 3 Treat as unsigned long : 43981 Treat as address: 0000 ABCD
该代码打印出了具有正确长度的地址。一个 32 位的可执行文件产生了上述输出。
整数 打印整数是很简单的。为了演示,我使用 setf
和 setiosflags
来指定数字的基数。应用流修改器 hex
/oct
也有同样的效果。
1 2 3 4 5 6 7 8 9 int myInt = 123 ; cout << "Decimal: " << myInt << endl; cout.setf (ios::hex , ios::basefield ); cout << "Hexadecimal: " << myInt << endl; cout << "Octal: " << resetiosflags (ios::basefield ) << setiosflags (ios::oct ) << myInt << endl;
注意: 默认情况下,没有指示所使用的基数,但你可以使用 showbase
添加一个。
1 2 3 4 Decimal : 123 Hexadecimal : 7 bOctal : 173
用零填充 1 2 3 4 5 0000003 0000035 0000357 0003579
你可以通过指定宽度和填充字符得到类似上述的输出:
1 2 3 4 5 cout << setfill ('0 ') << setw (7 ) << 3 << endl; cout << setfill ('0 ') << setw (7 ) << 35 << endl; cout << setfill ('0 ') << setw (7 ) << 357 << endl; cout << setfill ('0 ') << setw (7 ) << 3579 << endl;
浮点值 如果我想打印浮点数值,我可以选择“固定”和“科学”格式。此外,我还可以指定精度:
1 2 3 4 5 6 7 8 9 10 11 12 13 double myFloat = 1234.123456789012345 ;int defaultPrecision = cout.precision (); cout << "Default precision: " << myFloat << endl; cout.precision (4 ); cout << "Modified precision: " << myFloat << endl; cout.setf (ios::scientific , ios::floatfield ); cout << "Modified precision & scientific format: " << myFloat << endl; cout.precision (defaultPrecision); cout.setf (ios::fixed , ios::floatfield ); cout << "Default precision & fixed format: " << myFloat << endl;
上面的代码产生以下输出:
1 2 3 4 5 Default precision: 1234 .12 Modified precision: 1234 .1235 Modified precision & scientific format: 1 .2341 e+03 Default precision & fixed format: 1234 .12
时间和金钱 通过 put_money
,你可以用正确的、与当地有关的格式来打印货币单位。这需要你的控制台能够输出 UTF-8 字符集。请注意,变量 specialOffering
以美分为单位存储货币价值。
1 2 3 4 5 6 7 8 9 long double specialOffering = 9995 cout.imbue(locale("en_US.UTF-8" )) cout << showbase << put_money(specialOffering) << endl cout.imbue(locale("de_DE.UTF-8" )) cout << showbase << put_money(specialOffering) << endl cout.imbue(locale("ru_RU.UTF-8" )) cout << showbase << put_money(specialOffering) << endl
ios
的 imbue
方法让你指定一个地区。通过命令 locale -a
,你可以得到你系统中所有可用的地区标识符的列表。
(不知道出于什么原因,在我的系统上,它打印的欧元和卢布有三个小数位,对我来说看起来很奇怪,但这也许是官方的格式。)
同样的原则也适用于时间输出。函数 put_time
可以让你以相应的地区格式打印时间。此外,你可以指定时间对象的哪些部分被打印出来。
1 2 3 4 5 6 7 8 9 10 11 time_t now = time (nullptr); tm localtm = cout.imbue(locale("en_US.UTF-8" )); cout << "en_US : " << put_time (&localtm , "%c" ) << endl; cout.imbue(locale("de_DE.UTF-8" )); cout << "de_DE : " << put_time (&localtm , "%c" ) << endl; cout.imbue(locale("ru_RU.UTF-8" )); cout << "ru_RU : " << put_time (&localtm , "%c" ) << endl;
格式指定符 %c
会打印一个标准的日期和时间字符串:
1 2 3 4 en_US : Tue 02 Nov 2021 07 :36 :36 AM CETde_DE : Di 02 Nov 2021 07 :36 :36 CETru_RU : Вт 02 ноя 2021 07 :36 :36
创建自定义的流修改器 你也可以创建你自己的流。下面的代码在应用于 ostream
对象时插入了一个预定义的字符串:
1 2 3 4 5 6 ostream& myManipulator(ostream& os ) { string myStr = ">>>Here I am<<<" ; os << myStr; return os ; }
另一个例子: 如果你有重要的事情要说,就像互联网上的大多数人一样,你可以使用下面的代码在你的信息后面根据重要程度插入感叹号。重要程度被作为一个参数传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct T_Importance { int levelOfSignificance; };T_Importance importance (int lvl) { T_Importance x = {.levelOfSignificance = lvl }; return x; } ostream& operator <<(ostream& __os, T_Importance t){ for (int i = 0 ; i < t.levelOfSignificance; ++i){ __os.put ('!' ); } return __os; }
这两个修饰符现在都可以简单地传递给 cout
:
1 2 3 4 cout << "My custom manipulator: " << myManipulator << endl ; cout << "I have something important to say" << importance (5 ) << endl ;
产生以下输出:
1 2 3 4 My custom manipulator: >>>Here I am<<< I have something important to say!!!!!
结语 下次你再纠结于控制台输出格式时,我希望你记得这篇文章及其 速查表 。
在 C++ 应用程序中,cout
是 printf 的新邻居。虽然使用 printf
仍然有效,但我可能总是喜欢使用 cout
。特别是与定义在 <ios>
中的修改函数相结合,会产生漂亮的、可读的代码。
via: https://opensource.com/article/21/11/c-stdcout-cheat-sheet
作者:Stephan Avenwedde 选题:lujun9972 译者:wxy 校对:wxy
本文由 LCTT 原创编译,Linux中国 荣誉推出