尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++ 中 std::vector 和 std::list 的区别详解

C++ 中 std::vector 和 std::list 的区别详解 前言在 C 标准库STL中std::vector 和 std::list 都是最常用的序列容器它们都支持 push_back、insert、erase、begin()/end() 等相似接口看起来“用法差不多”。但如果只停留在接口层面就很容易在性能瓶颈出现时踩坑。本篇文章将一步步帮你彻底搞清楚二者的本质区别~~一、底层数据结构这是理解一切差异的根源std::vector T 动态数组。它在连续的一块内存上存储元素就像一个可以自动扩容的普通数组。当元素数量超过当前容量时会向操作系统申请一块更大的连续内存把旧元素搬过去复制或移动再释放旧内存。std::list T 双向链表。每个元素都是一个独立的节点节点结构大致为12345structNode{T dataNode* prevNode* next}节点之间通过指针链接内存完全不连续。list 内部只维护头尾指针head 和 tail无需连续内存块。二、内存布局与分配策略vector元素连续存放相邻元素地址差正好是 sizeof(T)。维护两个关键值size()当前元素个数和 capacity()已分配空间能容纳的最大元素个数始终满足 size() ≤ capacity()。扩容策略实现相关通常是 1.5~2 倍增长当 size() capacity() 时重新分配更大内存典型 2 倍并把所有元素移动过去。缓存友好CPU 读取连续内存时会自动预取命中率极高。list每个节点单独向堆申请内存new Node节点之间可能散布在堆的任意位置。每个节点至少额外占用 2 个指针64 位系统 16 字节加上可能的内存对齐开销内存占用远大于 vector。插入/删除无需搬动其他节点只需改 2~4 个指针即可。vector 更“省内存 快访问”list 更“灵活但浪费空间”。三、操作时间复杂度对比操作std::vectorstd::listjieshi随机访问 v[i] / *itO(1)O(n)vector 直接指针运算list 只能从头/尾遍历尾部插入 push_back摊销 O(1)O(1)vector 偶尔扩容导致摊销尾部删除 pop_backO(1)O(1)-头部插入 push_frontO(n)O(1)vector 需要整体右移头部删除 pop_frontO(n)O(1)-中间插入/删除有迭代器O(n)O(1)vector 需要移动后续所有元素查找元素无序O(n)O(n)两者都需要线性遍历排序 std::sort极快连续内存极慢无法随机访问std::list 只能用自己的 sort()记忆总结vector随机访问快中间修改慢。list任意位置修改快随机访问慢。四、迭代器有效性与类别迭代器是 STL 容器的“指针”修改容器后迭代器可能失效这是很多Bug的源头。1.迭代器类别vector随机访问迭代器支持 it n、it[n]、it other 等。list双向迭代器仅支持 、–。2.插入/删除后的迭代器有效性vectorinsert/erase可能导致所有迭代器、指针、引用全部失效因为可能触发 reallocation。即使不 reallocation后面的元素也会前移指向后面元素的迭代器会“错位”。#reallocation 指的是动态数组的内存重新分配。listinsert/erase只有指向被删除元素的迭代器失效其他所有迭代器、指针、引用全部保持有效这是链表的最大优势。所以在循环中边遍历边删除时list 可以安全地 it lst.erase(it);vector 必须小心处理索引或使用 erase 返回的迭代器。五、其他特性与成员函数差异vector 独有动态数组特权reserve(n)提前分配容量避免反复 reallocation。capacity()、shrink_to_fit()C11。data()返回底层数组指针可直接传给 C API。支持 operator[]不检查越界和 at()抛异常。list 独有链表特权spliceO(1) 把另一个 list 的子链“剪切”过来无需复制元素。merge、remove、remove_if、reverse、unique 等链表专用算法。sort()使用稳定的归并排序std::sort 无法用于 list。共同点两者都支持 emplace_back、emplaceC11 原地构造、移动语义高效转移。六、内存占用与缓存性能假设 sizeof(T) 8 字节存储 10000 个元素vector约 80KB 少量元数据。list约 80KB数据 10000×16 字节指针≈ 240KB3 倍内存。缓存命中率vector 在顺序遍历时几乎 100% 命中 L1/L2 缓存。list 每个节点跳转可能导致缓存失效性能差距可达 5~10 倍。七、适用场景选择 std::vector 的情况需要频繁随机访问下标、排序、二分查找。主要在尾部 push/pop。元素数量较多对内存和缓存敏感。需要与 C 语言 API 交互data()。典型场景游戏中的实体列表、日志缓冲、JSON 解析结果、科学计算数组等。2.选择 std::list 的情况极其频繁地在任意位置插入/删除尤其链表中间。需要稳定迭代器删除一个元素后其他迭代器不能失效。元素本身很大移动代价高list 只改指针。典型场景LRU 缓存的链表实现、音乐播放器的播放队列频繁切歌、多线程任务调度队列频繁插入/移除任务。八、代码示例12345678910111213141516171819#include vector#include list#include iostreamusingnamespacestd;intmain(){vectorint v {1,2,3,4,5};listint l {1,2,3,4,5};// vector 中间插入慢v.insert(v.begin()2,999);// 1 2 999 3 4 5// list 中间插入快 迭代器不失效auto it l.begin();advance(it,2);// 指向第 3 个元素l.insert(it,999);// 1 2 999 3 4 5// 此时原 it 仍指向原来的 3不会失效coutvector[2] v[2]endl;// 999O(1)// list 不能写 l[2]必须遍历return0;}总结vector 动态数组连续内存 -》 随机访问快、缓存友好、内存省但中间操作慢。list 双向链表指针链接 -》 任意位置修改快、迭代器稳定但随机访问慢、内存占用高。
返回列表