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

 更新时间:2026年03月31日 09:26:24   作者:是娇娇公主~  
本文详细介绍了C++标准库中的std::vector和std::list的区别,从底层数据结构、内存布局与分配策略、操作时间复杂度对比、迭代器有效性与类别、其他特性与成员函数差异、内存占用与缓存性能等方面进行了阐述,感兴趣的朋友跟随小编一起看看吧

前言

在 C++ 标准库(STL)中,std::vector 和 std::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::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.插入/删除后的迭代器有效性

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 区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言中的函数指针学习笔记

    C语言中的函数指针学习笔记

    这篇文章主要介绍了C语言中的函数指针的一些学习知识点记录,文中作者整理了一些比较interesting的函数指针用法,需要的朋友可以参考下
    2016-04-04
  • 指向类成员函数的指针其实并非指针

    指向类成员函数的指针其实并非指针

    对于指向类成员的指针,必须紧记,指向类成员(非static)的指针并非指针
    2013-08-08
  • Qt中定时器 QTimerEvent 和 QTimer的使用

    Qt中定时器 QTimerEvent 和 QTimer的使用

    Qt框架中的定时器功能主要包括两种实现方式,包括QTimerEvent和QTimer,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-01-01
  • C++实现LeetCode(309.买股票的最佳时间含冷冻期)

    C++实现LeetCode(309.买股票的最佳时间含冷冻期)

    这篇文章主要介绍了C++实现LeetCode(309.买股票的最佳时间含冷冻期),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • 老程序员教你一天时间完成C语言扫雷游戏

    老程序员教你一天时间完成C语言扫雷游戏

    这篇文章主要为大家详细介绍了C语言实现扫雷游戏初级版,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • C++实现学生选课系统的思路与详细过程

    C++实现学生选课系统的思路与详细过程

    C语言是在国内外广泛使用的一种计算机语言,下面这篇文章主要给大家介绍了关于C++实现学生选课系统的思路与详细过程,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • C语言字符函数isalnum()和iscntrl()详解

    C语言字符函数isalnum()和iscntrl()详解

    大家好,本篇文章主要讲的是C语言字符函数isalnum()和iscntrl()详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-02-02
  • 漫谈C++哈夫曼树的原理及实现

    漫谈C++哈夫曼树的原理及实现

    给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。本文将通过图片为大家详细讲讲C++哈夫曼树的原理及实现,需要的可以参考一下
    2022-08-08
  • C++ Boost实现异步端口扫描器详解

    C++ Boost实现异步端口扫描器详解

    端口扫描是一种用于识别目标系统上哪些网络端口处于开放、关闭或监听状态的网络活动,本文将运用Boost框架实现一个基于TCP的扫描工具,有需要的小伙伴可以参考下
    2023-11-11
  • C语言数据结构通关时间复杂度和空间复杂度

    C语言数据结构通关时间复杂度和空间复杂度

    对于一个算法,其时间复杂度和空间复杂度往往是相互影响的,当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间,这篇文章主要给大家介绍了关于C语言时间复杂度、空间复杂度的相关资料,需要的朋友可以参考下
    2022-04-04

最新评论