前言

c++ 标准库(STL)中,std::vectorstd::list 都是最常用的序列容器,它们都支持 push_back、insert、erase、begin()/end() 等相似接口,看起来“用法差不多”。但如果只停留在接口层面,就很容易在性能瓶颈出现时踩坑。本篇文章将一步步帮你彻底搞清楚二者的本质区别~~

一、底层数据结构(这是理解一切差异的根源)

std::vector< T >:动态数组。
它在连续的一块内存上存储元素,就像一个可以自动扩容的普通数组。当元素数量超过当前容量时,会向操作系统申请一块更大的连续内存,把旧元素搬过去(复制或移动),再释放旧内存。

std::list< T >:双向链表。
每个元素都是一个独立的节点,节点结构大致为:

struct Node{
T data;
Node* prev;
Node* next;
};

节点之间通过指针链接,内存完全不连续。list 内部只维护头尾指针(head 和 tail),无需连续内存块。

二、内存布局与分配策略

  1. vector:
  • 元素连续存放,相邻元素地址差正好是 sizeof(T)。
  • 维护两个关键值:size()(当前元素个数)和 capacity()(已分配空间能容纳的最大元素个数),始终满足 size() ≤ capacity()。
  • 扩容策略(实现相关,通常是 1.5~2 倍增长):当 size() == capacity() 时,重新分配更大内存(典型 2 倍),并把所有元素移动过去。
  • 缓存友好:CPU 读取连续内存时会自动预取,命中率极高。
  1. list:
  • 每个节点单独向堆申请内存(new Node),节点之间可能散布在堆的任意位置。
  • 每个节点至少额外占用 2 个指针(64 位系统 16 字节),加上可能的内存对齐开销,内存占用远大于 vector。
  • 插入/删除无需搬动其他节点,只需改 2~4 个指针即可。

vector 更“省内存 + 快访问”,list 更“灵活但浪费空间”。

三、操作时间复杂度对比

操作 std::vector std::list jieshi
随机访问 v[i] / *it O(1) O(n) vector 直接指针运算,list 只能从头/尾遍历
尾部插入 push_back 摊销 O(1) O(1) vector 偶尔扩容导致摊销
尾部删除 pop_back O(1) O(1)
头部插入 push_front O(n) O(1) vector 需要整体右移
头部删除 pop_front O(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.插入/删除后的迭代器有效性

vector:
insert/erase:可能导致所有迭代器、指针、引用全部失效(因为可能触发 reallocation)。
即使不 reallocation,后面的元素也会前移,指向后面元素的迭代器会“错位”。
#:reallocation 指的是动态数组的内存重新分配。

list:
insert/erase:只有指向被删除元素的迭代器失效,其他所有迭代器、指针、引用全部保持有效(这是链表的最大优势)。

所以在循环中边遍历边删除时,list 可以安全地 it = lst.erase(it);,vector 必须小心处理索引或使用 erase 返回的迭代器。

五、其他特性与成员函数差异

  1. vector 独有(动态数组特权):
  • reserve(n):提前分配容量,避免反复 reallocation。
  • capacity()、shrink_to_fit()(C++11)。
  • data():返回底层数组指针,可直接传给 C API。
  • 支持 operator[](不检查越界)和 at()(抛异常)。
  1. list 独有(链表特权):
  • splice:O(1) 把另一个 list 的子链“剪切”过来(无需复制元素)。
  • merge、remove、remove_if、reverse、unique 等链表专用算法。
  • sort():使用稳定的归并排序(std::sort 无法用于 list)。

共同点:两者都支持 emplace_back、emplace(C++11 原地构造)、移动语义(高效转移)。

六、内存占用与缓存性能

假设 sizeof(T) = 8 字节,存储 10000 个元素:

vector:约 80KB + 少量元数据。
list:约 80KB(数据)+ 10000×16 字节(指针)≈ 240KB,3 倍内存。

缓存命中率:

vector 在顺序遍历时几乎 100% 命中 L1/L2 缓存。
list 每个节点跳转可能导致缓存失效,性能差距可达 5~10 倍。

七、适用场景

  1. 选择 std::vector 的情况:
  • 需要频繁随机访问(下标、排序、二分查找)。
  • 主要在尾部 push/pop。
  • 元素数量较多,对内存和缓存敏感。
  • 需要与 C 语言 API 交互(data())。
  • 典型场景:游戏中的实体列表、日志缓冲json 解析结果、科学计算数组等。

2.选择 std::list 的情况:

  • 极其频繁地在任意位置插入/删除(尤其链表中间)。
  • 需要稳定迭代器(删除一个元素后,其他迭代器不能失效)。
  • 元素本身很大,移动代价高(list 只改指针)。
  • 典型场景:LRU 缓存的链表实现、音乐播放器的播放队列(频繁切歌)、多线程任务调度队列(频繁插入/移除任务)。

八、代码示例

#include <vector>
#include <list>
#include <iostream>
using namespace std;
int main()
{
vector<int> v = {1,2,3,4,5};
list<int> 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,不会失效
cout<<"vector[2] ="<<v[2]<<endl;  // 999(O(1))
// list 不能写 l[2],必须遍历
return 0;
}

总结:
vector = 动态数组:连续内存 -》 随机访问快、缓存友好、内存省,但中间操作慢。
list = 双向链表:指针链接 -》 任意位置修改快、迭代器稳定,但随机访问慢、内存占用高。

到此这篇关于C++ 中 std::vector 和 std::list 的区别的文章就介绍到这了,更多相关C++ std::vector 和 std::list 区别内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

声明:本站(华域联盟www.cnhackhy.com)所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。