标签 格式化 下的文章

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

我写文章主要是为了给自己写文档。我在编程时非常健忘,所以我经常会写下有用的代码片段、特殊的特性,以及我使用的编程语言中的常见错误。这篇文章完全切合我最初的想法,因为它涵盖了从 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_ioschar 类型的模板特化。

格式化流

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

  1. 使用 ios_base 提供的格式标志。
  2. 在头文件 <iomanip><ios> 中定义的流修改函数。
  3. 通过调用 << 操作符的 特定重载

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

右对齐

默认情况下,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 位的可执行文件产生了上述输出。

整数

打印整数是很简单的。为了演示,我使用 setfsetiosflags 来指定数字的基数。应用流修改器 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;

iosimbue 方法让你指定一个地区。通过命令 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++ 应用程序中,coutprintf 的新邻居。虽然使用 printf 仍然有效,但我可能总是喜欢使用 cout。特别是与定义在 <ios> 中的修改函数相结合,会产生漂亮的、可读的代码。


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

作者:Stephan Avenwedde 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

有时你会发现需要格式化某个文本文件中的内容。比如,该文本文件每行一个单词,而任务是把所有的单词都放在同一行。当然,你可以手工来做,但没人喜欢手工做这么耗时的工作。而且,这只是一个例子 - 事实上的任务可能千奇百怪。

好在,有一个命令可以满足至少一部分的文本格式化的需求。这个工具就是 fmt。本教程将会讨论 fmt 的基本用法以及它提供的一些主要功能。文中所有的命令和指令都在 Ubuntu 16.04LTS 下经过了测试。

Linux fmt 命令

fmt 命令是一个简单的文本格式化工具,任何人都能在命令行下运行它。它的基本语法为:

fmt [-WIDTH] [OPTION]... [FILE]...

它的 man 页是这么说的:

重新格式化文件中的每一个段落,将结果写到标准输出。选项 -WIDTH--width=DIGITS 形式的缩写。

下面这些问答方式的例子应该能让你对 fmt 的用法有很好的了解。

Q1、如何使用 fmt 来将文本内容格式成同一行?

使用 fmt 命令的基本形式(省略任何选项)就能做到这一点。你只需要将文件名作为参数传递给它。

fmt [file-name]

下面截屏是命令的执行结果:

format contents of file in single line

你可以看到文件中多行内容都被格式化成同一行了。请注意,这并不会修改原文件(file1)。

Q2、如何修改最大行宽?

默认情况下,fmt 命令产生的输出中的最大行宽为 75。然而,如果你想的话,可以用 -w 选项进行修改,它接受一个表示新行宽的数字作为参数值。

fmt -w [n] [file-name]

下面这个例子把行宽削减到了 20:

change maximum line width

Q3、如何让 fmt 突出显示第一行?

这是通过让第一行的缩进与众不同来实现的,你可以使用 -t 选项来实现。

fmt -t [file-name]

make fmt highlight the first line

Q4、如何使用 fmt 拆分长行?

fmt 命令也能用来对长行进行拆分,你可以使用 -s 选项来应用该功能。

fmt -s [file-name]

下面是一个例子:

make fmt split long lines

Q5、如何在单词与单词之间,句子之间用空格分开?

fmt 命令提供了一个 -u 选项,这会在单词与单词之间用单个空格分开,句子之间用两个空格分开。你可以这样用:

fmt -u [file-name]

注意,在我们的案例中,这个功能是默认开启的。

总结

没错,fmt 提供的功能不多,但不代表它的应用就不广泛。因为你永远不知道什么时候会用到它。在本教程中,我们已经讲解了 fmt 提供的主要选项。若想了解更多细节,请查看该工具的 man 页


via: https://www.howtoforge.com/linux-fmt-command/

作者:Himanshu Arora 译者:lujun9972 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出