PAYATHON 2026

如何在 cout 输出中补零并保持表格对齐?

社区小冯

结论

可以用 std::setw 控制列宽。std::setfill('0') 只用于格式化电表读数,输出读数后要立即把填充字符恢复为空格。电表编号最好用 std::string 读取,否则转换成整数时会丢失输入中的前导零。

还需要注意以下几点:

  • std::setw(n) 只影响紧接着输出的一个字段。
  • std::setfill() 会持续生效,需要手动恢复。
  • setw 设置的是最小宽度,不能代替列分隔符。列与列之间仍要添加明确的空格。
  • 金额应配合 std::fixed 和 std::setprecision(2) 输出。

推荐实现

假设输入顺序为”上次读数、当前读数、客户姓名”,记录可以这样定义:

#include <iomanip>
#include <iostream>
#include <string>

struct BillingRecord
{
    std::string previousReading;
    std::string currentReading;
    std::string customerName;
    int kwhUsed;
    double charge;
};

表头和数据可以按下面的方式输出:

void printRecord(const BillingRecord& record)
{
    std::cout
        << "               $"
        << std::right
        << std::setw(9)
        << std::fixed
        << std::setprecision(2)
        << record.charge

        << std::setw(10)
        << record.kwhUsed

        << "  "
        << std::setfill('0')
        << std::setw(8)
        << record.currentReading

        << "  "
        << std::setw(8)
        << record.previousReading

        << std::setfill(' ')
        << "  "
        << std::left
        << record.customerName
        << '\n';
}

完整示例:

#include <iomanip>
#include <iostream>
#include <string>

struct BillingRecord
{
    std::string previousReading;
    std::string currentReading;
    std::string customerName;
    int kwhUsed;
    double charge;
};

void printRecord(const BillingRecord& record)
{
    std::cout
        << "               $"
        << std::right
        << std::setw(9)
        << std::fixed
        << std::setprecision(2)
        << record.charge
        << std::setw(10)
        << record.kwhUsed
        << "  "
        << std::setfill('0')
        << std::setw(8)
        << record.currentReading
        << "  "
        << std::setw(8)
        << record.previousReading
        << std::setfill(' ')
        << "  "
        << std::left
        << record.customerName
        << '\n';
}

int main()
{
    std::cout
        << "                  ===================\n"
        << "                  UTILITY BILLING LOG\n"
        << "                  SPRING 2017\n"
        << "                  ===================\n\n"
        << "                                       METER READINGS\n"
        << "                  CHARGE   KWH USED   CURRENT  PREVIOUS  CUSTOMER NAME\n"
        << "               ==========  ========  ========  ========  ===================\n";

    BillingRecord first{
        "04304450",
        "04305150",
        "Edward L Jones",
        700,
        163.00
    };

    BillingRecord second{
        "01235431",
        "01236581",
        "Meryl Petersen",
        1150,
        260.30
    };

    printRecord(first);
    printRecord(second);
}

输出结果如下:

                  ===================
                  UTILITY BILLING LOG
                  SPRING 2017
                  ===================

                                       METER READINGS
                  CHARGE   KWH USED   CURRENT  PREVIOUS  CUSTOMER NAME
               ==========  ========  ========  ========  ===================
               $   163.00       700  04305150  04304450  Edward L Jones
               $   260.30      1150  01236581  01235431  Meryl Petersen

为什么原输出会粘连

这类问题通常是由下面的写法引起的:

std::cout << std::setfill('0');

std::setfill('0') 不只对下一项有效。它会影响后续所有使用 std::setw 的输出,直到程序再次调用 std::setfill()。这样一来,原本用于表格缩进和列宽的空格也会被替换成零:

000000000000000$000240.75...

应当把零填充限制在需要补零的字段上:

std::cout << std::setfill('0')
          << std::setw(8) << reading
          << std::setfill(' ');

std::setw 的行为不同,它只控制紧接着的一个输出项:

std::cout << std::setw(8) << current
          << std::setw(8) << previous;

这两个字段都需要设置宽度,所以要分别调用一次 std::setw(8)。

前导零应该怎样保留

电表读数最好按字符串读取:

std::string previous;
std::string current;

std::cin >> previous >> current;

这样可以原样保留输入的 04304450。

如果按整数读取:

int reading;
std::cin >> reading;

得到的数值会变成 4304450。虽然输出时可以把它补成固定的 8 位:

std::cout << std::setfill('0')
          << std::setw(8)
          << reading
          << std::setfill(' ');

但这种方式只有在读数始终为 8 位时才可靠。电表读数更接近固定格式的标识或显示值,因此通常更适合使用 std::string。

读取带空格的客户姓名

客户姓名中可能包含空格,不能直接这样读取:

std::cin >> customerName;

这种写法只能读到姓名的第一个单词。读取两个电表字段后,可以改用 std::getline:

std::cin >> previousReading >> currentReading;
std::getline(std::cin >> std::ws, customerName);

std::ws 会先清除读数后残留的空白字符,再由 std::getline 读入完整姓名。

注意事项

std::fixed、std::setprecision()、std::setfill()、std::left 和 std::right 都可能影响后续输出。某个字段使用特殊格式后,应根据下一列的格式要求恢复相应状态:

std::cout << std::setfill('0')
          << std::setw(8) << meterReading
          << std::setfill(' ')
          << std::right;

std::setw 设置的只是最小宽度。如果内容超过指定宽度,C++ 不会将其截断,而是会把后面的列继续向右推。因此,客户姓名这类长度不固定的字段更适合放在最后一列。

备注:内容仅供参考。