直接用substr截中文会乱码,因为substr按字节截取而UTF-8中文占3字节,中途切断会导致非法字节序列;安全做法是从目标字节数往回扫描,找到最近的合法UTF-8起始字节再截断。

为什么直接用 substr 截中文会乱码?
因为 substr 按字节截取,而 UTF-8 中文是 3 字节(如 “你好” 占 6 字节),若在某个中文字符中间切开,就得到非法 UTF-8 字节序列——解码失败或显示 。
关键不是“能不能截”,而是“在哪截才合法”。UTF-8 的每个字符起始字节有固定模式:0xxxxxxx(ASCII)、11xxxxxx(多字节首字节)、10xxxxxx(后续字节)。必须确保不切断一个字符的字节流。
如何安全地按字节数截取 UTF-8 字符串?
核心思路:从目标字节数位置往回扫描,找到最近的一个合法 UTF-8 起始字节位置,再截取。不能硬切,得“找边界”。
- 先用
std::string::substr(0, max_bytes)取出前 N 字节 - 再从末尾开始检查每个字节:如果是
10xxxxxx(即(b & 0xC0) == 0x80),说明是续字节,跳过 - 遇到第一个非续字节(即
(b & 0xC0) != 0x80),就是某个字符的开头,截断位置就定在这里 - 如果整个子串全是续字节(比如从中间硬切进一个 3 字节汉字),那就退到 0 —— 返回空串
示例逻辑:
立即学习“C++免费学习笔记(深入)”;
std::string safe_utf8_truncate(const std::string& s, size_t max_bytes) {
if (max_bytes >= s.size()) return s;
std::string candidate = s.substr(0, max_bytes);
int i = candidate.size() - 1;
while (i >= 0 && ((candidate[i] & 0xC0) == 0x80)) --i;
return candidate.substr(0, i + 1);
}
用 std::wstring 或 std::u8string 能避开问题吗?
不能直接解决“按字节数截取”这个需求。虽然 std::u8string(C++20)语义上是 UTF-8 字符串,但它仍是 std::basic_string<char8_t>,底层还是字节数组,substr 依然按字节切。
std::wstring 在 Windows 上用 UTF-16,在 Linux/macOS 上常是 UCS-4,但:一是平台不一致;二是你原始数据大概率是 UTF-8 字节流(文件、网络、JSON);三是转换本身就有开销和风险(BOM、代理对、无效序列)。
真正要“按字符数截”,得先做 UTF-8 解码(比如用 std::codecvt_utf8 —— 已弃用)或第三方库(如 ICU、utf8cpp),但那和“按字节数截”已是不同需求了。
实际使用时最容易踩的坑
最常见错误是只检查最后一个字节是否为 0xC0~0xFF 就认为安全,忽略了 UTF-8 多字节结构。例如字节 0xE4 是合法首字节,但 0xE4 0xB8 不是完整字符,必须确认后续字节是否匹配。
- 别用
strlen或s.data() + n手动指针运算——没做边界校验,极易越界或截断在中间 - 别假设输入一定合法 UTF-8——遇到非法序列(如
0xFF)应视为单字节字符或跳过,否则循环可能崩溃 - Web 场景中注意:HTTP header、URL path、form data 都可能是 UTF-8,但某些旧系统会混入 GBK,此时需先检测编码再处理
真正安全的截取,本质是“找 UTF-8 字符边界”,而不是“数够了就停”。字节数只是输入约束,边界判断才是不可跳过的步骤。


















