正确做法是先判断原字符串长度是否超过width,若超过则截取前width - prefix.length()个字符再拼接前缀,且需确保该差值非负以避免substr越界。

用 substr() 截断字符串但保留前缀的常见错误
直接调用 str.substr(0, width) 会粗暴截断,如果原字符串比 width 短,没问题;但如果要加前缀(比如 "..."),就必须预留空间——否则会出现前缀被截掉、或总长超限、或越界崩溃。最典型的错误是没检查 width 是否小于前缀长度,导致 substr() 参数为负或超出范围。
关键逻辑是:只有当原字符串长度 > width 时才需要截断;且截断后必须给前缀留出位置,所以实际可保留的尾部字符数是 width - prefix.length(),这个值必须 ≥ 0 才能安全操作。
- 若
width ,直接返回 <code>prefix(比如width=2,前缀是"...",根本放不下,只能全显示前缀) - 若
str.length() ,无需截断,直接返回原字符串 - 否则,取
str.substr(str.length() - (width - prefix.length())),再拼上前缀
完整可复用的 C++ 截断函数(支持右对齐省略)
以下函数实现“右侧截断 + 左侧加前缀”,即保留末尾内容,前面用前缀提示被截:
std::string truncate_with_prefix(const std::string& str, size_t width, const std::string& prefix = "...") {
if (width size_t keep_len = width - prefix.length();
if (keep_len > str.length()) return str; // 防御性检查
return prefix + str.substr(str.length() - keep_len);
}
使用示例:
truncate_with_prefix("hello world", 8, "...") → "...world"
truncate_with_prefix("abc", 10, "[~]") → "abc"(不截断)
truncate_with_prefix("test", 2, "...") → "..."(width=2 ,直接返回前缀)
为什么不用 std::string::resize() 或迭代器截断?
resize() 是从末尾删字符,无法实现“保留后缀+加前缀”的语义;而用迭代器手动构造容易忽略 std::string 的 size_type 无符号特性,导致 it - n 下溢成极大正数,引发越界读。用 substr() 配合长度计算更直观、边界更可控。
另外注意:substr(pos, len) 中 len 超出剩余长度时会自动截断到末尾,这是标准行为,可以依赖;但 pos 超出长度则抛 std::out_of_range,所以必须先确保 pos = str.length() - keep_len ≥ 0。
- 永远先算
keep_len,再验证是否 ≤str.length() - 不要写
str.substr(str.length() - width + prefix.length())这类未加括号或未提前校验的表达式 - 如果需左对齐截断(保留前缀+省略后缀),只需改为
str.substr(0, width - prefix.length()) + prefix,但要注意此时前缀在右边,语义不同
宽字符(std::wstring)和 UTF-8 字符串的陷阱
上述函数按 char 或 wchar_t 计数,不是按 Unicode 字符或显示宽度。对 UTF-8 字符串(如含中文),str.length() 返回字节数,一个汉字占 3 字节,会导致截断点落在某个汉字中间,显示乱码。
真正按“显示宽度”截断需额外处理:
- UTF-8 场景下,必须用 ICU、utf8cpp 或手写函数先将字符串解码为 Unicode 码点,再统计宽度(中文/全角字符通常算 2 宽,ASCII 算 1)
- Windows 上
std::wstring+GetConsoleScreenBufferInfo可查终端列宽,但仍是近似值 - 多数命令行工具(如日志打印)其实只要求“不超过 N 个字节”,此时原函数已足够;只有 GUI 或富文本渲染才需真宽度计算
别过早优化——先确认你的场景是否真的需要按图形宽度截断。90% 的 CLI 输出,按字节截断加前缀就够用,且更快更轻量。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











