【C++进阶】顺序容器

目录

一、顺序容器概述

[1.1 vector](#1.1 vector)

[1.2 deque](#1.2 deque)

[1.3 list](#1.3 list)

[1.4 forward_list](#1.4 forward_list)

[1.5 array](#1.5 array)

[二、vector 容器](#二、vector 容器)

[2.1 vector 的基本使用](#2.1 vector 的基本使用)

[2.2 vector 的常用操作](#2.2 vector 的常用操作)

[2.3 vector 的内存管理](#2.3 vector 的内存管理)

[三、deque 容器](#三、deque 容器)

[3.1 deque 的基本使用](#3.1 deque 的基本使用)

[3.2 deque 的常用操作](#3.2 deque 的常用操作)

[四、list 容器](#四、list 容器)

[4.1 list 的基本使用](#4.1 list 的基本使用)

[4.2 list 的常用操作](#4.2 list 的常用操作)

[五、forward_list 容器](#五、forward_list 容器)

[5.1 forward_list 的基本使用](#5.1 forward_list 的基本使用)

[5.2 forward_list 的常用操作](#5.2 forward_list 的常用操作)

[六、array 容器](#六、array 容器)

[6.1 array 的基本使用](#6.1 array 的基本使用)

[6.2 array 的常用操作](#6.2 array 的常用操作)

七、选择合适的顺序容器

八、进阶技巧与注意事项

[8.1. 迭代器失效问题](#8.1. 迭代器失效问题)

[8.2. 性能优化技巧](#8.2. 性能优化技巧)

[8.3. 容器性能与内存管理](#8.3. 容器性能与内存管理)

[8.4. C++11/14/17新特性](#8.4. C++11/14/17新特性)

九、常见误区解答

十、总结

十一、参考资料


在C++标准模板库(STL)中,容器是非常重要的工具。容器可以帮助我们管理一组对象,并且提供了各种操作这些对象的方法。顺序容器(Sequential Containers)是 C++ 标准库中的一类容器,它们按照元素插入的顺序来存储元素,并且可以通过元素的位置来访问它们。本文详细介绍 C++ 中的顺序容器,包括 vectordequelistforward_listarray,并提供相应的代码示例。

一、顺序容器概述

顺序容器提供了高效的元素存储和访问方式。不同的顺序容器在性能和使用场景上有所差异,主要体现在插入、删除元素的效率,以及随机访问的能力上。以下是 C++ 标准库中常见的顺序容器:

1.1 vector

vector 是最常用的顺序容器,它类似于动态数组。vector 可以自动调整大小以容纳更多的元素,并且支持随机访问。在尾部插入和删除元素的效率很高,但在中间或头部插入和删除元素的效率较低。

1.2 deque

deque 是双端队列,它支持在头部和尾部高效地插入和删除元素,同时也支持随机访问。与 vector 不同,deque 在头部插入和删除元素的效率也很高。

1.3 list

list 是双向链表,它支持在任意位置高效地插入和删除元素,但不支持随机访问。如果需要频繁地在中间插入或删除元素,list 是一个不错的选择。

1.4 forward_list

forward_list 是单向链表,它只支持单向遍历,不支持反向遍历。forward_list 在插入和删除元素方面比 list 更高效,但功能相对较少。

1.5 array

array 是固定大小的数组,它的大小在编译时就已经确定。array 支持随机访问,并且在性能上与普通数组相当,但提供了更多的标准库功能。

下面是一个简单的表格对比这些顺序容器的特点:

容器类型 随机访问 头部插入 / 删除 尾部插入 / 删除 中间插入 / 删除
vector 支持 低效率 高效率 低效率
deque 支持 高效率 高效率 低效率
list 不支持 高效率 高效率 高效率
forward_list 不支持 高效率 不支持 高效率
array 支持 不支持 不支持 不支持

二、vector 容器

2.1 vector 的基本使用

vector 位于 <vector> 头文件中,使用时需要包含该头文件。以下是一个简单的 vector 使用示例:

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    // 创建一个空的 vector
    std::vector<int> vec;

    // 向 vector 中添加元素
    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);

    // 访问 vector 中的元素
    for (int i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

首先创建了一个空的 vector,然后使用 push_back 方法向 vector 中添加元素,最后使用下标运算符 [] 访问 vector 中的元素。

2.2 vector 的常用操作

  • 获取元素个数 :可以使用 size() 方法获取 vector 中元素的个数。
  • 检查是否为空 :使用 empty() 方法检查 vector 是否为空。
  • 访问元素 :除了使用下标运算符 [] 访问元素外,还可以使用 at() 方法,at() 方法会进行边界检查,如果越界会抛出异常。
  • 插入元素 :可以使用 insert() 方法在指定位置插入元素。
  • 删除元素 :使用 erase() 方法删除指定位置的元素,使用 pop_back() 方法删除最后一个元素。

以下是一个包含更多操作的示例:

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 获取元素个数
    std::cout << "Size: " << vec.size() << std::endl;

    // 检查是否为空
    std::cout << "Is empty: " << (vec.empty() ? "Yes" : "No") << std::endl;

    // 使用 at() 方法访问元素
    std::cout << "Element at index 2: " << vec.at(2) << std::endl;

    // 在指定位置插入元素
    vec.insert(vec.begin() + 2, 10);
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除指定位置的元素
    vec.erase(vec.begin() + 3);
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除最后一个元素
    vec.pop_back();
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

2.3 vector 的内存管理

vector 会自动管理内存,当元素数量超过当前容量时,vector 会重新分配更大的内存空间,并将原有元素复制到新的内存空间中。可以使用 capacity() 方法获取 vector 当前的容量,使用 reserve() 方法预先分配一定的内存空间,以减少内存重新分配的次数。

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;

    std::cout << "Initial capacity: " << vec.capacity() << std::endl;

    // 预先分配内存
    vec.reserve(10);
    std::cout << "Capacity after reserve: " << vec.capacity() << std::endl;

    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }
    std::cout << "Capacity after adding elements: " << vec.capacity() << std::endl;

    return 0;
}

三、deque 容器

3.1 deque 的基本使用

deque 位于 <deque> 头文件中,使用时需要包含该头文件。deque 的使用方法与 vector 类似,但它支持在头部和尾部高效地插入和删除元素。

cpp 复制代码
#include <iostream>
#include <deque>

int main() {
    std::deque<int> deq;

    // 在尾部插入元素
    deq.push_back(10);
    deq.push_back(20);

    // 在头部插入元素
    deq.push_front(5);

    // 访问元素
    for (int num : deq) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

3.2 deque 的常用操作

deque 提供了与 vector 类似的操作,如 size()empty()at() 等,同时还提供了 push_front()pop_front() 方法用于在头部插入和删除元素。

cpp 复制代码
#include <iostream>
#include <deque>

int main() {
    std::deque<int> deq = {1, 2, 3, 4, 5};

    // 获取元素个数
    std::cout << "Size: " << deq.size() << std::endl;

    // 检查是否为空
    std::cout << "Is empty: " << (deq.empty() ? "Yes" : "No") << std::endl;

    // 在头部插入元素
    deq.push_front(0);

    // 在尾部插入元素
    deq.push_back(6);

    // 访问元素
    for (int num : deq) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除头部元素
    deq.pop_front();

    // 删除尾部元素
    deq.pop_back();

    // 访问元素
    for (int num : deq) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

四、list 容器

4.1 list 的基本使用

list 位于 <list> 头文件中,使用时需要包含该头文件。list 是双向链表,支持在任意位置高效地插入和删除元素。

cpp 复制代码
#include <iostream>
#include <list>

int main() {
    std::list<int> lst;

    // 在尾部插入元素
    lst.push_back(10);
    lst.push_back(20);

    // 在头部插入元素
    lst.push_front(5);

    // 遍历 list
    for (int num : lst) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

4.2 list 的常用操作

list 提供了一些特殊的操作,如 insert() 可以在指定位置插入元素,erase() 可以删除指定位置的元素,splice() 可以将一个 list 中的元素转移到另一个 list 中。

cpp 复制代码
#include <iostream>
#include <list>

int main() {
    std::list<int> lst1 = {1, 2, 3};
    std::list<int> lst2 = {4, 5, 6};

    // 在指定位置插入元素
    auto it = lst1.begin();
    ++it;
    lst1.insert(it, 10);

    // 遍历 lst1
    for (int num : lst1) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除指定位置的元素
    it = lst1.begin();
    ++it;
    lst1.erase(it);

    // 遍历 lst1
    for (int num : lst1) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 将 lst2 中的元素转移到 lst1 中
    lst1.splice(lst1.end(), lst2);

    // 遍历 lst1
    for (int num : lst1) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

五、forward_list 容器

5.1 forward_list 的基本使用

forward_list 位于 <forward_list> 头文件中,使用时需要包含该头文件。forward_list 是单向链表,只支持单向遍历。

cpp 复制代码
#include <iostream>
#include <forward_list>

int main() {
    std::forward_list<int> flst;

    // 在头部插入元素
    flst.push_front(10);
    flst.push_front(20);

    // 遍历 forward_list
    for (int num : flst) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

5.2 forward_list 的常用操作

forward_list 提供了一些特殊的操作,如 insert_after() 可以在指定位置之后插入元素,erase_after() 可以删除指定位置之后的元素。

cpp 复制代码
#include <iostream>
#include <forward_list>

int main() {
    std::forward_list<int> flst = {1, 2, 3};

    // 在指定位置之后插入元素
    auto it = flst.begin();
    flst.insert_after(it, 10);

    // 遍历 forward_list
    for (int num : flst) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除指定位置之后的元素
    it = flst.begin();
    flst.erase_after(it);

    // 遍历 forward_list
    for (int num : flst) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

六、array 容器

6.1 array 的基本使用

array 位于 <array> 头文件中,使用时需要包含该头文件。array 是固定大小的数组,需要在定义时指定数组的大小。

cpp 复制代码
#include <iostream>
#include <array>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    // 遍历 array
    for (int num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

6.2 array 的常用操作

array 提供了与普通数组类似的操作,如 size() 可以获取数组的大小,at() 可以访问指定位置的元素。

cpp 复制代码
#include <iostream>
#include <array>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    // 获取数组的大小
    std::cout << "Size: " << arr.size() << std::endl;

    // 访问指定位置的元素
    std::cout << "Element at index 2: " << arr.at(2) << std::endl;

    return 0;
}

七、选择合适的顺序容器

在选择顺序容器时,需要根据具体的需求来决定使用哪种容器。以下是一些选择的建议:

  • 如果需要随机访问元素,并且插入和删除操作主要在尾部进行,vector 是一个不错的选择。
  • 如果需要在头部和尾部都进行高效的插入和删除操作,并且也需要随机访问元素,deque 是合适的。
  • 如果需要频繁地在中间插入或删除元素,listforward_list 是更好的选择。如果只需要单向遍历,forward_list 可以提供更高的效率。
  • 如果数组的大小在编译时就已经确定,并且不需要动态调整大小,array 是一个不错的选择。

八、进阶技巧与注意事项

8.1. 迭代器失效问题

cpp 复制代码
// 错误示例:在循环中保存end迭代器
std::vector<int> vec = {1,2,3};
auto end_it = vec.end();
for(auto it = vec.begin(); it != end_it; ++it) {
    if(*it == 2) {
        vec.erase(it); // 导致end_it失效
        break;
    }
}

// 正确做法:实时获取end迭代器
for(auto it = vec.begin(); it != vec.end(); ) {
    if(*it == 2) {
        it = vec.erase(it); // 返回下一个有效迭代器
    } else {
        ++it;
    }
}

8.2. 性能优化技巧

  • 预留空间 :对vector/string使用reserve()预分配内存
  • 批量插入 :使用insert(iter, first, last)代替多次单次插入
  • emplace操作 :使用emplace_back()直接在容器内构造对象

8.3. 容器性能与内存管理

① 时间复杂度对比

操作容器 随机访问 头部插入 尾部插入 中间插入
vector O(1) O(n) O(1) O(n)
deque O(1) O(1) O(1) O(n)
list O(n) O(1) O(1) O(1)
forward_list O(n) O(1) O(n) O(1)

② 内存管理策略

vector扩容机制

cpp 复制代码
vector<int> v;
v.reserve(50); // 预分配内存
for(int i=0; i<100; ++i){
    v.push_back(i);
    std::cout << "Size: " << v.size() 
              << " Capacity: " << v.capacity() << std::endl;
}

扩容过程图示

cpp 复制代码
[元素][元素][元素][空] → 插入 → [元素][元素][元素][元素] → 扩容 → [元素][元素][元素][元素][空][空][空][空]

8.4. C++11/14/17新特性

  • 非成员begin/end:支持统一获取迭代器
  • 移动语义:提高容器操作效率
  • 结构化绑定:简化元素访问(C++17)

九、常见误区解答

Q:为什么vector中间插入比list慢?

A:vector采用连续内存存储,中间插入需要移动后续所有元素,时间复杂度O(n);而list通过指针调整即可完成,时间复杂度O(1)

Q:何时应该选择forward_list?

A:当只需要单向遍历且内存敏感时,forward_list比list节省约25%内存,但失去双向遍历能力

Q:array与原生数组的区别?

A:array提供边界检查(at()方法)、标准容器接口和自动内存管理,比原生数组更安全易用

十、总结

本文详细介绍了 C++ 中的顺序容器,包括 vectordequelistforward_listarray。每个容器都有其特点和适用场景,在实际编程中,需要根据具体的需求来选择合适的容器。通过合理使用顺序容器,可以提高程序的性能和可维护性。

十一、参考资料

  • **《C++ Primer(第 5 版)》**这本书是 C++ 领域的经典之作,对 C++ 的基础语法和高级特性都有深入讲解。

  • **《Effective C++(第 3 版)》**书中包含了很多 C++ 编程的实用建议和最佳实践。

  • 《C++ Templates: The Complete Guide(第 2 版)》 该书聚焦于 C++ 模板编程,而using声明在模板编程中有着重要应用,如定义模板类型别名等。

  • C++ 官方标准文档:C++ 标准文档是最权威的参考资料,可以查阅最新的 C++ 标准(如 C++11、C++14、C++17、C++20 等)文档。例如,ISO/IEC 14882:2020 是 C++20 标准的文档,可从相关渠道获取其详细内容。

  • cppreference.com:这是一个非常全面的 C++ 在线参考网站,提供了详细的 C++ 语言和标准库文档。

  • LearnCpp.com:该网站提供了系统的 C++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。

  • 《Effective STL》Scott Meyers

  • CppReference容器文档

  • 开源项目STL源码分析


相关推荐
郭涤生20 分钟前
The whole book test_《C++20Get the details》_notes
开发语言·c++·笔记·c++20
Jerry说前后端23 分钟前
剑指Offer(数据结构与算法面试题精讲)C++版——day6
开发语言·c++·面试
爱凤的小光28 分钟前
图漾相机——C#语言属性设置
开发语言·c#
martian66531 分钟前
NVM 多版本Node.js 管理全指南(Windows系统)
java·开发语言·windows·node.js
Yan-英杰34 分钟前
【百日精通JAVA | SQL篇 | 第四篇】约束
java·服务器·开发语言·数据库·人工智能·sql·mysql
梁下轻语的秋缘37 分钟前
每日c/c++题 备战蓝桥杯(求解三个数的最大公约数与最小公倍数)
c语言·c++·学习·算法·蓝桥杯
LoveXming37 分钟前
Qt采用多线程实现ABAB交叉打印
开发语言·qt
独好紫罗兰1 小时前
洛谷题单3-P1423 小玉在游泳-python-流程图重构
开发语言·python·算法
曦月逸霜1 小时前
第十四届蓝桥杯省赛真题解析(含C++详细源码)
c++·算法·蓝桥杯
愚润求学2 小时前
Linux开发工具——gcc
linux·服务器·开发语言