第11.9章: 与字符串的关系

English

一套系统,处处可用

第9.12章中介绍的 concat_fast_io 与 to<T> 并不是另一条独立的字符串格式化流水线 — 它们就是支撑 print、println 和 scan 的那套 I/O 系统。fast_io 中的每个字符串操作都汇入库的核心 I/O 层。

这意味着你为控制台和文件输出学到的定制点 — print_define 与 print_reserve_define — 会自动让你的类型也能配合字符串 API 工作。不需要额外实现任何东西。

concat_fast_io 内部使用 print_reserve_define

当你调用 concat_fast_io 时,库是通过调用 println 所用的同一个 print_reserve_define(或 print_define)把每个参数格式化进字符串的。如果你的类型能打印到控制台,它就能打印进字符串 — 而且免费。

例如,假设我们在第11.2章中为 Point 结构体定义了 print_reserve_define:


#include <fast_io.h>
#include <fast_io_dsal/string.h>

struct Point {
    double x;
    double y;
};

inline constexpr void print_reserve_define(::fast_io::io_reserve_type_t<char, Point>,
                                           char* iter,
                                           Point const& p) {
    // Format: "(x, y)"
    *iter++ = '(';
    iter = ::fast_io::detail::print_reserve_integral(iter, p.x);
    *iter++ = ',';
    *iter++ = ' ';
    iter = ::fast_io::detail::print_reserve_integral(iter, p.y);
    *iter++ = ')';
}

int main() {
    using namespace ::fast_io::iomnp;
    Point p{3.0, 4.0};

    // Works on the console
    println("Console: ", p);

    // Also works in concat_fast_io — same customisation point
    ::fast_io::string s = ::fast_io::concat_fast_io("Point: ", p);
    // s == "Point: (3, 4)"

    println(s);
}

不需要单独的 to_string 函数,不需要 std::format 特化,也不需要 operator<< 重载。一次定义,所有输出目标都可用。

to<T> 使用同一条 I/O 流水线

to<T> 分两个阶段工作,两个阶段都走标准 I/O 流水线:

  1. 格式化 — 用 concat_fast_io(因而也就是 print_reserve_define)把参数格式化进一个临时字符串。
  2. 解析 — 用 scan_define 把得到的字符串扫描进目标类型 T,这正是 scan 所用的同一个定制点。

#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;
    // Format 42 as a string ("42"), then parse it back as a double
    double d = ::fast_io::to<double>(42);  // d == 42.0

    // Concatenate multiple arguments, then parse the result
    ::std::size_t n = ::fast_io::to<::std::size_t>(1zu, 23zu);
    // "1" + "23" -> "123" -> 123

    println("d = ", d, ", n = ", n);
}

因为两个阶段都复用标准流水线,任何同时实现了 print_reserve_define 和 scan_define 的自定义类型,都能在不写任何额外胶水代码的情况下通过 to<T> 往返。

concat_std:格式化到 C++ 的 std::string

在第9.16章中我们介绍了 C++ 标准库容器,包括 ::std::string。如果你需要与期望 ::std::string 的标准库代码互操作,fast_io 提供 ::fast_io::concat_std — 它的用法与 concat_fast_io 完全一样,只是产出 ::std::string 而不是 ::fast_io::string。

与 concat_fast_io 一样,concat_std 使用同样的 print_reserve_define 定制点。任何能配合 println 或 concat_fast_io 工作的类型,都能自动配合 concat_std 工作。

此外还有针对 UTF-8、UTF-16、UTF-32 和宽字符字符串的字符类型变体:


#include <fast_io.h>
#include <fast_io_dsal/string.h>
#include <string>

struct Point {
    int x;
    int y;
};

// Assume print_reserve_define is defined as in Ch11.2

int main() {
    using namespace ::fast_io::iomnp;
    Point p{42, 100};

    // concat_std produces a ::std::string
    ::std::string s = ::fast_io::concat_std("Point: ", p);
    // s == "Point: (42, 100)"

    // u8concat_std produces a ::std::u8string
    ::std::u8string u8s = ::fast_io::u8concat_std(u8"Unicode: ", p);

    println("std::string: ", s);
}

此外还有 ::fast_io::concatln_std(及其字符类型变体),它在末尾追加一个换行,就像 concatln_fast_io 一样。

concat_std 的主要用途,是需要把格式化好的字符串交给期望 ::std::string 的遗留 C++ 代码时。对新代码来说,优先使用 concat_fast_io 配 ::fast_io::string。

统一的设计

要点是:fast_io 为每一种输出目标提供一套定制点:

定制点 被谁使用
print_define / print_reserve_define println、print、concat_fast_io、concatln_fast_io、concat_std、concatln_std、to<T>
scan_define / scan_context_define scan、to<T>(解析阶段)

控制台、文件、带缓冲 I/O、字符串、socket — 它们共享同一套格式化与解析层。定义一次你的类型,它在所有地方都能用。

写入已有的字符串:{chtype}ostring_ref_fast_io

concat_fast_io 创建的是新字符串,但有时你想把格式化后的输出直接写进一个已有的字符串缓冲区。::fast_io::{chtype}ostring_ref_fast_io 家族正是为此而生。它们是非拥有引用类型,包住一个已有字符串,并把它暴露为 print、println 等输出操作的输出设备。

其中的 {chtype} 占位符替换为字符类型:

你通过传入指向已有字符串的指针来构造这个引用。写入之前字符串不会被清空 — 输出追加在当前位置之后。写完之后,字符串里就包含所有已写入的内容。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main()
{
    using namespace ::fast_io::iomnp;

    ::fast_io::u8string result;

    // Wrap the existing string as an output device
    ::fast_io::u8ostring_ref_fast_io ref(result);

    // Write to the string using print/println
    print(ref, "Hello, ");
    print(ref, "World!");
    println(ref);  // adds newline

    // The string now contains "Hello, World!\n"
    println("Result: ", result);

    // You can also write structured data
    ::fast_io::u8string buffer;
    ::fast_io::u8ostring_ref_fast_io buf_ref(buffer);

    ::std::size_t const x{42zu};
    ::std::size_t const y{100zu};
    println(buf_ref, "Point: (", x, ", ", y, ")");

    // buffer now contains "Point: (42, 100)\n"
    println(buffer);
}

当你需要逐步构建一个字符串,或者想复用字符串缓冲区以避免反复分配时,这特别有用。引用类型本身很轻 — 它只持有一个指向字符串的指针。

注意这些类型与 ::fast_io::u8string 等拥有所有权的字符串类型不同。名字里的 _ref_ 表示它们是非拥有引用 — 它们不管理底层字符串的生命周期。你必须保证字符串比引用活得更久。

要点