相关知识请看上一篇 五分钟c++(8)iostream 标准库
在 C++ 的 iostream 库中,每一个流对象(如 cin, cout)内部都维护着一组格式化标志。这些标志本质上是一个位掩码(bitmask),其中的每一个“位”都像一个开关,控制着输入输出的某个特定方面,例如整数的进制、浮点数的表示法、对齐方式等。
你可以通过调用流对象的成员函数来设置、清除或查询这些标志。
主要的成员函数
ios_base::flags():fmtflags flags() const;: 返回当前所有格式标志的位掩码。fmtflags flags(fmtflags fmtfl);: 用fmtfl替换当前所有的格式标志,并返回之前的标志。这通常用于保存旧状态,设置新状态,之后再恢复旧状态。
ios_base::setf(): (set flag)fmtflags setf(fmtflags fmtfl);: 打开(设置)fmtfl中指定的标志位,不影响其他标志。返回之前的标志。fmtflags setf(fmtflags fmtfl, fmtflags mask);: 先清除mask中指定的标志位,然后再打开fmtfl中指定的标志。这对于互斥的标志组(如进制、对齐方式)特别有用。
ios_base::unsetf(): (unset flag)void unsetf(fmtflags mask);: 关闭(清除)mask中指定的标志位。
常见的格式化标志
这些标志定义在 std::ios_base 类中。为了方便,我们通常使用 std::ios。
| 类别 | 标志 (Flag) | 所属标志组 | 描述 |
|---|---|---|---|
| 对齐 | left | adjustfield | 左对齐,在字段末尾填充 |
right | adjustfield | 右对齐(默认),在字段开头填充 | |
internal | adjustfield | 内部对齐,在符号或进制前缀后、数字前填充 | |
| 进制 | dec | basefield | 十进制(默认) |
oct | basefield | 八进制 | |
hex | basefield | 十六进制 | |
| 浮点数 | fixed | floatfield | 使用定点表示法(例如 123.45) |
scientific | floatfield | 使用科学记数法(例如 1.2345e+02) | |
| 其他 | boolalpha | 将 bool 值输出为 “true” 或 “false” | |
showbase | 输出整数时显示进制前缀(0 for oct, 0x for hex) | ||
showpoint | 对于浮点数,总是显示小数点和末尾的零 | ||
showpos | 对于正数,显示 + 号 | ||
skipws | (输入时) 跳过前导空白字符(默认) | ||
unitbuf | 每次输出后刷新缓冲区 | ||
uppercase | 十六进制或科学记数法中的字母使用大写(0XFF, 1.23E+02) |
代码示例
https://www.programiz.com/online-compiler/8X1ZAh9WSQtet
#include <iostream>
#include <iomanip> // 尽管这里主要用flags,但对比时会用到
int main() {
int num = 255;
double pi = 3.14;
std::cout << "--- 默认输出 ---" << std::endl;
std::cout << num << std::endl;
std::cout << pi << std::endl;
std::cout << true << std::endl;
std::cout << "\n--- 使用 Formatting Flags ---" << std::endl;
// 1. 保存当前状态
std::ios_base::fmtflags original_flags = std::cout.flags();
// 2. 设置新标志
// 设置为十六进制,大写,显示进制前缀
std::cout.setf(std::ios::hex | std::ios::uppercase | std::ios::showbase);
std::cout << "设置 hex, uppercase, showbase: " << num << std::endl; // 输出: 0XFF
// 清除 uppercase 标志
std::cout.unsetf(std::ios::uppercase);
std::cout << "清除 uppercase: " << num << std::endl; // 输出: 0xff
// 使用 setf 的第二种形式来改变进制(这是一个互斥组)
// 先清除 basefield 组的所有标志,再设置 dec 标志
std::cout.setf(std::ios::dec, std::ios::basefield);
std::cout << "切换回 dec: " << num << std::endl; // 输出: 255
// 设置 boolalpha
std::cout.setf(std::ios::boolalpha);
std::cout << "设置 boolalpha: " << true << std::endl; // 输出: true
// 3. 恢复原始状态
std::cout.flags(original_flags);
std::cout << "\n--- 恢复默认输出 ---" << std::endl;
std::cout << num << std::endl;
std::cout << true << std::endl;
return 0;
}
这个程序会输出
--- 默认输出 ---
255
3.14
1
--- 使用 Formatting Flags ---
设置 hex, uppercase, showbase: 255
清除 uppercase: 255
切换回 dec: 255
设置 boolalpha: true
--- 恢复默认输出 ---
255
1
=== Code Execution Successful ===
什么时候用格式化标志
比起流操作符(Stream Manipulators)(这个我在下一篇介绍),如果我们想在程序的某个大部分阶段内,都遵循一种统一的格式时,在开头用 flags 设置一次是最佳选择。因为这个库的效果是持续的,所以使用Flags能让代码更加简洁和易读。
这样我们只需要在函数开始的时候设置flag,在函数结束的时候在把flag的状态还原。这样做的好处是不会在使用流的中间用操作符改状态,这样函数的意图更加明显。比如下边的例子
https://www.programiz.com/online-compiler/5bPOzgnWtxD34
// Online C++ compiler to run C++ program online
#include <iostream>
#include <vector>
#include <iomanip>
void print_data_dump(const std::vector<int>& data);
void print_data_dump_hex(const std::vector<int>& data);
int main() {
// Write C++ code here
std::vector v = {20,30,40,50};
print_data_dump(v);
print_data_dump_hex(v);
print_data_dump(v);
return 0;
}
void print_data_dump(const std::vector<int>& data) {
for (int value : data) {
// 循环体内代码很干净,无需重复设置格式
std::cout << std::setw(2) << value << " ";
}
std::cout << std::endl;
}
// 场景:输出一个十六进制数据Dump
void print_data_dump_hex(const std::vector<int>& data) {
// 在函数入口处设置一次风格
std::ios_base::fmtflags original_flags = std::cout.flags();
std::cout.setf(std::ios::hex | std::ios::uppercase, std::ios::basefield);
std::cout.fill('0');
print_data_dump(data);
// 函数结束前恢复,不影响其他代码
std::cout.flags(original_flags);
}
这个程序会输出
20 30 40 50
14 1e 28 32
20 30 40 50
=== Code Execution Successful ===
可以看出,flag的效果会只限于 print_data_dump_hex 内,不会对外部的程序造成影响。
1万+

被折叠的 条评论
为什么被折叠?



