C++ std::string 面试 20 问:从 size 到 SSO,背完直接上场
发布时间:2026/9/27 22:29:32 锦皓数字建站

C std::string 面试 20 问从 size 到 SSO背完直接上场摘要面试官问“sizeof(std::string) 是多少”你答 32 字节他追问“为什么不是 24”你卡住了。本文把 std::string 面试中最常被追问的 20 个问题整理成可直接背诵的速查表每题给出一句话答案和面试官想听的展开点。覆盖 size/capacity 区别、reserve 与 resize、SSO 原理与容量、移动语义的真实边界、string_view 悬空陷阱、UTF-8 截断乱码以及深浅拷贝的手撕要点。文末附完整面试思维导图和 5 道高频手撕题。一、基础概念篇Q1std::string和 C 风格char*的本质区别是什么一句话答案char*是指向字符数组的裸指针不携带长度和所有权信息std::string是封装了字符数组的类自动管理内存、维护 size 和 capacity提供值语义。面试官想听的展开char*需要手动malloc/free容易内存泄漏和缓冲区溢出std::string的size()是 O(1)而strlen是 O(n)std::string支持嵌入\0结束由 size 决定不靠\0标记。Q2size()和length()有什么区别一句话答案完全等价都返回当前有效字符数字节数length()是为了兼容早期 STL 保留的别名。面试官想听的展开容器统一用size()length()仅对std::string存在。Q3size()和capacity()的区别是什么一句话答案size()是当前有效字符数capacity()是已分配内存能容纳的字符总数size capacity恒成立。面试官想听的展开reserve(n)只改 capacityresize(n)改 size 并可能改 capacityclear()把 size 设为 0 但不释放 capacity。Q4std::string的元素是连续存储的吗一句话答案C11 起强制保证连续存储s[0] s.data()可以安全传给 C API。面试官想听的展开C98/03 不保证连续C11 的 [string.require] 明确规定basic_string的元素必须连续存放。Q5data()和c_str()有什么区别一句话答案C11 起两者都保证\0结尾c_str()始终返回const char*data()在 C17 起有非 const 重载返回char*可修改[0, size())范围。面试官想听的展开写入data()[size()]终止符位置是未定义行为保存这两个函数返回的指针后任何可能重新分配的操作都会使其失效。二、容量与内存管理篇Q6reserve()和resize()的区别是什么一句话答案reserve只改 capacity不碰 size字符串内容不变resize改 size可能同时增加 capacity新增位置填充\0或指定字符。面试官想听的展开reserve用于预分配避免反复扩容resize用于改变字符串有效长度。resize缩小字符串时不会释放 capacity。Q7clear()会释放内存吗一句话答案不会。clear()只把 size 设为 0capacity 保持不变。面试官想听的展开这是有意设计避免循环中反复分配。要确定性释放内存用std::string().swap(s)shrink_to_fit()是非绑定请求实现可以忽略。Q8std::string的扩容策略是什么一句话答案标准只要求摊销常数时间复杂度常见实现用指数增长libstdc 是 2 倍libc 和 MSVC 是 1.5 倍。面试官想听的展开指数增长保证 n 次追加的摊销复杂度为 O(n)固定增量会导致 O(n²)。Q9shrink_to_fit()一定能释放多余内存吗一句话答案不一定。标准规定它是non-binding request实现可以选择忽略。面试官想听的展开libstdc 和 libc 通常执行收缩MSVC 在某些版本中可能忽略。需要确定性释放用 swap 技巧。三、SSO 与底层布局篇Q10什么是 SSO它的容量是多少一句话答案SSO短字符串优化把短字符串直接存在对象内部的 union 缓冲区中避免堆分配。libstdc 和 MSVC 的 SSO 容量是 15 字符libc 是 22 字符64 位平台。面试官想听的展开libstdc 的std::string是 32 字节libc 是 24 字节libc 用指针最低位标记长短模式节省了一个指针字段。Q11sizeof(std::string)是多少一句话答案GCC/libstdc 和 MSVC 是32 字节Clang/libc 是24 字节64 位平台。面试官想听的展开32 字节布局包含一个指针、size、capacity 和一个 16 字节的 union 缓冲区libc 的 24 字节布局通过指针最低位标记模式省去了独立标记字段。Q12移动一个 SSO 字符串是 O(1) 吗一句话答案不是。SSO 字符串的数据在对象内部没有堆指针可以窃取移动构造执行的是内联缓冲区拷贝memcpy 15 字节不是 O(1) 指针交换。面试官想听的展开只有非 SSO堆分配字符串的移动才是 O(1) 指针窃取。这是很多面试者会答错的细节。Q13C98 的 COW 为什么被废弃了一句话答案C11 规定operator[]不得使迭代器/引用失效而 COW 的非常量operator[]可能触发复制并导致失效此外 COW 的引用计数在多线程下是线程安全噩梦。COW 被事实上禁止。面试官想听的展开GCC 5.12015 年默认切换到非 COW 的 SSO 实现导致 ABI 断裂。四、性能与 string_view 篇Q14operator和的性能差异在哪里一句话答案operator返回新字符串每次调用都分配新内存并拷贝两个操作数原地追加只在容量不足时扩容。面试官想听的展开循环或链式operator会创建大量临时对象改用先reserve再/append可以快 3–5 倍。Q15string_view比const string快在哪什么时候不该用一句话答案当参数是const char*或字面量时const string会隐式构造临时std::string而string_view零开销。但如果参数总是std::string左值const string更轻量。面试官想听的展开string_view的生命周期绝不能超过所引用的字符串。不能用于需要存储字符串的场景否则会悬空。Q16移动一个std::string总是 O(1) 吗append(std::move(s))呢一句话答案非 SSO 字符串的移动是 O(1)SSO 字符串的移动是内联缓冲区拷贝。append(std::move(s))在目标容量不足时会退化为拷贝不是移动。面试官想听的展开移动语义的前提是目标能直接接管源的内存容量不够时只能拷贝。五、编码与实战篇Q17std::string的length()返回的是字符数还是字节数一句话答案字节数。std::string是basic_stringchar不识别 UTF-8。“你好”的length()是 6不是 2。面试官想听的展开中文在 UTF-8 下占 3 字节size()数的是字节。需要字符数用 utf8cpp 的utf8::distance()。Q18substr截取中文为什么会产生乱码一句话答案substr按字节截取UTF-8 是变长编码截在中文的中间字节上会产生不完整的 UTF-8 序列显示为乱码。面试官想听的展开安全做法是按 UTF-8 首字节模式识别字符边界累计到目标字符数后返回字节偏移再传给substr。Q19Windows 控制台中文乱码的根本原因是什么一句话答案Windows 控制台默认代码页是 936GBK而源文件通常是 UTF-8控制台按 GBK 解码 UTF-8 字节导致乱码。面试官想听的展开解决方式是SetConsoleOutputCP(65001)或chcp 65001让控制台切到 UTF-8MSVC 还需要/utf-8编译选项。Q20C 标准库能做 GBK 到 UTF-8 的转换吗一句话答案不能。std::string没有编码语义std::wstring_convert已弃用codecvt在 C20 被移除。面试官想听的展开Windows 用MultiByteToWideCharWideCharToMultiByte经 UTF-16 中转Linux 用iconv跨平台推荐 Boost.Locale 或 ICU。六、高频手撕题速览面试中除了问概念还经常要求手写代码。以下 5 道题出现频率最高题 1手写一个简化版 String 类实现构造、拷贝构造、赋值重载、析构。核心考察点深拷贝。默认拷贝是浅拷贝两个对象指向同一块堆内存析构时 double free。必须在拷贝构造和赋值重载中new char[]并strcpy。classString{char*data_;size_t size_;public:String(constchar*str):size_(strlen(str)){data_newchar[size_1];strcpy(data_,str);}String(constStringother):size_(other.size_){// 深拷贝data_newchar[size_1];strcpy(data_,other.data_);}Stringoperator(constStringother){if(this!other){char*tmpnewchar[other.size_1];strcpy(tmp,other.data_);delete[]data_;data_tmp;size_other.size_;}return*this;}~String(){delete[]data_;}};题 2为什么拷贝构造和赋值重载都要写只写一个行不行不行。拷贝构造用于创建新对象String b a;赋值重载用于已存在对象之间的赋值b a;。如果不写拷贝构造编译器生成浅拷贝版本两个对象共享堆内存析构时 double free。赋值重载同理。题 3strcpy和memcpy的区别是什么strcpy以\0为结束标记拷贝到\0为止memcpy按指定字节数拷贝不关心内容。如果字符串包含嵌入的\0必须用memcpy。题 4reserve和resize的手写实现要点是什么reserve只改 capacity分配新内存拷贝原内容释放旧内存更新指针和 capacitysize 不变。resize改 size如果新 size 旧 size填充\0或指定字符如果新 size 旧 size截断但不释放内存。题 5std::string为什么不能memsetstd::string是封装类型内部维护指针、size、capacity 等元数据。memset直接操作原始内存会破坏这些元数据导致未定义行为。需要清零用clear()或赋值为空字符串。七、面试思维导图std::string 面试 ├── 基础概念 │ ├── vs char*封装/自动管理/值语义 │ ├── size vs length等价 │ ├── size vs capacity有效字符 vs 已分配容量 │ └── data vs c_strC11 起都保证 \0 ├── 容量管理 │ ├── reserve vs resize容量 vs 长度 │ ├── clear 不释放容量 │ ├── shrink_to_fit 非绑定 │ └── 扩容策略2 倍 / 1.5 倍 ├── 底层布局 │ ├── SSO15 字符libstdc/MSVC/ 22 字符libc │ ├── sizeof32 字节libstdc/MSVC/ 24 字节libc │ ├── COW 被 C11 事实禁止 │ └── 连续内存保证C11 起 ├── 性能 │ ├── operator 临时对象 │ ├── reserve 预分配 │ ├── string_view 生命周期陷阱 │ └── append(std::move) 容量不足退化 ├── 编码 │ ├── length() 返回字节数 │ ├── substr 按字节截取中文会乱 │ └── GBK ↔ UTF-8 需系统 API/iconv └── 手撕题 ├── 深拷贝 String 类 ├── strcpy vs memcpy ├── reserve/resize 实现 └── 为什么不能 memset八、总结std::string 面试的核心考点可以归纳为三层递进第一层会用——size/capacity区别、reserve/resize区别、find返回npos必须判断。第二层懂底层——SSO 容量15/22、sizeof32/24、COW 为什么被禁、移动语义的真实边界SSO 不是 O(1)。第三层能实战——string_view生命周期陷阱、UTF-8 截断乱码、GBK/UTF-8 转换、深拷贝手撕实现。面试中如果被问住先给一句话答案再展开细节。面试官问sizeof(std::string)不要只答一个数字接着补一句“libstdc 是 32 字节libc 是 24 字节差异在于 libc 用指针最低位标记 SSO 模式省了一个字段”。这句话能直接把你和其他候选人区分开。系列导航上一篇《中文乱码终结者C std::string 的 UTF-8 实战避坑指南》下一篇暂无系列完结评论区互动这 20 问里你被哪一问卡住过是 SSO 容量、string_view生命周期还是手撕深拷贝评论区说说我看看要不要出一篇“手撕 String 类完整实现”的番外。参考资料cppreference:std::basic_stringC Standard [string.require], [string.access], [string.capacity]libstdcbasic_string.hSSO 容量 15libcstring头文件SSO 容量 22
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。