当我开始写作时,我主要是为了给自己做文档。在编程方面,我非常健忘,所以我开始记下有用的代码片段、特殊特性以及我使用的编程语言中的常见错误。本文完美地契合了最初的想法,因为它涵盖了从 C++ 打印到控制台时格式化的常见用例。
[下载 C++ std::cout 速查表]
像往常一样,本文附带了许多示例。除非另有说明,否则代码片段中显示的所有类型和类都是 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 是一个模板类,它具有针对称为 ios 的常用字符类型的特化。因此,当您在标准 I/O 的上下文中阅读有关 ios 的内容时,它是 basic_ios 的 char 类型特化。
格式化流
通常,有三种格式化基于 ostream 的流的方法
- 使用 ios_base 提供的格式标志。
- 标头 <iomanip> 和 <ios> 中定义的流修改函数。
- 通过调用 << 运算符的 特定重载。
所有方法都有其优点和缺点,通常取决于使用哪种方法的情况。以下示例显示了所有方法的混合使用。
右对齐
默认情况下,cout 占用打印数据所需的空间。为了使右对齐输出生效,您必须定义一行允许占用的最大宽度。我使用格式标志来实现目标。
右对齐输出的标志和宽度调整仅适用于后续行
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 指定填充字符来更改它
cout << right << setfill('.') << setw(30) << 500 << " pcs" << endl;
cout << right << setfill('.') << setw(30) << 3000 << " pcs" << endl;
cout << right << setfill('.') << setw(30) << 24500 << " pcs" << endl;
代码产生以下输出
...........................500 pcs
..........................3000 pcs
.........................24500 pcs
组合
想象一下,您的 C++ 程序跟踪您的食品储藏室库存。您时不时地想打印当前库存清单。为此,您可以使用以下格式。
以下代码是左对齐和右对齐输出的组合,使用点作为填充字符以获得美观的列表
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;
输出
Flour...............................0.70 kg
Honey..................................2 Glasses
Noodles..............................800 g
Beer..................................20 Bottles
打印值
当然,基于流的输出还提供了多种可能性来输出各种变量类型。
布尔值
boolalpha 开关允许您将布尔值的二进制解释转换为字符串
cout << "Boolean output without using boolalpha: " << true << " / " << false << endl;
cout << "Boolean output using boolalpha: " << boolalpha << true << " / " << false << endl;
上面的行产生以下输出
Boolean output without using boolalpha: 1 / 0
Boolean output using boolalpha: true / false
地址
如果整数的值应被视为地址,则将其强制转换为 void* 以调用正确的重载就足够了。这是一个例子
unsigned long someAddress = 0x0000ABCD;
cout << "Treat as unsigned long: " << someAddress << endl;
cout << "Treat as address: " << (void*)someAddress << endl;
代码产生以下输出
Treat as unsigned long: 43981
Treat as address: 0000ABCD
代码以正确的长度打印地址。32 位可执行文件产生上述输出。
整数
打印整数很简单。出于演示目的,我使用 setf 和 setiosflags 指定数字的基数。应用流修饰符 hex/oct 将具有相同的效果
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 添加一个。
Decimal: 123
Hexadecimal: 7b
Octal: 173
用零填充
0000003
0000035
0000357
0003579
您可以通过指定宽度和填充字符来获得如上所示的输出
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;
浮点值
如果我想打印浮点值,我可以在 fixed 和 scientific 格式之间进行选择。此外,我可以指定精度。
double myFloat = 1234.123456789012345;
int defaultPrecision = cout.precision(); // == 2
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;
/* back to default */
cout.precision(defaultPrecision);
cout.setf(ios::fixed, ios::floatfield);
cout << "Default precision & fixed format: " << myFloat << endl;
上面的代码产生以下输出
Default precision: 1234.12
Modified precision: 1234.1235
Modified precision & scientific format: 1.2341e+03
Default precision & fixed format: 1234.12
时间和金钱
使用 put_money,您可以以正确的、依赖于区域设置的格式打印货币单位。这要求您的控制台可以输出 UTF-8 字符集。请注意,变量 specialOffering 以美分存储货币价值
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
,您可以获得系统上所有可用区域设置标识符的列表。
$99.95
99,950€
99,950₽
(出于某种原因,它在我的系统上以三位小数位打印欧元和卢布,这对我来说看起来很奇怪,但这可能是官方格式。)
相同的原则适用于时间输出。函数 put_time 允许您以相应的区域设置格式打印时间。此外,您可以指定要打印的时间对象的哪些部分。
time_t now = time(nullptr);
tm localtm = *localtime(&now);
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 导致打印标准日期和时间字符串
en_US : Tue 02 Nov 2021 07:36:36 AM CET
de_DE : Di 02 Nov 2021 07:36:36 CET
ru_RU : Вт 02 ноя 2021 07:36:36
创建自定义流修饰符
您还可以创建自己的流。以下代码在应用于 ostream 对象时插入预定义的字符串
ostream& myManipulator(ostream& os) {
string myStr = ">>>Here I am<<<";
os << myStr;
return os;
}
另一个例子: 如果您有重要的事情要说,就像互联网上的大多数人一样,您可以使用以下代码在您的消息后插入感叹号,具体取决于重要性级别。重要性级别作为参数传递
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
cout << "My custom manipulator: " << myManipulator << endl;
cout << "I have something important to say" << importance(5) << endl;
产生以下输出
My custom manipulator: >>>Here I am<<<
I have something important to say!!!!!
结论
下次您在控制台输出格式化方面遇到困难时,我希望您能记住本文或相关的 速查表。
在 C++ 应用程序中,cout 是 printf 的新邻居。虽然使用 printf 仍然有效,但我可能总是更喜欢使用 cout。特别是与 <ios> 中定义的修改函数结合使用,可以产生美观、可读的代码。
评论已关闭。