C++ 控制台格式化打印技巧
格式化

> 
> 下次当你为控制台输出的格式而苦恼时，请参考这篇文章及其速查表。
> 
> 
> 


![](/data/attachment/album/202111/28/100548utbax4o4ttwcgyjz.jpg)


我写文章主要是为了给自己写文档。我在编程时非常健忘，所以我经常会写下有用的代码片段、特殊的特性，以及我使用的编程语言中的常见错误。这篇文章完全切合我最初的想法，因为它涵盖了从 C++ 控制台格式化打印时的常见用例。


像往常一样，这篇文章带有大量的例子。除非另有说明，代码片段中显示的所有类型和类都是 `std` 命名空间的一部分。所以当你阅读这段代码时，你必须在类型和类的前面加上`using namespace std;`。当然，该示例代码也可以在 [GitHub](https://github.com/hANSIc99/cpp_output_formatting) 上找到。


### 面向对象的流


如果你曾经用过 C++ 编程，你肯定使用过 [cout](https://en.cppreference.com/w/cpp/io/cout)。当你包含 `<iostream>` 时，[ostream](https://en.cppreference.com/w/cpp/io/basic_ostream) 类型的 `cout` 对象就进入了作用域。这篇文章的重点是 `cout`，它可以让你打印到控制台，但这里描述的一般格式化对所有 [ostream](https://en.cppreference.com/w/cpp/io/basic_ostream) 类型的流对象都有效。`ostream` 对象是 `basic_ostream` 的一个实例，其模板参数为 `char` 类型。头文件 `<iosfwd>` 是 `<iostream>` 的包含层次结构的一部分，包含了常见类型的前向声明。


类 `basic_ostream` 继承于 `basic_ios`，该类型又继承于 `ios_base`。在 [cppreference.com](https://en.cppreference.com/w/cpp/io) 上你可以找到一个显示不同类之间关系的类图。


`ios_base` 类是所有 I/O 流类的基类。`basic_ios` 类是一个模板类，它对常见的字符类型进行了<ruby> 模板特化 <rt>  specialization </rt></ruby>，称为 `ios`。因此，当你在标准 I/O 的上下文中读到 `ios` 时，它是 `basic_ios` 的 `char` 类型的模板特化。


### 格式化流


一般来说，基于 `ostream` 的流有三种格式化的方法。


1. 使用 `ios_base` 提供的格式标志。
2. 在头文件 `<iomanip>` 和 `<ios>` 中定义的流修改函数。
3. 通过调用 `<<` 操作符的 [特定重载](https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt)。


所有这些方法都有其优点和缺点，通常取决于使用哪种方法的情况。下面显示的例子混合使用所有这些方法。


#### 右对齐


默认情况下，`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;

```

#### 浮点值


如果我想打印浮点数值，我可以选择“固定”和“科学”格式。此外，我还可以指定精度：



```
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!!!!!

```

### 结语


下次你再纠结于控制台输出格式时，我希望你记得这篇文章及其 [速查表](https://opensource.com/downloads/cout-cheat-sheet)。


在 C++ 应用程序中，`cout` 是 [printf](https://opensource.com/article/20/8/printf) 的新邻居。虽然使用 `printf` 仍然有效，但我可能总是喜欢使用 `cout`。特别是与定义在 `<ios>` 中的修改函数相结合，会产生漂亮的、可读的代码。




---


via: <https://opensource.com/article/21/11/c-stdcout-cheat-sheet>


作者：[Stephan Avenwedde](https://opensource.com/users/hansic99) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
When I started writing, I did it primarily for the purpose of documenting for myself. When it comes to programming, I'm incredibly forgetful, so I began to write down useful code snippets, special characteristics, and common mistakes in the programming languages I use. This article perfectly fits the original idea as it covers common use cases of formatting when printing to console from C++.

**[Download the C++ std::cout cheat sheet]**

As usual, this article comes with a lot of examples. Unless otherwise stated, all types and classes shown in the code snippets are part of the `std`

namespace. So while you are reading this code, you have to put `using namespace std;`

in front of types and classes. Of course, the example code is also available on [GitHub](https://github.com/hANSIc99/cpp_output_formatting).


## Object-oriented stream

If you've ever programmed in C++, you've certainly already used [cout](https://en.cppreference.com/w/cpp/io/cout). The *cout* object of type [ostream](https://en.cppreference.com/w/cpp/io/basic_ostream) comes into scope when you include <iostream>. This article focuses on *cout*, which lets you print to the console but the general formatting described here is valid for all stream objects of type [ostream](https://en.cppreference.com/w/cpp/io/basic_ostream). An *ostream* object is an instance of basic_ostream with the template parameter of type char. The header <iosfwd>, which is part of the include hierarchy of <iostream>, contains forward declarations for common types.

The class *basic_ostream* inherits from basic_ios and this type, in turn, inherits from ios_base. On [cppreference.com](https://en.cppreference.com/w/cpp/io) you find a class diagram that shows the relationship between the different classes.

The class ios_base is the base class for all I/O stream classes. The class basic_ios is a template class that has a specialization for common character types called **ios**. So when you read about *ios* in the context of standard I/O, it is the char-type specialization of basic_ios.

## Formatting streams

In general, there are three ways of formatting ostream-based streams:

- Using the format flags provided by ios_base.
- Stream modifying functions defined in the header <iomanip> and <ios>.
- By invoking a
[specific overload](https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt)of the*<<*-operator.

All methods have their pros and cons, and it usually depends on the situation when which method is used. The following examples show a mixture of all methods.

### Right justified

By default, *cout* occupies as much space as the data to print requires. To allow that right-justified output to take effect, you have to define the maximum width that a line is allowed to occupy. I use the format flags to reach the goal.

The flag for right-justified output and the width adjustment only applies to the subsequent line:

```
cout.setf(ios::right, ios::adjustfield);
cout.width(50);
cout << "This text is right justified" << endl;
cout << "This text is left justified again" << endl;
```


In the above code, I configure the right-justified output using setf. I recommend you apply the bitmask *ios::adjustfield* to *setf*, which causes all flags the bitmask specifies to be reset before the actual *ios::right* flag gets set to prevent colliding combinations.

### Fill white space

When using right-justified output, the empty space is filled with blanks by default. You can change it by specifying the fill character using 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;
```


The code produces the following output:

```
...........................500 pcs
..........................3000 pcs
.........................24500 pcs
```


### Combine

Imagine your C++ program keeps track of your pantry inventory. From time to time, you want to print a list of the current stock. To do so, you could use the following formatting.

The following code is a combination of left- and right-justified output using dots as fill characters to get a nice looking list:

```
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;
```


Output:

```
Flour...............................0.70 kg
Honey..................................2 Glasses
Noodles..............................800 g
Beer..................................20 Bottles
```


## Printing values

Of course, stream-based output also offers a multitude of possibilities to output all kinds of variable types.

### Boolean

The boolalpha switch lets you convert the binary interpretation of a bool to a string:

```
cout << "Boolean output without using boolalpha: " << true << " / " << false << endl;
cout << "Boolean output using boolalpha: " << boolalpha << true << " / " << false << endl;
```


The lines above produce the following output:

```
Boolean output without using boolalpha: 1 / 0
Boolean output using boolalpha: true / false
```


### Addresses

If the value of an integer should be treated as an address, it is sufficient to cast it to *void** in order to invoke the correct overload. Here is an example:

```
unsigned long someAddress = 0x0000ABCD;
cout << "Treat as unsigned long: " << someAddress << endl;
cout << "Treat as address: " << (void*)someAddress << endl;
```


The code produces the following output:

```
Treat as unsigned long: 43981
Treat as address: 0000ABCD
```


The code prints the address with the correct length. A 32-bit executable produced the above output.

### Integers

Printing integers is straightforward. For demonstration purpose I specify the base of the number using setf and setiosflags. Applying the stream modifiers hex/oct would have the same effect:

```
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;
```


**Note:** There is no indicator for the used base by default, but you can add one using showbase.

```
Decimal: 123
Hexadecimal: 7b
Octal: 173
```


### Padding with zeros

```
0000003
0000035
0000357
0003579
```


You can get an output like the above by specifying the width and the fill character:

```
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;
```


### Floating-point values

If I want to print floating-point values, I can choose between the *fixed*- and *scientific*-format. Additionally, I can specify the precision.

```
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;
```


The code above produces the following output:

```
Default precision: 1234.12
Modified precision: 1234.1235
Modified precision & scientific format: 1.2341e+03
Default precision & fixed format: 1234.12
```


### Time and Money

With put_money, you can print currency units in the correct, locale-dependent formatting. This requires that your console can output UTF-8 charset. Note that the variable *specialOffering* stores the monetary value in cents:

```
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;
```


The imbue-method of *ios* lets you specify a locale. With the command `locale -a`

, you can get a list of all available locale identifiers on your system.

```
$99.95
99,950€
99,950₽
```


*(For whatever reason, it prints euro and ruble with three decimal places on my system, which looks strange for me, but maybe this is the official formatting.)*

The same principle applies to time output. The function put_time lets you print the time in the corresponding locale format. Additionally, you can specify which parts of a time object get printed.

```
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;
```


The format specifier *%c* causes to print a standard date and time string:

```
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
```


## Creating custom stream modifiers

You can also create your own stream. The following code inserts a predefined string when applied to an *ostream* object:

```
ostream& myManipulator(ostream& os) {
string myStr = ">>>Here I am<<<";
os << myStr;
return os;
}
```


**Another example:** If you have something important to say, like most people on the internet, you could use the following code to insert exclamation marks after your message depending on the level of importance. The level of importance gets passed as an argument:

```
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;
}
```


Both modifiers can now be simply passed to *cout*:

```
cout << "My custom manipulator: " << myManipulator << endl;
cout << "I have something important to say" << importance(5) << endl;
```


Producing the following output:

```
My custom manipulator: >>>Here I am<<<
I have something important to say!!!!!
```


## Conclusion

Next time you struggle with console output formatting, I hope you remember this article or the related [cheat sheet](https://opensource.com/downloads/cout-cheat-sheet).

In C++ applications, *cout* is the new neighbor of [printf](https://opensource.com/article/20/8/printf). While using *printf* is still valid, I would probably always prefer using *cout*. Especially the combination with the modifying function defined in *<ios>* results in nice, readable code.

## Comments are closed.