第11.2章: 为 I/O 定义自定义类型
概述
fast_io 用定制点让你定义自己的类型如何被打印和扫描。主要有四个定制点,各有不同的取舍。
1. 定制点一览
| 定制点 | 最适合 | 速度 | 安全性 |
|---|---|---|---|
print_reserve_define |
大小固定的类型(整数、浮点数、枚举、小结构体) | 最快 | 如果算错大小就不安全 |
print_scatter_define |
已经以连续字符串形式存放的类型(字符串字面量、枚举名) | 无复制 | 安全 |
print_define |
复杂/复合类型(最灵活) | 中等 | 安全 |
scan_context_define |
表示形式会跨越多次缓冲区填充的类型(流式解析) | 不适用(输入) | 安全 |
2. print_reserve_define — 最快(但危险)
print_reserve_define 直接写进 I/O 缓冲区。它是最快的方法 — 甚至比返回数组还快,因为它复用已有的 I/O 缓冲区内存,没有任何额外的分配或复制。
危险:你必须正确计算缓冲区大小。如果
print_reserve_size 返回的值太小,你就会写越界 — 这是未定义行为,可能破坏内存。
#include <fast_io.h>
struct Point {
int x;
int y;
};
// Step 1: Tell fast_io the maximum buffer size needed
template <::std::integral char_type>
inline constexpr ::std::size_t print_reserve_size(
::fast_io::io_reserve_type_t<char_type, Point>) noexcept
{
// Two ints, a comma, parentheses, and a space
// Max int: 10 digits + sign = 11 chars each
// "(x, y)" = 1 + 11 + 2 + 11 + 1 = 26
return 26;
}
// Step 2: Write into the buffer, return pointer past last written char
template <::std::integral char_type>
inline constexpr char_type* print_reserve_define(
::fast_io::io_reserve_type_t<char_type, Point>,
char_type* iter, Point p) noexcept
{
// Use character literals appropriate for each character type
// This handles EBCDIC and other non-ASCII encodings correctly
if constexpr (::std::same_as<char_type, char>) {
*iter++ = '(';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.x);
*iter++ = ',';
*iter++ = ' ';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.y);
*iter++ = ')';
} else if constexpr (::std::same_as<char_type, char8_t>) {
*iter++ = u8'(';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.x);
*iter++ = u8',';
*iter++ = u8' ';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.y);
*iter++ = u8')';
} else if constexpr (::std::same_as<char_type, char16_t>) {
*iter++ = u'(';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.x);
*iter++ = u',';
*iter++ = u' ';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.y);
*iter++ = u')';
} else if constexpr (::std::same_as<char_type, char32_t>) {
*iter++ = U'(';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.x);
*iter++ = U',';
*iter++ = U' ';
iter = ::fast_io::detail::print_reserve_define_integer<10>(iter, p.y);
*iter++ = U')';
}
return iter;
}
int main() {
using namespace ::fast_io::iomnp;
Point p{42, 100};
println("Point: ", p); // Output: Point: (42, 100)
}
何时使用:当你的类型有一个固定的小规模文本表示,且你能在编译期精确算出大小时。
3. print_scatter_define — 无复制
print_scatter_define 返回指向已有数据的指针和长度。不需要复制 — I/O 系统直接引用你的数据。
#include <fast_io.h>
enum class Color { Red, Green, Blue };
template <::std::integral char_type>
inline constexpr ::fast_io::basic_io_scatter_t<char_type>
print_scatter_define(::fast_io::io_reserve_type_t<char_type, Color>, Color c) noexcept
{
// Use character type-specific string literals
if constexpr (::std::same_as<char_type, char>) {
switch (c) {
case Color::Red: return {"Red", 3};
case Color::Green: return {"Green", 5};
case Color::Blue: return {"Blue", 4};
}
return {"Unknown", 7};
} else if constexpr (::std::same_as<char_type, char8_t>) {
switch (c) {
case Color::Red: return {u8"Red", 3};
case Color::Green: return {u8"Green", 5};
case Color::Blue: return {u8"Blue", 4};
}
return {u8"Unknown", 7};
} else if constexpr (::std::same_as<char_type, char16_t>) {
switch (c) {
case Color::Red: return {u"Red", 3};
case Color::Green: return {u"Green", 5};
case Color::Blue: return {u"Blue", 4};
}
return {u"Unknown", 7};
} else if constexpr (::std::same_as<char_type, char32_t>) {
switch (c) {
case Color::Red: return {U"Red", 3};
case Color::Green: return {U"Green", 5};
case Color::Blue: return {U"Blue", 4};
}
return {U"Unknown", 7};
}
}
int main() {
using namespace ::fast_io::iomnp;
println("Color: ", Color::Green); // Output: Color: Green
}
何时使用:当你的类型的文本表示在内存中已经是一段连续字符串时(字符串字面量、枚举名等)。
4. print_define — 最灵活
print_define 用 print_freestanding 写到输出流上。它最灵活,但也是三种打印方法里最慢的(依然很快 — 只是不如 print_reserve_define 那么快)。
#include <fast_io.h>
struct Person {
::fast_io::string name;
int age;
};
template <::std::integral char_type, typename T>
inline constexpr void print_define(
::fast_io::io_reserve_type_t<char_type, Person>,
T& out, Person const& p)
{
print_freestanding(out, p.name, " (age: ", p.age, ")");
}
int main() {
using namespace ::fast_io::iomnp;
Person alice{::fast_io::string{"Alice"}, 30};
println("Person: ", alice); // Output: Person: Alice (age: 30)
}
何时使用:用于输出依赖运行时数据的复杂类型,或者当你需要组合多个子对象时。
5. scan_context_define — 多次调用的扫描
scan_context_define 用于文本表示可能跨越多次缓冲区填充的类型。它用一个上下文在多次调用之间记录解析状态。
你必须提供三个函数:
scan_context_type— 声明上下文类型scan_context_define— 从缓冲区解析,返回 partial/ok/invalidscan_context_eof_define— 处理输入结束
#include <fast_io.h>
struct MyType {
int value;
};
// Context type holds partial parse state
struct MyContext {
int accumulated{};
bool started{};
};
// Step 1: Declare context type
template <::std::integral char_type>
inline constexpr auto scan_context_type(
::fast_io::io_reserve_type_t<char_type, MyType>) noexcept
{
return ::fast_io::io_type_t<MyContext>{};
}
// Step 2: Parse from buffer
template <::std::integral char_type>
inline constexpr ::fast_io::parse_result<char_type const*>
scan_context_define(
::fast_io::io_reserve_type_t<char_type, MyType>,
MyContext& ctx,
char_type const* first, char_type const* last,
MyType& t) noexcept
{
// Parse digits, accumulating into ctx.accumulated
for (auto it = first; it != last; ++it) {
char_type ch = *it;
if (ch >= static_cast<char_type>('0') &&
ch <= static_cast<char_type>('9')) {
ctx.accumulated = ctx.accumulated * 10 +
(static_cast<int>(ch) - static_cast<int>('0'));
ctx.started = true;
} else {
t.value = ctx.accumulated;
return {it, ::fast_io::parse_code::ok};
}
}
return {last, ::fast_io::parse_code::partial}; // need more data
}
// Step 3: Handle EOF
template <::std::integral char_type>
inline constexpr ::fast_io::parse_code
scan_context_eof_define(
::fast_io::io_reserve_type_t<char_type, MyType>,
MyContext& ctx, MyType& t) noexcept
{
if (ctx.started) {
t.value = ctx.accumulated;
return ::fast_io::parse_code::ok;
}
return ::fast_io::parse_code::invalid;
}
6. 我该用哪一个?
| 你的类型是…… | 那么用…… |
|---|---|
| 大小固定、编译期可计算(整数、小结构体) | print_reserve_define(最快) |
| 已经是连续字符串(枚举名、字面量) | print_scatter_define(无复制) |
| 复杂,由多个部分组成 | print_define(最灵活) |
| 可能跨越多次缓冲区读取(流式解析) | scan_context_define |
要点
print_reserve_define最快 — 直接写进 I/O 缓冲区,没有任何额外分配。但必须正确计算大小,否则会写越界。print_scatter_define返回指向已有数据的指针 — 零复制。最适合枚举名和类字符串类型。print_define最灵活 — 用print_freestanding写到流上。scan_context_define处理会跨越多次缓冲区填充的类型 — 用上下文记录解析状态。- 选择适合你类型的最简单方法。只有在你能够精确计算大小时才用
print_reserve_define。