格式化输出控制
定义与作用
格式化输出控制通过操纵符(manipulators)和格式标志调整流的输出格式——对齐、精度、进制、填充字符等。
#include <iomanip>
std::cout << std::setw(10) << std::setfill('0') << std::hex << 255;
// 输出: "00000000ff"
核心原理
格式标志体系
完整示例
示例一:飞翔科技财务报告
场景说明:高英用格式化输出为飞翔科技生成财务报告。
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
struct Revenue {
std::string dept;
double q1;
double q2;
double q3;
double q4;
};
int main() {
std::vector<Revenue> report = {
{"技术部", 1888.88, 1666.66, 2320.00, 1888.88},
{"产品部", 1234.56, 1456.78, 1678.90, 1890.12},
{"运营部", 888.88, 999.99, 1111.11, 1234.56},
{"设计部", 520.00, 666.66, 777.77, 888.88},
};
std::cout << "=== 飞翔科技 2026 年季度营收报告 ===\n\n";
// 表头
std::cout << std::left
<< std::setw(12) << "部门"
<< std::right
<< std::setw(12) << "Q1(万元)"
<< std::setw(12) << "Q2(万元)"
<< std::setw(12) << "Q3(万元)"
<< std::setw(12) << "Q4(万元)"
<< std::setw(14) << "合计(万元)"
<< "\n";
std::cout << std::setfill('=') << std::setw(74) << "" << "\n";
std::cout << std::setfill(' ');
// 数据行
double totals[4] = {0};
for (const auto& r : report) {
double sum = r.q1 + r.q2 + r.q3 + r.q4;
std::cout << std::left << std::setw(12) << r.dept
<< std::right << std::fixed << std::setprecision(2)
<< std::setw(12) << r.q1
<< std::setw(12) << r.q2
<< std::setw(12) << r.q3
<< std::setw(12) << r.q4
<< std::setw(14) << sum
<< "\n";
totals[0] += r.q1;
totals[1] += r.q2;
totals[2] += r.q3;
totals[3] += r.q4;
}
// 合计行
std::cout << std::setfill('-') << std::setw(74) << "" << "\n";
std::cout << std::setfill(' ');
double grandTotal = totals[0] + totals[1] + totals[2] + totals[3];
std::cout << std::left << std::setw(12) << "合计"
<< std::right << std::fixed << std::setprecision(2)
<< std::setw(12) << totals[0]
<< std::setw(12) << totals[1]
<< std::setw(12) << totals[2]
<< std::setw(12) << totals[3]
<< std::setw(14) << grandTotal
<< "\n";
// 年增长率
std::cout << "\n=== 营收指标 ===\n";
std::cout << "年度总营收: " << std::setw(10) << grandTotal << " 万元\n";
std::cout << "季度平均值: " << std::setw(10) << std::setprecision(2)
<< (grandTotal / 4) << " 万元\n";
// 数字格式展示
std::cout << "\n=== 数字格式展示 ===\n";
int flags = 0xFF; // 权限位掩码
std::cout << "权限位(十进制): " << std::dec << flags << "\n";
std::cout << "权限位(十六进制): " << std::hex << std::showbase
<< flags << "\n";
std::cout << "权限位(八进制): " << std::oct << flags << "\n";
std::cout << std::dec; // 恢复十进制
std::cout << "科学计数法: " << std::scientific << grandTotal << "\n";
std::cout << std::fixed; // 恢复定点
// 布尔输出
std::cout << "\n=== 系统状态 ===\n";
std::cout << std::boolalpha
<< " 数据库连接: " << true << "\n"
<< " 缓存服务: " << true << "\n"
<< " 消息队列: " << false << "\n"
<< std::noboolalpha;
}
预期输出:
=== 飞翔科技 2026 年季度营收报告 ===
部门 Q1(万元) Q2(万元) Q3(万元) Q4(万元) 合计(万元)
==========================================================================
技术部 1888.88 1666.66 2320.00 1888.88 7764.42
产品部 1234.56 1456.78 1678.90 1890.12 6260.36
运营部 888.88 999.99 1111.11 1234.56 4234.54
设计部 520.00 666.66 777.77 888.88 2853.31
--------------------------------------------------------------------------
合计 4532.32 4790.09 5887.78 5902.44 21112.63
=== 营收指标 ===
年度总营收: 21112.63 万元
季度平均值: 5278.16 万元
=== 数字格式展示 ===
权限位(十进制): 255
权限位(十六进制): 0xff
权限位(八进制): 0377
科学计数法: 2.111263e+04
=== 系统状态 ===
数据库连接: true
缓存服务: true
消息队列: false
逐段分析:
setw设置输出宽度(仅对下一次输出生效,需重复设置)setprecision+fixed控制小数点后位数left/right控制对齐方向hex/oct/dec切换进制;showbase添加0x/0前缀scientific切换科学计数法;需手动恢复到 fixed 或 defaultfloatboolalpha输出true/false而非1/0
示例二:自定义 operator<<
场景说明:小崔为飞翔科技的 IP 地址结构实现自定义输出。
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <cstdint>
struct IPv4 {
uint8_t a, b, c, d;
IPv4(uint8_t a_, uint8_t b_, uint8_t c_, uint8_t d_)
: a(a_), b(b_), c(c_), d(d_) {}
// 自定义 operator<< 作为友元
friend std::ostream& operator<<(std::ostream& os, const IPv4& ip) {
return os << static_cast<int>(ip.a) << '.'
<< static_cast<int>(ip.b) << '.'
<< static_cast<int>(ip.c) << '.'
<< static_cast<int>(ip.d);
}
};
struct Server {
std::string hostname;
IPv4 ip;
double load;
std::string status;
friend std::ostream& operator<<(std::ostream& os, const Server& s) {
// 保存原始格式标志
auto flags = os.flags();
auto fill = os.fill();
os << std::left
<< std::setw(20) << s.hostname
<< std::setw(18) << s.ip
<< std::right
<< std::setw(8) << std::fixed << std::setprecision(1)
<< s.load << "%"
<< " "
<< std::left << std::setw(8) << s.status;
// 恢复格式
os.flags(flags);
os.fill(fill);
return os;
}
};
int main() {
std::vector<Server> servers = {
{"飞翔-Web-01", IPv4(192, 168, 1, 101), 45.2, "在线"},
{"飞翔-Web-02", IPv4(192, 168, 1, 102), 62.8, "在线"},
{"飞翔-DB-Master", IPv4(10, 0, 0, 1), 78.5, "在线"},
{"飞翔-DB-Slave", IPv4(10, 0, 0, 2), 12.3, "在线"},
{"飞翔-Cache-01", IPv4(172, 16, 0, 1), 91.0, "告警!"},
};
std::cout << "=== 飞翔科技服务器状态面板 ===\n\n";
std::cout << std::left
<< std::setw(20) << "主机名"
<< std::setw(18) << "IP 地址"
<< std::setw(12) << "CPU 负载"
<< "状态\n";
std::cout << std::setfill('-') << std::setw(58) << "" << "\n";
std::cout << std::setfill(' ');
for (const auto& s : servers)
std::cout << s << "\n";
std::cout << std::setfill('-') << std::setw(58) << "" << "\n";
std::cout << "共 " << servers.size() << " 台服务器\n";
}
预期输出:
=== 飞翔科技服务器状态面板 ===
主机名 IP 地址 CPU 负载 状态
----------------------------------------------------------
飞翔-Web-01 192.168.1.101 45.2% 在线
飞翔-Web-02 192.168.1.102 62.8% 在线
飞翔-DB-Master 10.0.0.1 78.5% 在线
飞翔-DB-Slave 10.0.0.2 12.3% 在线
飞翔-Cache-01 172.16.0.1 91.0% 告警!
----------------------------------------------------------
共 5 台服务器
逐段分析:
- 自定义
operator<<可输出任意复杂结构 - 保存/恢复
flags()和fill()是良好习惯,避免污染调用者的格式 static_cast<int>(ip.a):uint8_t会被视为char类型直接输出字符,需转换friend声明使operator<<可访问私有成员
易错场景与面试考点
易错场景
1. setw 仅对一次输出生效
std::cout << std::setw(10) << "A" << "B" << "\n";
// ^^^^^^^^^^^^^^^^
// 只对 "A" 生效,"B" 不受影响
// 每次都需要 setw
std::cout << std::setw(10) << "A" << std::setw(10) << "B" << "\n";
2. hex/oct 粘性设置
std::cout << std::hex << 255 << " " << 100 << "\n";
// 输出: "ff 64" —— 所有后续数字都是十六进制!
std::cout << std::dec; // 必须显式恢复
3. uint8_t 输出问题
uint8_t val = 65;
std::cout << val << "\n"; // 输出 'A' 而不是 65!
// 解决:static_cast<int>(val)
面试考点
| 考点 | 要点 |
|---|---|
| setw vs width | setw 每调一次生效一字段,width() 返回当前宽度 |
| 粘性 vs 非粘性 | setw 非粘性(单次),其他大多粘性(持续) |
| setprecision | 配合 fixed 为小数位数,否则为有效数字位数 |
| boolalpha | true/false 字符串 vs 1/0 |
| showbase | hex → 0x, oct → 0 |
| internal 对齐 | 符号靠左、数值靠右、中间填充 |
| 自定义 operator<< | 返回 ostream& 支持链式调用 |
| 格式恢复 | 修改格式后应保存/恢复 flags() |