::fast_io::string
1. 创建字符串
::fast_io::string 是用于文本的容器。你可以直接构造一个:
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s("Hello fast_io");
println(s);
}
2. 拼接
fast_io 中的拼接复用了它的 I/O 打印系统。任何可打印的类型都可以拼接进一个字符串。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string name{"Alice"};
::fast_io::string greeting = ::fast_io::concat_fast_io("Hello, ", name, "!");
::fast_io::string greeting_ln = ::fast_io::concatln_fast_io("Hello, ", name); // adds newline
print(greeting_ln); // Output: Hello, Alice\n
}
3. 读取输入
使用 fast_io::iomnp 中的操纵器把文本读进字符串。把
using namespace fast_io::iomnp; 放在 main() 内部,这样它只局部生效。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string str;
// read next space-delimited token
scan(str);
println("Token: ", str);
// read one line (without newline)
scan(line_get(str));
println("Line: ", str);
// read whole input until end of the file
scan(whole_get(str));
println("Whole input size: ", str.size());
}
这些同样适用于文件:
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace fast_io::iomnp;
::fast_io::string str;
::fast_io::ibuf_file ibf("input.txt");
scan(ibf, line_get(str)); // read one line
scan(ibf, whole_get(str)); // read whole file
}
检测文件结束(EOF)
使用 scan<true> 来检测文件结束(EOF)。只要还有输入可用,它就返回 true。
示例 1:逐个读取词直到 EOF
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace fast_io::iomnp;
::fast_io::string str;
// keep reading space-delimited tokens until end of the file
for (; scan<true>(str); ) {
println("Token: ", str);
}
}
示例 2:逐行读取直到 EOF
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace fast_io::iomnp;
::fast_io::string str;
// keep reading lines until end of the file
for (; scan<true>(line_get(str)); ) {
println("Line: ", str);
}
}
4. 安全地访问字符
fast_io::string 允许你直接访问字符:
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
char first = s.front(); // first character
char last = s.back(); // last character
println("First: ", chvw(first)); // H
println("Last: ", chvw(last)); // o
// access by index
char middle = s[2]; // third character (index starts at 0)
println("Middle: ", chvw(middle)); // l
}
⚠️ 安全提示:如果你在字符串为空或索引越界时调用 s.front()、s.back()
或使用 s[i],fast_io 会执行边界检查。如果检查失败,它就调用
__builtin_trap(),让程序立刻静默停止。
崩溃示例
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s; // empty string
// All of these will crash:
char c1 = s.front(); // invalid: empty string
char c2 = s.back(); // invalid: empty string
char c3 = s[5]; // invalid: index out of range
}
安全用法
为了避免崩溃,访问之前始终先检查:
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
if(!s.is_empty()) {
println("First: ", chvw(s.front()), "\n"
"Last: ", chvw(s.back()));
}
std::size_t i = 2;
if(i < s.size()) {
println("Index ", i, ": ", chvw(s[i]));
}
}
5. 遍历字符
首选:基于范围的 for 循环
遍历每一个字符最简便、最安全的方式是基于范围的 for 循环:
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
for(char c : s) {
print(chvw(c), ' ');
}
// Output: H e l l o
}
替代方案:索引循环
如果你需要位置,请使用容器索引。这比迭代器更安全,因为会做边界检查:
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
for(std::size_t i{}, n{s.size()}; i != n; ++i) {
println("Index ", i, ": ", chvw(s[i]));
}
}
进阶:迭代器
迭代器的行为类似指向字符串内存的指针。它们很强大,但对初学者而言更难掌握。 与容器索引不同,迭代器不做边界检查。误用会导致未定义行为—— 也就是说程序可能崩溃、破坏数据,甚至为安全漏洞打开大门。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
// Iteration uses a left-inclusive range: [begin(), end())
// begin() points to the first element ('H')
// end() points just past the last element (not valid to dereference)
for(auto it = s.begin(), ed = s.end(); it != ed; ++it) {
print(chvw(*it), ' ');
}
// Output: H e l l o
}
左闭区间
C++ 中的迭代器遵循左闭右开区间的惯例:
begin()→ 指向第一个有效元素(包含)。end()→ 指向最后一个元素之后的位置(不包含,不能解引用)。- 循环条件
it != s.end()确保迭代在越界之前停止。
这种设计让区间更易于组合:区间的长度就是 end() - begin(),而
算法可以安全地在 end() 处停下,不会访问非法内存。
未定义行为与真实的漏洞利用
未定义行为意味着 C++ 标准对会发生什么不作任何规定。程序可能看起来能正常工作, 但也可能崩溃、破坏内存,或者暴露漏洞。著名的漏洞利用正是滥用了这几类 内存安全缺陷:
- 释放后使用(迭代器失效)—— 曾被 Heartbleed(2014)以及无数浏览器漏洞利用。
- 缓冲区溢出(越界访问)—— 曾被 Morris 蠕虫(1988)与 WannaCry 勒索软件(2017)利用。
释放后使用和缓冲区溢出都是内存安全缺陷的例子。这些缺陷让攻击者得以 执行任意代码、窃取敏感数据或传播恶意软件。这就是为什么即使在 简单的示例中,安全的迭代做法也至关重要。
根据 Google Chromium 安全团队的说法,Chrome 中约 70% 的严重安全缺陷都是内存安全问题, 其中一半是释放后使用问题。这项分析基于 2015 年以来的 912 个高或严重级别的缺陷。 你可以在此阅读他们的详细拆解: Chromium 项目:内存安全 。
迭代器失效
如果字符串重新分配(例如在 append 或 reserve 之后),迭代器就会失效。
使用失效的迭代器是未定义行为——与释放后使用同属一类缺陷。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
auto it2 = s.begin();
s.append(" world"); // may reallocate, invalidates it2
// ⚠️ Undefined behavior: iterator now points to freed memory
println(*it2); // like a use-after-free
}
迭代器算术
迭代器是连续的,因此你可以做算术运算(+/-、[]、比较)。
⚠️ 这些运算不做边界检查。负偏移或越过 end() 都是未定义行为。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
auto it = s.begin(); // points to 'H'
println("First: ", chvw(*it));
// Move forward with +n
println("Third: ", *(it + 2)); // 'l'
// Negative indexing relative to a valid position
auto it2 = s.begin() + 2; // points to 'l'
println("Second via negative index: ", chvw(it2[-1])); // 'e'
// Random access with operator[]
println("Fourth via []: ", chvw(it[3])); // 'l'
// Comparisons
if(it < s.end()) {
print("Iterator is before end()\n");
}
// ⚠️ Buffer overflow example: stepping past end()
auto bad = s.end();
// println(chvw(*bad)); // undefined behavior, like a buffer overflow
// ⚠️ Use-after-free example: iterator invalidation
auto it3 = s.begin();
s.append(" world"); // may reallocate, invalidates it3
// println(chvw(*it3)); // undefined behavior, like use-after-free
}
打印迭代器的地址
你可以用 ::fast_io::mnp::pointervw 操纵器查看迭代器指向的内存地址
(它可直接从 <fast_io.h> 获得):
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
auto it = s.begin();
println("Character: ", chvw(*it));
println("Address: ", ::fast_io::mnp::pointervw(it));
}
给初学者的建议
迭代器很强大,但也属进阶内容。大多数情况下,优先选择:
- 基于范围的 for 循环 —— 最安全、最简单的迭代方式。
- 容器索引(
s[i]) —— 比迭代器更安全,带边界检查。 - 迭代器 —— 强大但属进阶内容;只在算法要求时使用。
fast_io::string 中的容器索引会执行边界检查,并在越界时调用 __builtin_trap(),
立刻停止程序。这种快速失败(fail-fast)行为可防止悄无声息的内存破坏。相比之下,迭代器不检查边界,
可能导致历史上被利用过的同类内存安全缺陷。
6. 修改
字符串可以通过两种主要方式修改:按位置(索引)引用,或者使用迭代器。 两种方式都能插入、删除或替换字符串的片段,但它们在安全性上有所不同。
基于索引的修改
当你使用位置时,字符串会检查索引是否有效。如果越界, 程序会立刻停止,而不是悄悄破坏内存。这种快速失败行为使 基于索引的修改更安全、更容易推理。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
// Add text at the end
s.insert_index(5, " world");
println(s); // Hello world
// Remove everything from position 5 onward
s.erase_index(5);
println(s); // Hello
// Replace the first 5 characters ("Hello") with "Hi"
s.replace_index(0, 5, "Hi");
println(s); // Hi
}
基于迭代器的修改
迭代器的行为类似指向字符串内存的指针。它们允许更灵活的操作, 但不做边界检查。如果你使用的迭代器无效,或者在重新分配之后已经失效, 行为就是未定义的——与缓冲区溢出或释放后使用同属一类缺陷。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::string s{"Hello"};
// Insert using a valid iterator
auto it = s.begin() + 5;
s.insert(it, '!');
println(s); // Hello!
// Replace using iterators: replace "Hello" with "Hi"
s.replace(s.begin(), s.begin() + 5, "Hi");
println(s); // Hi!
// ⚠️ Danger: iterator invalidation
auto it2 = s.begin();
s.append(" world"); // may reallocate, invalidates it2
// s.insert(it2, '?'); // undefined behavior
}
小结
基于位置的修改更安全,因为它强制执行边界检查,并在出错时快速失败。 基于迭代器的修改更强大,但没有检查,需要自律以避免失效 和越界访问。误用迭代器会导致未定义行为,以及造成真实 安全漏洞的同类内存安全缺陷。
7. 其他字符串类型
除了 fast_io::string(基于 char)之外,
fast_io 还为不同的字符编码提供了专门的字符串容器:
fast_io::u8string—— UTF-8 编码文本fast_io::u16string—— UTF-16 编码文本fast_io::u32string—— UTF-32 编码文本fast_io::wstring—— 宽字符(wchar_t,依平台而定)
自动文件名转码
fast_io 会自动把文件名转码成操作系统所需的编码。
这意味着你可以安全地用 u8、u16、u32 或 L 字符串字面量
作为文件名,而 fast_io 会在内部处理转换。你不需要担心
平台差异——该库会确保把正确的编码传给操作系统。
UTF-8 字符串示例
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::u8obuf_file u8obf(u8"a.txt"); // UTF‑8 filename, transcoded automatically
::fast_io::u8string u8str = ::fast_io::u8concat(u8"blah blah", 30, u8"fassfaafs");
println(u8obf, u8"hello: ", u8str);
}
UTF-16 字符串示例
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::u16obuf_file u16obf(u"a16.txt"); // UTF‑16 filename, transcoded automatically
::fast_io::u16string u16str = ::fast_io::u16concat_fast_io(u"UTF", 16, u"text");
println(u16obf, u"Message: ", u16str);
}
UTF-32 字符串示例
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::u32obuf_file u32obf(U"a32.txt"); // UTF‑32 filename, transcoded automatically
::fast_io::u32string u32str = ::fast_io::u32concat_fast_io(U"UTF", 32, U"text");
println(u32obf, U"Message: ", u32str);
}
跨字符类型打印
使用 ::fast_io::mnp::code_cvt 可以把不同字符类型的字符串一起打印。
这样你就能在同一份输出中混用 string、u8string、u16string 和 u32string,
无需手工转换。
#include <fast_io.h>
#include <fast_io_dsal/string.h>
int main() {
using namespace ::fast_io::iomnp;
::fast_io::u8obuf_file obf(u8"hello.txt");
::fast_io::u8string s(u8"ASCII text");
::fast_io::u16string u16s = ::fast_io::u16concat_fast_io(u"UTF", 16, u" text", ::fast_io::mnp::code_cvt(s));
// code_cvt allows cross charactype printing
println(obf, s, u8" | ", code_cvt(u16s));
}
关于宽字符串的说明
fast_io::wstring 基于 wchar_t,而它依平台而不同
(Windows 上是 UTF-16,大多数 Unix 系统上是 UTF-32)。由于这种不一致,
初学者应当避免使用 wchar_t/wstring,除非
有特定的平台需求。请优先使用 u8string、u16string
或 u32string,以获得可预测、可移植的 Unicode 处理。
最佳实践
- 把u8string作为默认选择:UTF-8 是最可移植、支持最广的编码。
- 依靠自动文件名转码:你可以传入任何字面量类型,fast_io 会把它适配到操作系统。
- 在输出中混用不同字符串类型时,使用code_cvt。
- 除非你针对需要它的特定平台 API,否则避免 wchar_t/wstring。
8. 内存安全工具
现代编译器与运行时提供了强大的工具来检测和防止内存安全缺陷。
它们可以与 fast_io 结合使用,以确保代码健壮且安全。
-
AddressSanitizer(ASan):用
-fsanitize=address启用,可在运行时捕获缓冲区溢出、 释放后使用以及其他内存错误。 -
UndefinedBehaviorSanitizer(UBSan):用
-fsanitize=undefined启用,用于检测 非法移位、整数溢出或未对齐访问等未定义行为。 -
模糊测试(Fuzzing):把 sanitizer 与 Clang 的
-fsanitize=fuzzer结合, 自动生成随机输入,对代码路径做压力测试以找出隐藏的缺陷。 -
内存标记(Memory Tagging):在受支持的平台(ARM MTE、WebAssembly
内存标记等)上使用
-fsanitize=memtag来强制带标签的内存安全。它通过把标签与内存分配 关联起来,检测空间与时间上的错误。WebAssembly 内存标记方法由与本fast_io库相同的作者开发,这也凸显了它对实用内存安全的关注。详情见 ACM 参考 中关于跨平台内存标记的说明。
在实践中:开发期间用 sanitizer 运行你的构建,把模糊测试集成进 CI 流水线,
并在可用处采用内存标记。这些工具补充了 fast_io 的快速失败哲学,
能在细微缺陷进入生产环境之前就将其捕获。
要点
::fast_io::string 是 fast_io 中的核心文本容器,专为安全高效的文本处理而设计。
- 用
concat_fast_io/concatln_fast_io做高效拼接。 - 用
push_back、append和索引安全地操作文本。 - 读取输入时:无操纵器表示读取词(token),
line_get读取一行,whole_get读取整个流。 - 优先使用基于范围的循环;需要位置时用索引,迭代器只用于进阶情形。
- 为可移植性优先选择
u8string(UTF-8);除非平台特定,否则避免wchar_t/wstring。 - 使用 Clang sanitizer(
-fsanitize=address,undefined)、模糊测试(-fsanitize=fuzzer) 以及内存标记(在 ARM MTE、WebAssembly 等平台上用-fsanitize=memtag)等工具来检测和防止内存安全缺陷。
简而言之:依靠安全的默认做法,谨慎使用迭代器,并用 UTF-8 处理跨平台文本。