第11.3章: 操纵器
什么是操纵器?
操纵器是一个小辅助对象,它包住一个(或几个)值,告诉 print / println / scan 如何格式化或解析那个值。它不用 "%x" 或 std::hex 那样的格式串,而是把格式化表达在类型系统里:操纵器是一个有自己类型的对象,因此编译器可以检查它并把它完全内联掉。
所有操纵器都在 ::fast_io::manipulators 命名空间里,它的便捷简写是 ::fast_io::mnp。大多数程序只需加一句:
using namespace ::fast_io::mnp;
然后就能直接使用操纵器。下面每个示例都假定有这句 using 声明。
数制:hex、oct、bin、dec
最常见的操纵器改变打印或扫描整数所用的数制。hex、oct、bin、dec 产生小写数字。它们的大写同胞 hexupper / octupper / binupper 也存在。用于扫描时,使用带 _get 的变体:hex_get、oct_get、bin_get、dec_get。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
::std::size_t n{0xBEEFz};
// Output
println("hex : ", hex(n));
println("oct : ", oct(n));
println("bin : ", bin(n));
println("dec : ", dec(n));
println("hexup : ", hexupper(n));
// Input: read a and b as hexadecimal
::std::size_t a{}, b{};
scan(hex_get(a), hex_get(b));
println("sum = ", hex(a + b));
}
hex0x — 带 0x 前缀的十六进制
hex0x 用小写十六进制加常见的 0x 前缀打印整数。要大写数字和 0X 就用 hex0xupper。当你要产出人能读懂、并能再抄回源码的输出时,它们最合适。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
::std::size_t flags{0xCAFEz};
// prints: flags = 0xcafe
println("flags = ", hex0x(flags));
// prints: flags = 0XCAFE
println("flags = ", hex0xupper(flags));
}
boolalpha
默认情况下 bool 打印成 1 或 0。boolalpha 操纵器则把它打印成 true / false(或通过模板参数指定大写 TRUE / FALSE)。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
bool ok{true};
println("raw : ", ok); // 1
println("alpha : ", boolalpha(ok)); // true
println("alpha up : ", boolalpha<true>(ok)); // TRUE
}
浮点格式:fixed、scientific、general
三个操纵器控制浮点输出所用的记法:
fixed(x)— 普通十进制记法,例如1234.5678。scientific(x)— 科学计数法,例如1.2345678e3。general(x)— 让库选择更短的那种形式。
每个都有一个 comma_ 同胞(comma_fixed、comma_scientific、comma_general),会插入千位分隔符。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
double pi{3.14159265358979};
double big{1234567.875};
println("fixed : ", fixed(pi));
println("scientific : ", scientific(pi));
println("general : ", general(pi));
// With thousands separators
println("comma_fixed : ", comma_fixed(big));
}
comma_decimal — 千位分隔符
当你只想要逗号(或其他分组分隔符)而不改变浮点记法时,该用的就是 comma_* 家族。comma_decimal 是最常用的 — 它用普通十进制记法格式化数字并插入分组分隔符。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
::std::size_t population{8110700zu};
// prints something like: 8,110,700
println("people: ", comma_decimal(population));
}
对齐:left、right、middle
left、right 和 middle 把值补齐到给定的字段宽度。第一个参数是值,第二个是宽度,可选的第三个参数是填充字符(默认是空格)。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
int n{-42};
// default fill is ' '
println("|", left(n, 10zu), "|"); // | -42 |
println("|", right(n, 10zu), "|"); // | -42 |
println("|", middle(n, 10zu), "|"); // | -42 |
// custom fill character
println("|", left(n, 10zu, '*'), "|"); // | -42*******|
println("|", right(n, 10zu, '0'), "|"); // | 0000000-42|
}
chvw — 把整数当作字符打印
chvw(ch) 把一个整型值当作字符打印。当你的 char 存在整数里,或者你想打印一个码点又不想经过字符串时,它很有用。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
char c{'A'};
// Prints the character A followed by its integer value
println("char = ", chvw(c), " code = ", static_cast<int>(c));
}
rgvw — 范围视图
rgvw(range, sep) 打印 range 的每个元素,用给定的字符串或字符分隔。它是不写循环就导出容器内容的地道做法。分隔符可以是 C 字符串、单个字符,或者类似 os_c_str 的对象。
#include <fast_io.h>
#include <fast_io_dsal/vector.h>
using namespace fast_io::io;
int main()
{
::fast_io::vector<int> v{1, 2, 3, 4, 5};
// prints: 1, 2, 3, 4, 5
println(rgvw(v, ", "));
// Any separator works
// prints: 1 | 2 | 3 | 4 | 5
println(rgvw(v, " | "));
}
addrvw 与 pointervw
addrvw(p) 用全宽十六进制加 0x 前缀打印指针的数值 — 经典的“地址”格式。pointervw(p) 类似,但面向泛指指针类对象,给出干净的地址风格输出。两者都可用于裸指针和指针类类型。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
int x{42};
int *p{&x};
// prints: &x = 0x00007ffd...
println("&x = ", addrvw(p));
// prints: ptr = 0x00007ffd...
println("ptr = ", pointervw(p));
}
hex_encode — 把二进制数据写成十六进制串
hex_encode(first, last)(或 range 重载 hex_encode(range))把一段字节序列变成十六进制字符串,非常适合打印哈希值、密钥或原始缓冲区。hex_encode_upper 产生大写字母。
#include <fast_io.h>
#include <fast_io_dsal/vector.h>
using namespace fast_io::io;
int main()
{
::fast_io::vector<::std::uint8_t> digest{
0xDEz, 0xADz, 0xBEz, 0xEFz, 0x01z, 0x23z
};
// prints: deadbeef0123
println(hex_encode(digest));
// prints: DEADBEEF0123
println(hex_encode_upper(digest));
}
ordinal — 第 1、第 2、第 3…
ordinal(n) 打印整数并附上英文序数后缀:1st、2nd、3rd、4th、21st 等等。适合排名、排行榜以及带日期的日志消息。
#include <fast_io.h>
using namespace fast_io::io;
int main()
{
for (int i{1}; i <= 5; ++i)
{
// prints: 1st, 2nd, 3rd, 4th, 5th
println(ordinal(i));
}
}
复合操纵器:left_width、right_width
复合操纵器把多项格式化操作合成一个操纵器对象。left_width 与 right_width 就是把值格式化与对齐结合起来的例子。它们是无状态的 — 只作用于自己包住的那个值,没有会被破坏的流状态。
left_width(value, width, fill) 格式化 value 并在给定 width 的字段内左对齐,用 fill 填充(默认空格)。类似地,right_width(value, width, fill) 右对齐。当你想把格式化(例如 hex 或 fixed)与对齐结合在一个可组合的表达式里时,它们很有用。
#include <fast_io.h>
using namespace ::fast_io::iomnp;
int main()
{
// Combine hex formatting with left alignment
int value{255};
println("|", left_width(hex(value), 10zu), "|"); // | ff |
println("|", right_width(hex(value), 10zu), "|"); // | ff |
// With custom fill character
println("|", left_width(hex(value), 10zu, '0'), "|"); // | ff00000000|
println("|", right_width(hex(value), 10zu, '*'), "|"); // | ********ff|
// Combine with other manipulators
double pi{3.14159};
println("|", left_width(fixed(pi), 15zu), "|"); // | 3.14159 |
println("|", right_width(fixed(pi), 15zu), "|"); // | 3.14159 |
// These are stateless: each manipulator applies only to its value
println(hex(255), " ", 255); // prints: ff 255 (second value is decimal)
}
关键优势在于这些操纵器是无状态的。没有会被破坏的流状态,也不需要手动恢复设置。每个操纵器都是自包含的对象,只作用于它包住的值。
完整附录
上面展示的只是最常用的一批操纵器。库还提供任意进制 I/O(base<N>、base_get<N>)、ISO 时间戳、IP 地址格式化、十六进制浮点、百分比格式化器、转码操纵器等。
完整参考见附录:操纵器。
要点
- 操纵器就是
::fast_io::manipulators(简写::fast_io::mnp)里的普通对象,控制值如何被格式化或解析。 - 数制操纵器:输出用
hex、oct、bin、dec;输入用hex_get、oct_get、bin_get、dec_get。hex0x会加上0x前缀。 boolalpha在1/0与true/false之间切换。- 浮点格式化:
fixed、scientific、general,以及带千位分隔符的comma_*变体。 - 对齐:
left、right、middle接受一个值和字段宽度,外加可选填充字符。 chvw把整型值当字符打印;rgvw带分隔符打印 range。addrvw与pointervw把指针格式化成地址;hex_encode把字节序列变成十六进制串。ordinal追加英文序数后缀。- 复合操纵器:
left_width与right_width把值格式化与对齐结合起来。它们无状态、自包含。 - 所有操纵器与
print、println、scan的组合方式与普通值完全一样 — 没有另一套需要学习的格式化 DSL。