C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++中std::vector Vs std::deque VS std::list

C++中std::vector Vs std::deque VS std::list的对比分析

作者:点云SLAM

本文对比了C++中vector、deque和list的核心差异,重点分析内存布局、性能特性及迭代器失效规则,建议优先使用vector(缓存友好、高效遍历),deque适用于双端操作,list仅在需稳定迭代器或O(1)节点移动时使用,强调避免频繁小分配与迭代器失效风险

1) 核心差异速览

2) 内存布局与缓存局部性(关键影响性能)

3) 算法复杂度(常见操作 Big-O)

(常见且直观版本)

操作vectordequelist
随机访问 operator[]O(1)(连续)O(1)(分段)O(n)
push_back摊还 O(1)(可能重分配)摊还 O(1)O(1)
push_frontO(n)(要移动元素)摊还 O(1)O(1)
插入/删除 中间位置O(n)(移动元素)O(n)(移动/拷贝)O(1)(若已知迭代器)
splice / 跨容器移动O(1)(不复制元素)
遍历(线性访问成本)最快(缓存)中等最慢(指针跳转)

注:

vector 的尾部 push_back摊还 O(1)(因为扩容策略),但发生重分配时会拷贝/移动全部元素。

deque 在两端插入通常是 O(1),但插入可能导致 map 调整。list 的插入/删除在已定位节点时是真正 O(1)。(表中结论与 cppreference 的复杂度说明一致)

4) 迭代器 / 指针 / 引用失效规则(非常重要)

这是容易出 bug 的地方,下面列出常见操作如何影响已有的迭代器/指针/引用(以当前标准行为为准)。

std::vector

std::deque

规则稍复杂,总结常见点:

std::list

5) 典型使用场景与实战建议(什么时候选哪个)

优先选择 std::vector

选择 std::deque

选择 std::list

6) 常见陷阱 & 优化技巧(实战干货)

删除元素时的正确写法(在遍历中安全删元素):

vector / deque

for (auto it = c.begin(); it != c.end(); ) {
    if (should_erase(*it)) it = c.erase(it); // erase 返回下一个迭代器(C++11 起)
    else ++it;
}

list

for (auto it = l.begin(); it != l.end(); ) {
    if (cond) it = l.erase(it); // O(1),其他迭代器不受影响
    else ++it;
}

erase-remove 惯用法:对 vector 批量删除满足条件的元素应该使用:

v.erase(std::remove_if(v.begin(), v.end(), pred), v.end());

这比在循环中逐个 erase 快很多。

7) 代码示例 — 常见用法与陷阱演示

预分配避免重分配(vector)

std::vector<MyType> v;
v.reserve(1000);           // 预分配,避免扩容导致的整体移动
for (...) v.emplace_back(...); // in-place 构造,避免拷贝

遍历时删除(安全写法)

// vector / deque
for (auto it = v.begin(); it != v.end(); ) {
    if (need_erase(*it)) it = v.erase(it);
    else ++it;
}

// list
for (auto it = l.begin(); it != l.end(); ) {
    if (need_erase(*it)) it = l.erase(it); // 只使被删节点的迭代器失效
    else ++it;
}

list::splice(O(1) 移动节点,不复制)

std::list<int> a{1,2,3,4}, b{10,11};
// 把 a 中的 [2,4) 移到 b 的开头
auto first = std::next(a.begin()); // 指向 2
auto last  = std::next(first, 2);  // 指向 4 (不含)
b.splice(b.begin(), a, first, last); // O(1)

8) 快速参考表(便于记忆)

9)std::queue应用示例

下面是一个 C++11/17 标准库实现的多线程生产者-消费者模型完整示例

采用 std::threadstd::mutexstd::condition_variable,队列用 std::queue 封装,支持多生产者、多消费者。

示例代码

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>

// 线程安全队列
template <typename T>
class ThreadSafeQueue {
public:
    void push(T value) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            queue_.push(std::move(value));
        }
        cv_.notify_one(); // 唤醒一个等待的消费者
    }

    T pop() {
        std::unique_lock<std::mutex> lock(mtx_);
        cv_.wait(lock, [this]{ return !queue_.empty() || done_; });
        if (queue_.empty()) {
            return T(); // 若结束且队列空,返回默认值
        }
        T value = std::move(queue_.front());
        queue_.pop();
        return value;
    }

    void shutdown() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            done_ = true;
        }
        cv_.notify_all(); // 唤醒所有等待线程
    }

private:
    std::queue<T> queue_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool done_ = false;
};

// ------------------- 生产者/消费者测试 -------------------
void producer(ThreadSafeQueue<int>& q, int id, int count) {
    for (int i = 0; i < count; i++) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟耗时
        int value = id * 100 + i;
        q.push(value);
        std::cout << "[Producer " << id << "] produced: " << value << "\n";
    }
}

void consumer(ThreadSafeQueue<int>& q, int id) {
    while (true) {
        int value = q.pop();
        if (value == 0 && q.pop() == 0) break; // 简单退出条件(可换为特殊结束标记)
        if (value != 0) {
            std::cout << "    [Consumer " << id << "] consumed: " << value << "\n";
        }
    }
}

int main() {
    ThreadSafeQueue<int> q;

    // 启动多个生产者和消费者
    std::thread p1(producer, std::ref(q), 1, 5);
    std::thread p2(producer, std::ref(q), 2, 5);

    std::thread c1(consumer, std::ref(q), 1);
    std::thread c2(consumer, std::ref(q), 2);

    // 等生产者结束
    p1.join();
    p2.join();

    // 通知消费者结束
    q.shutdown();

    c1.join();
    c2.join();

    std::cout << "All threads finished.\n";
    return 0;
}

运行逻辑

  1. 生产者线程 持续往队列里放任务 (push)。
  2. 消费者线程 调用 pop,若队列为空则阻塞等待。
  3. shutdown() 用于通知消费者退出。
  4. std::condition_variable 保证高效等待,而不是忙轮询。

输出示例(顺序可能不同,因为多线程)

[Producer 1] produced: 100
[Producer 2] produced: 200
    [Consumer 1] consumed: 100
    [Consumer 2] consumed: 200
[Producer 1] produced: 101
[Producer 2] produced: 201
    [Consumer 1] consumed: 101
    [Consumer 2] consumed: 201
...
All threads finished.

10)总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文