std::regex仅适用于ASCII英文分词,不支持Unicode和中文;中文分词需手动处理UTF-8字节边界,用string_view配合词典匹配(如前向最大匹配),或集成cppjieba库。

用 std::regex 做基础分词最简单,但别指望它处理中文或复杂规则
纯英文、空格/标点分隔的文本,std::regex 能快速上手。比如按单词边界切分:std::regex re(R"(\b\w+\b)");,配合 std::sregex_iterator 遍历即可。但注意:std::regex 在 GCC 中性能较差,Clang 也不支持 Unicode 字符类(如 \p{Han}),遇到中文基本失效。
常见错误是直接用 R"([\s\p{P}]+)" 想匹配所有空白和标点——std::regex 根本不识别 \p{...} 语法,会编译失败或静默忽略。
- 只用于 ASCII 英文+数字+基础标点场景
- 正则模式尽量写死字符集,例如
R"([ \t\n.,;:!?]+)",别依赖 Unicode 类 - 若需 UTF-8 支持,必须先用
std::string存原始字节,再手动查 UTF-8 编码边界——std::regex对多字节字符无感知
中文分词必须绕开 std::string 和 std::regex,改用 UTF-8 字节索引 + 查词典
C++ 标准库没有内置中文分词能力。所谓“分词”,本质是字符串切片 + 词典匹配(前向最大匹配、双向匹配等)。关键前提是:把 UTF-8 字符串当字节数组处理,用 std::string::size_type 记录字节偏移,而不是 std::string::at() 或迭代器——后者在 UTF-8 下会截断汉字。
示例片段(前向最大匹配):
立即学习“C++免费学习笔记(深入)”;
std::vector<std::string> tokenize(const std::string& text, const std::unordered_set<std::string>& dict) {
std::vector<std::string> tokens;
size_t i = 0;
while (i < text.length()) {
size_t longest = 0;
// 从最长可能词长往回试(假设词最长 10 UTF-8 字节)
for (int len = std::min(10UL, text.length() - i); len >= 1; --len) {
std::string cand = text.substr(i, len);
if (dict.find(cand) != dict.end()) {
longest = len;
break;
}
}
if (longest == 0) {
// 单字切分(跳过一个 UTF-8 字符)
longest = utf8_char_length(text[i]); // 自行实现:查首字节判断 1~4 字节
}
tokens.push_back(text.substr(i, longest));
i += longest;
}
return tokens;
}- 词典必须存 UTF-8 编码的
std::string,不能用std::wstring(Windows 下宽字符是 UTF-16,Linux 是 UCS-4,不可移植) -
utf8_char_length必须自己写:看首字节高位,0xxxxxxx→1 字节,110xxxxx→2 字节,依此类推 - 性能瓶颈在
substr频繁分配——生产环境应传入std::string_view并避免拷贝
想省事就集成 cppjieba,但要注意它的 C++11 限制和内存模型
cppjieba 是目前最成熟的开源 C++ 中文分词库,基于 Python jieba 移植,支持 HMM + 词典双模。但它要求编译器支持 C++11,且内部用 std::shared_ptr 管理词典,初始化后不能热更新。
典型用法:
#include "cppjieba/Jieba.hpp"
const char* const DICT_PATH = "./dict/jieba.dict.utf8";
const char* const HMM_PATH = "./dict/hmm_model.utf8";
cppjieba::Jieba jieba(DICT_PATH, HMM_PATH);
std::vector<std::string> words;
jieba.Cut("我爱北京天安门", words);- 词典文件路径必须是 UTF-8 编码的字符串(即使 Windows 下也要用 UTF-8 路径,不是 GBK)
- 构造
Jieba实例是重操作,应全局单例或静态局部变量,别在循环里反复 new - 它不处理全角标点自动过滤——输入
"你好!"会分出"你好"和"!",需额外清洗
自定义分词逻辑时,std::string_view 是唯一安全的切片方式
所有涉及子串提取的操作,只要不想触发内存拷贝或破坏 UTF-8 完整性,必须用 std::string_view。它只存指针+长度,且构造时不会验证是否为合法 UTF-8 字符边界——这反而是优点:你控制字节偏移,它不干涉。
错误示范:auto sv = std::string_view(text).substr(i, len) —— 若 i 落在某个汉字中间,sv 就是乱码字节序列,后续比较词典必然失败。
- 所有
i和len必须基于 UTF-8 字符边界计算,不能来自std::string::find的返回值(它返回字节位置,不保证是字符起点) - 词典查找时,用
std::string_view作 key 的std::unordered_set可避免构造临时std::string - 输出 token 列表时,如果上层需要所有权,才调用
std::string(sv)拷贝;否则全程用std::string_view引用原文本
C++ 做分词真正难的不是算法,是 UTF-8 边界管理、内存视图选择和词典加载时机——这些细节错一点,结果就全乱。


















