直接用substr截utf-8字符串会出错,因为substr按字节截取而utf-8是变长编码,中文、emoji等多字节字符被截断中间字节会导致乱码;需按unicode字符边界安全截取。

为什么直接用 substr 截 UTF-8 字符串会出错
因为 substr 按字节截取,而 UTF-8 是变长编码:ASCII 字符占 1 字节,中文通常占 3 字节,emoji 可能占 4 字节。直接截断中间字节会导致乱码(如显示 )或解析失败。
典型错误现象:"你好世界".substr(0, 4) 可能返回 "你" —— 因为前两个汉字共 6 字节,截 4 字节刚好卡在第二个汉字的中间。
- UTF-8 单个字符的字节长度由首字节高位模式决定:
0xxxxxxx(1 字节)、110xxxxx(2 字节)、1110xxxx(3 字节)、11110xxx(4 字节) -
std::string::size()返回的是字节数,不是字符数 - 没有标准库函数能直接按“Unicode 字符”截取
std::string
手动识别 UTF-8 起始字节实现安全截取
核心思路:从字符串开头逐字节扫描,跳过续字节(10xxxxxx),只把起始字节当作一个字符起点;累计到目标字符数后,用该位置作为 substr 的截止点。
示例逻辑(C++17):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
size_t utf8_truncate(const std::string& s, size_t max_chars) {
size_t pos = 0;
size_t chars = 0;
while (pos
- 返回的是字节位置,不是字符数,直接用于
s.substr(0, utf8_truncate(s, 5)) - 对非法 UTF-8 字节(如单独出现
10xxxxxx)做容错:当字节不匹配任何起始模式时,按 1 字节前进 - 性能尚可,O(n) 单次遍历;若频繁截取,建议缓存字符边界索引
使用 ICU 或 utf8cpp 库的更健壮方案
手写逻辑容易漏掉边界情况(如超长编码、代理对、BOM 处理)。生产环境推荐成熟库:
-
utf8cpp(header-only):utf8::distance(s.begin(), s.end())得字符总数;utf8::next(s.begin(), s.end())迭代每个字符起始位置 - ICU(重量级但完整):
icu::UnicodeString::moveFrom()+char32_t转换,支持 Unicode 标准化和图形簇(grapheme cluster)截取 - 注意:
std::wstring_convert已被 C++17 弃用,不要用
轻量场景推荐 utf8cpp,只需引入 utf8.h,示例:
#include <utf8.h>
std::string safe_substring(const std::string& s, size_t max_chars) {
auto begin = s.begin();
auto end = s.begin();
for (size_t i = 0; i <h3>截取后要不要加省略号?注意字节 vs 字符长度</h3>
<p>加 <code>"…"</code> 时,必须把它当作 UTF-8 字符处理:3 字节,不能简单拼接导致总长度超标。</p>
<ul>
<li>错误做法:<code>s.substr(0, max_bytes) + "..."</code> —— <code>"..."</code> 是 ASCII,但若原截取已满字节上限,拼接后可能超限</li>
<li>正确做法:先预留 3 字节空间给省略号,再截取,最后拼接:<code>safe_substring(s, max_chars - 1) + "…"</code>
</li>
<li>如果 <code>max_chars == 0</code>,直接返回空串;如果 <code>max_chars == 1</code>,省略号本身占 1 字符,就不能再截原文了</li>
</ul>
<p>最易被忽略的是:同一个视觉字符(如带变音符号的 é)可能由多个 Unicode 码点组成,严格按“用户感知字符”截取需 grapheme cluster 分析——这已超出纯 UTF-8 字节解析范畴,得靠 ICU。</p></utf8.h>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










