C++打印cout <<时的对齐方式

77

使用std::cout打印文本时,有没有一种方法可以对齐文本?我正在使用制表符进行对齐,但是当单词太长时,它们不再对齐。

Sales Report for September 15, 2010
Artist  Title   Price   Genre   Disc    Sale    Tax Cash
Merle   Blue    12.99   Country 4%  12.47   1.01    13.48
Richard Music   8.49    Classical   8%  7.81    0.66    8.47
Paula   Shut    8.49    Classical   8%  7.81    0.72    8.49

2
以下响应允许指定列的宽度。请注意,这需要您知道一个上限(例如数据库约束)或预先计算它(这可能意味着在实际开始打印之前解析整个结构)。第二种方法虽然必要,但当然较慢 :) - Matthieu M.
7个回答

146

用ISO C++标准的方法是#include <iomanip>并使用io操作符,如std::setw。然而,这些io操作符即使对于文本也很麻烦,并且对于格式化数字几乎无法使用(我假设您希望美元金额在小数点上对齐,具有正确数量的有效数字等)。即使只是针对普通文本标签,该代码的第一部分看起来也是这样:

// using standard iomanip facilities
cout << setw(20) << "Artist"
     << setw(20) << "Title"
     << setw(8) << "Price";
// ... not going to try to write the numeric formatting...

如果您能够使用Boost库,那么请使用Boost.Format库,无需犹豫。它完全兼容标准IO流,并为您提供了易于格式化printf / Posix格式字符串的所有良好特性,但不会失去iostream本身的强大便利性。例如,您的前两行的第一部分将类似于:

// using Boost.Format
cout << format("%-20s %-20s %-8s\n")  % "Artist" % "Title" % "Price";
cout << format("%-20s %-20s %8.2f\n") % "Merle" % "Blue" % 12.99;

3
Boost.Format 看起来很不错。它提供了类似 printf 的直观格式化方式,同时保证了类型安全。 - dreamlax
7
位置格式也很好,因为这意味着您可以从本地化文件中提取它们。 - Donal Fellows

15

使列对齐的另一种方法如下:

using namespace std;

cout.width(20); cout << left << "Artist";
cout.width(20); cout << left << "Title";
cout.width(10); cout << left << "Price";
...
cout.width(20); cout << left << artist;
cout.width(20); cout << left << title;
cout.width(10); cout << left << price;

我们应该估计每列值的最大长度。在这种情况下,“Artist”列的值不应超过20个字符,以此类推。


12

你需要使用IO操作符。 其中,setw 是一个很好的选择,下面是引用页面上的一个示例:

// setw example
#include <iostream>
#include <iomanip>
using namespace std;

int main () {
  cout << setw (10);
  cout << 77 << endl;
  return 0;
}

使用leftright操作符可以将字段左对齐或右对齐。

另外,查看一下setfill。这里有一个更详细的关于使用io操作符来格式化C++输出的教程:格式化C++输出的io操作符教程


11

另请参见:在C++代码中应该使用哪个C I/O库?

struct Item
{
   std::string     artist;
   std::string     c;
   integer         price;  // in cents (as floating point is not acurate)
   std::string     Genre;
   integer         disc;
   integer         sale;
   integer         tax;
};

std::cout << "Sales Report for September 15, 2010\n"
          << "Artist  Title   Price   Genre   Disc    Sale    Tax Cash\n";
FOREACH(Item loop,data)
{
    fprintf(stdout,"%8s%8s%8.2f%7s%1s%8.2f%8.2f\n",
          , loop.artist
          , loop.title
          , loop.price / 100.0
          , loop.Genre
          , loop.disc , "%"
          , loop.sale / 100.0
          , loop.tax / 100.0);

   // or

    std::cout << std::setw(8) << loop.artist
              << std::setw(8) << loop.title
              << std::setw(8) << fixed << setprecision(2) << loop.price / 100.0
              << std::setw(8) << loop.Genre
              << std::setw(7) << loop.disc << std::setw(1) << "%"
              << std::setw(8) << fixed << setprecision(2) << loop.sale / 100.0
              << std::setw(8) << fixed << setprecision(2) << loop.tax / 100.0
              << "\n";

    // or

    std::cout << boost::format("%8s%8s%8.2f%7s%1s%8.2f%8.2f\n")
              % loop.artist
              % loop.title
              % loop.price / 100.0
              % loop.Genre
              % loop.disc % "%"
              % loop.sale / 100.0
              % loop.tax / 100.0;
}

4
当您发出第一行时,
Artist  Title   Price   Genre   Disc    Sale    Tax Cash

要实现“对齐”,必须事先知道每个列需要多宽(否则,无法对齐)。一旦你知道了每个列所需的宽度(根据数据来源不同,有几种可能的方法来实现这一点),那么在另一个答案中提到的 setw 函数将会有所帮助,或者(更粗暴地说;-)你可以发出经过精心计算的额外空格数(笨重,当然),等等。我不建议使用制表符,因为你无法控制最终输出设备如何呈现它们。
回到核心问题,如果你有每个列的值在某种vector中,例如,你可以进行第一次格式化处理以确定列的最大宽度,当然还要考虑到列头的宽度。
如果你的行是“逐一”到来的,并且对齐很重要,你必须在它们到来时缓存或缓冲行(如果它们适合内存,否则在磁盘文件上,“倒回”并从头开始重新读取),并注意保持更新“每个列的最大宽度向量”,因为行确实到来了。如果保持对齐很重要,你不能输出任何东西(甚至不能输出标题!)直到你看到了最后一行(除非你以某种神奇的方式已经知道了列的宽度)。

4

C++20 std::format选项<, ^, 和 >

根据https://en.cppreference.com/w/cpp/utility/format/formatter#Standard_format_specification,应该遵循以下规定:

#include <format>

// left: "42    "
std::cout << std::format("{:<6}", 42);

// right: "    42"
std::cout << std::format("{:>6}", 42);

// center: "  42  "

std::cout << std::format("{:^6}", 42);

现有的fmt库在官方支持之前已经实现了:https://github.com/fmtlib/fmt 在Ubuntu 22.04上安装:
sudo apt install libfmt-dev

修改源代码以实现以下替换:

  • <format> 替换为 <fmt/core.h>
  • std::format 替换为 fmt::format

main.cpp

#include <iostream>

#include <fmt/core.h>

int main() {
    std::cout << "123456.\n";
    // left
    std::cout << fmt::format("{:<6}.\n", 42);
    // right
    std::cout << fmt::format("{:>6}.\n", 42);
    // center
    std::cout << fmt::format("{:^6}.\n", 42);
}

使用以下命令进行编译和运行:

g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out

输出:

123456.
42    .
    42.
  42  .

更多信息请参考:std::string格式化,类似于sprintf


0

最简单的方法,不使用“复杂”的函数,是手动格式化:

一个例子:

  std::cout << std::setprecision(10)
            << "integral of sin(x) from x = 0 to x = pi" << '\n'
            << "approximate: " << integrate(a, b, nbins) << '\n'
            << "exact:       " << 2.0                    << '\n';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接