如何在 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++ 不会将其截断,而是会把后面的列继续向右推。因此,客户姓名这类长度不固定的字段更适合放在最后一列。
备注:内容仅供参考。
