C++ 核心数据结构 基础概念(二)
文章目录一、STL容器分类总览二、核心容器特性、用法与高频误区2.1 std::array静态定长数组2.2 std::vector动态数组2.3 std::list双向链表2.4 std::set / std::multiset std::map / std::multimap有序红黑树容器2.5 std::unordered_map / unordered_set哈希容器2.6 容器适配器三、C20 三路比较运算符 四、工程编程规范4.1 Vector::clear 不释放内存4.2 不要跨线程使用stl容器4.3 谨慎使用std::vectorbool4.4 勿滥用std::map4.5 不要在循环中使用find4.6 不要在函数中返回std::move五、拓展阅读 C11~23进阶知识点5.1 左值 右值 移动语义5.2 智能指针5.3 现代C错误处理工具5.4 拓展学习资源HackingCPP六、容器选型速查七、核心避坑总结一、STL容器分类总览C标准容器分为四大类顺序容器std::array、std::vector、std::deque、std::list、std::forward_list有序关联容器底层红黑树std::set/std::multiset、std::map/std::multimap无序关联容器底层哈希表std::unordered_set/unordered_multiset、std::unordered_map/unordered_multimap容器适配器std::stack、std::queue、std::priority_queue依赖其他容器实现非原生容器二、核心容器特性、用法与高频误区2.1 std::array静态定长数组底层特性栈内存、编译期固定长度、连续原生内存长度N是模板参数arrayint,3和arrayint,5属于不同类型支持随机访问O(1)at()带越界检查抛异常[]无检查越界为UB适用场景固定长度、轻量、高频遍历的常量数组误区❌ 不能动态修改size不可当作动态数组使用❌ 存放超大数组导致栈溢出✅ 可用.data()获取原生指针不要隐式转为C风格数组2.2 std::vector动态数组底层特性堆上连续内存数组自动扩容通常翻倍扩容随机访问O(1)、缓存友好尾部增删均摊O(1)中间/头部增删O(n)扩容/重分配后迭代器、指针、引用会失效核心接口区分push_back(val)构造临时对象后拷贝/移动入容器存在冗余拷贝开销emplace_back(参数...)原地直接构造对象减少拷贝开销优先使用reserve(n)仅修改capacity总容量不改变size有效元素数量、不构造/析构元素用于提前预分配内存减少反复扩容开销resize(n)修改size有效元素数量新增/销毁元素改变实际元素数目clear()仅将size置0、析构元素不释放底层堆内存capacity不变shrink_to_fit()释放多余内存匹配当前size关键误区vector::clear不释放内存如需释放内存可使用v.clear(); v.shrink_to_fit();或std::vectorint().swap(v);std::vectorbool是特殊特化位容器不是普通bool数组底层按bit存储返回特殊代理对象、无法正常bool引用、迭代器行为异常工程中尽量改用vectorchar不要下标访问reserve预留但未resize的区域会越界UB避免频繁头部/中间插入删除性能极差2.3 std::list双向链表底层特性双向循环链表、非连续内存已知迭代器位置插入/删除O(1)不会导致其他迭代器失效不支持随机访问查找/下标访问O(n)缓存不友好、内存开销大误区❌ 不要用全局std::sort应使用成员函数lst.sort()❌ 不适合高频遍历、查找、随机访问场景✅ 适合频繁中间增删、极少查找的场景补充forward_list单向链表开销更小仅可正向遍历2.4 std::set / std::multiset std::map / std::multimap有序红黑树容器底层特性红黑平衡二叉搜索树稳定O(log n)读写默认升序有序set元素唯一multiset允许重复元素mapK,Vkey唯一键值对multimap允许重复key无operator[]key为const不可直接修改会破坏红黑树有序结构如需修改应先删除再插入核心误区map::operator[]key不存在时会自动插入默认value污染数据查询key是否存在用find()/ C20contains()multiset/multimaperase(值)会删除全部匹配元素仅删单个元素要传入迭代器版本erase❗勿滥用std::map无排序、区间查找需求时map O(log n)性能较差固定下标场景优先array/vector高频查询用unordered_map自定义类型放入set/map需提供正确严格弱序比较可配合C20三路比较2.5 std::unordered_map / unordered_set哈希容器底层特性哈希表链地址法无序存储平均读写O(1)大量哈希冲突时最坏退化为O(n)rehash会导致迭代器失效依赖哈希函数相等判断误区❌ 自定义类型直接使用缺少hash函数会编译报错需手动提供哈希函数和重载❌ 依赖遍历顺序遍历顺序无标准保证❌ 浮点数NaN做key相等语义异常✅ 适合高频单点查询、无需有序遍历的场景2.6 容器适配器std::stack默认底层dequeLIFO后进先出仅栈顶可访问禁止遍历内部元素破坏语义std::queue默认底层dequeFIFO先进先出仅首尾可访问std::priority_queue底层vector堆完全二叉树最值优先弹出插入/弹出O(log n)不要直接修改底层数组破坏堆结构三、C20 三路比较运算符 ✅ 语法#includecomparestructA{intx;std::string y;autooperator(Aconst)constdefault;booloperator(Aconst)constdefault;};含义默认逐成员三路比较编译器自动生成支持四种比较运算返回类型std::strong_ordering强序int、string等可完全判等/大小std::weak_ordering弱序可判大小、存在等价非等同std::partial_ordering偏序含NaN浮点数存在不可比较情况误区❌ 盲目 default用于带指针/资源句柄的类会发生浅比较错误✅ 仅适用于纯值结构体配套默认operator保证等值语义完整✅ 编译需开启C20-stdc20引入compare头文件四、工程编程规范4.1 Vector::clear 不释放内存原理clear仅析构元素、size0capacity容量不变底层堆内存不释放会造成常驻内存占用解决方案v.clear(); v.shrink_to_fit();或空容器swap释放内存4.2 不要跨线程使用stl容器核心STL容器默认非线程安全无内置锁保护✅ 多线程只读安全❌ 一写多读/多线程同时写容器结构损坏、迭代器失效、崩溃/数据错乱UB✅ 有写操作时手动加锁std::mutex做同步避免全局STL容器跨线程读写4.3 谨慎使用std::vector本质是bit位打包特化容器不是普通bool数组代理引用、迭代器异常、不符合常规数组行为✅ 替代方案std::vectorchar4.4 勿滥用std::map✅ 若没有排序需求则使用array/vector或unordered_mapmap底层红黑树O(log n)读写伴随树旋转开销性能弱于数组/哈希容器✅ 选型固定下标小数据用array/vector高频无序查找用unordered_map有序/区间查找才用map4.5 不要在循环中使用findstd::find为O(n)线性查找嵌套循环使用会整体变为O(n²)大数据性能极差// ❌ 错误写法for(autox:vec){if(find(vec2.begin(),vec2.end(),x)!vec2.end()){}}✅ 优化预处理查找集合存入unordered_set实现O(1)查找4.6 不要在函数中返回std::moveC11起RVO/NRVO返回值优化可自动优化局部对象返回零开销移动// ❌ 错误写法破坏RVO优化std::stringfunc(){std::string stest;returnstd::move(s);}// ✅ 正确写法std::stringfunc(){std::string stest;returns;}✅ 仅对非局部变量、外部传入参数才酌情使用std::move转移所有权五、拓展阅读 C11~23进阶知识点5.1 左值 右值 移动语义左值有变量名、可取地址、生命周期较长普通局部/全局变量右值临时对象、字面量、无名字、不可长期取地址临时表达式返回值std::move将左值强制转为右值转移资源所有权指针/句柄避免昂贵深拷贝是emplace_back、移动构造底层基础完美转发std::forwardT保持原值类别用于模板参数转发5.2 智能指针std::unique_ptr独占所有权不可拷贝、可移动首选性能最优std::shared_ptr共享所有权引用计数管理适合多线程共享资源存在计数开销std::weak_ptr弱引用不增加shared_ptr引用计数解决循环引用内存泄漏问题5.3 现代C错误处理工具✅ std::optional (C17)语义存储“存在有效值”或“无有效值”两种状态替代魔法值、nullptr返回值用途查找、解析函数显式标记返回值是否有效类型安全#includeoptionalstd::optionalintfind_val(intkey);✅ std::expected (C23)语义要么返回正常结果T要么返回错误信息E兼顾性能与错误信息用途显式错误处理减少异常开销编译期可检查错误分支#includeexpectedstd::expectedint,std::stringcalc(intx);5.4 拓展学习资源HackingCPPan organized collection of links to topic articlesabout learning modern, idiomatic C(roughly) ordered by increasing level of C proficiency (top to bottom)many articles are based on slide decks taken from an undergraduate C crash course网址https://hackingcpp.com/cpp/beginners_guide六、容器选型速查固定长度轻量存储array通用存储、随机访问、高频遍历vector频繁中间增删、极少查找list有序遍历、区间查找、有序去重map/set高频单点查询、无需排序unordered_map/unordered_set两端高频增删dequeLIFO/FIFO逻辑stack/queue最值优先、TopKpriority_queue七、核心避坑总结vector clear不清内存vector存在特殊坑区分reserve/resize语义STL容器默认非线程安全跨线程读写必须加锁同步无排序需求不要滥用map优先array/vector/unordered_map避免嵌套循环std::find防止O(n²)性能退化不盲目return std::move尊重编译器RVO优化C20三路比较默认生成仅适用于纯值结构体现代C优先用optional/expected做安全错误处理减少魔法值和裸指针emplace_back优先于push_back减少对象拷贝开销