第11.9章: 与字符串的关系
一套系统,处处可用
第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 流水线:
-
格式化 — 用
concat_fast_io(因而也就是print_reserve_define)把参数格式化进一个临时字符串。 -
解析 — 用
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 和宽字符字符串的字符类型变体:
::fast_io::concat_std— 产出::std::string(char)::fast_io::u8concat_std— 产出::std::u8string(char8_t)::fast_io::u16concat_std— 产出::std::u16string(char16_t)::fast_io::u32concat_std— 产出::std::u32string(char32_t)::fast_io::wconcat_std— 产出::std::wstring(wchar_t)
#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} 占位符替换为字符类型:
::fast_io::ostring_ref_fast_io— 用于char字符串::fast_io::u8ostring_ref_fast_io— 用于char8_t/ UTF-8 字符串::fast_io::u16ostring_ref_fast_io— 用于char16_t/ UTF-16 字符串::fast_io::u32ostring_ref_fast_io— 用于char32_t/ UTF-32 字符串::fast_io::wostring_ref_fast_io— 用于wchar_t字符串
你通过传入指向已有字符串的指针来构造这个引用。写入之前字符串不会被清空 — 输出追加在当前位置之后。写完之后,字符串里就包含所有已写入的内容。
#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_ 表示它们是非拥有引用 — 它们不管理底层字符串的生命周期。你必须保证字符串比引用活得更久。
要点
-
concat_fast_io不是单独的格式化器 — 它内部调用print_reserve_define,也就是println所用的同一个入口。 -
to<T>先用concat_fast_io格式化到字符串,再用scan_define解析。两个阶段都走标准 I/O 流水线。 -
任何实现了
print_reserve_define的自定义类型都能自动配合concat_fast_io以及to<T>的格式化阶段。 -
同时实现
print_reserve_define与scan_define,你的类型就能免费地通过to<T>往返。 - 这就是统一的设计:控制台、文件、字符串和 socket 输出共用一套定制点。
-
用
::fast_io::{chtype}ostring_ref_fast_io把格式化输出直接写进已有的字符串缓冲区。这些非拥有引用类型让你能逐步构建字符串,或者复用缓冲区以避免分配。 -
当你需要产出
::std::string以便与 C++ 标准库代码互操作时,用::fast_io::concat_std。它使用与concat_fast_io相同的定制点。