第11.3章: 操纵器

English

什么是操纵器?

操纵器是一个小辅助对象,它包住一个(或几个)值,告诉 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

三个操纵器控制浮点输出所用的记法:

每个都有一个 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 地址格式化、十六进制浮点、百分比格式化器、转码操纵器等。

完整参考见附录:操纵器。

要点