(C++)STL标准库(vector动态数组)(list列表)(set集合)(map键值对)相关对比,基础教程

目录

C++四大容器深度解析:vector、list、set、map (8000字详解)

一、容器概述与基础概念

[1.1 STL容器分类](#1.1 STL容器分类)

[1.2 容器共性特征](#1.2 容器共性特征)

[1.3 复杂度表示法](#1.3 复杂度表示法)

二、vector(动态数组)

[2.1 基本特性](#2.1 基本特性)

[2.2 核心操作](#2.2 核心操作)

增加元素

删除元素

修改元素

查找元素

[2.3 性能分析](#2.3 性能分析)

[2.4 最佳实践](#2.4 最佳实践)

三、list(双向链表)

[3.1 基本特性](#3.1 基本特性)

[3.2 核心操作](#3.2 核心操作)

增加元素

删除元素

修改元素

查找元素

[3.3 特殊操作](#3.3 特殊操作)

[3.4 性能分析](#3.4 性能分析)

[3.5 最佳实践](#3.5 最佳实践)

四、set(有序集合)

[4.1 基本特性](#4.1 基本特性)

[4.2 核心操作](#4.2 核心操作)

增加元素

删除元素

查找元素

修改元素

[4.3 性能分析](#4.3 性能分析)

[4.4 最佳实践](#4.4 最佳实践)

五、map(有序映射)

[5.1 基本特性](#5.1 基本特性)

[5.2 核心操作](#5.2 核心操作)

增加元素

删除元素

修改元素

查找元素

[5.3 特殊操作](#5.3 特殊操作)

[5.4 性能分析](#5.4 性能分析)

[5.5 最佳实践](#5.5 最佳实践)

六、容器对比与选择指南

[6.1 功能对比矩阵](#6.1 功能对比矩阵)

[6.2 性能对比总结](#6.2 性能对比总结)

[6.3 选择决策树](#6.3 选择决策树)

[6.4 典型应用场景](#6.4 典型应用场景)

七、高级技巧与最佳实践

[7.1 自定义比较函数](#7.1 自定义比较函数)

[7.2 高效遍历与删除](#7.2 高效遍历与删除)

[7.3 容器适配器](#7.3 容器适配器)

[7.4 性能优化技巧](#7.4 性能优化技巧)

[7.5 C++17新特性](#7.5 C++17新特性)

八、总结与综合示例

[8.1 容器选择黄金法则](#8.1 容器选择黄金法则)

[8.2 综合应用示例:学生成绩系统](#8.2 综合应用示例:学生成绩系统)

[8.3 最终建议](#8.3 最终建议)

C++四大容器深度解析:vector、list、set、map (8000字详解)

一、容器概述与基础概念

1.1 STL容器分类

C++标准模板库(STL)提供了多种容器类型,主要分为两大类:

  • 序列容器:保持元素插入顺序

    • vector:动态数组

    • list:双向链表

    • deque:双端队列

    • array:固定大小数组(C++11)

    • forward_list:单向链表(C++11)

  • 关联容器:元素按键排序

    • set:唯一键集合

    • map:键值对集合

    • multiset:允许重复键的集合

    • multimap:允许重复键的键值对集合

1.2 容器共性特征

所有容器都支持以下操作:

cpp 复制代码
size_type size() const;      // 返回元素数量
bool empty() const;          // 检查容器是否为空
void clear();                // 清空容器
iterator begin();            // 返回起始迭代器
iterator end();              // 返回结束迭代器
void swap(container& other); // 交换两个容器内容

1.3 复杂度表示法

  • O(1):常数时间复杂度

  • O(n):线性时间复杂度

  • O(log n):对数时间复杂度

  • O(n log n):线性对数时间复杂度

二、vector(动态数组)

2.1 基本特性

  • 底层实现:动态分配的连续内存空间

  • 内存管理:当容量不足时,自动分配更大内存(通常是2倍扩容)

  • 访问方式:支持随机访问(下标操作)

  • 适用场景:需要快速随机访问,尾部操作频繁

2.2 核心操作

增加元素
cpp 复制代码
void push_back(const T& value);     // 尾部添加元素(拷贝)
void push_back(T&& value);          // 尾部添加元素(移动)
void emplace_back(Args&&... args);  // 尾部直接构造元素(C++11)
iterator insert(const_iterator pos, const T& value); // 指定位置插入

示例:

cpp 复制代码
vector<int> vec;
vec.push_back(10);              // 拷贝添加
vec.emplace_back(20);           // 直接构造
vec.insert(vec.begin() + 1, 15); // 在第二个位置插入15
// vec: [10, 15, 20]
删除元素
cpp 复制代码
void pop_back();                         // 删除尾部元素
iterator erase(const_iterator pos);      // 删除指定位置元素
iterator erase(const_iterator first, const_iterator last); // 删除范围

示例:

cpp 复制代码
vec.pop_back();         // 删除20
vec.erase(vec.begin()); // 删除10
// vec: [15]
修改元素
cpp 复制代码
T& operator[](size_type pos);       // 下标访问(无边界检查)
T& at(size_type pos);               // 下标访问(有边界检查)
void resize(size_type count);       // 改变容器大小
void reserve(size_type new_cap);    // 预留空间

示例:

cpp 复制代码
vec[0] = 100;         // 修改第一个元素
vec.at(0) = 200;      // 安全修改
vec.resize(5);        // 扩大容器,新增元素默认初始化
vec.reserve(100);     // 预留空间,避免多次扩容
查找元素
cpp 复制代码
// 使用标准算法查找
auto it = find(vec.begin(), vec.end(), value);
if (it != vec.end()) {
    // 找到元素
}

2.3 性能分析

操作 时间复杂度 说明
尾部插入 O(1) 均摊 偶尔需要扩容
头部插入 O(n) 需要移动所有元素
随机访问 O(1) 直接计算地址
中间插入 O(n) 需要移动后续元素
尾部删除 O(1)
头部删除 O(n) 需要移动所有元素
查找 O(n) 需要遍历

2.4 最佳实践

  1. 使用reserve()预分配空间减少扩容开销

  2. 优先使用emplace_back()而非push_back()

  3. 避免在中间位置频繁插入/删除

  4. 使用shrink_to_fit()释放多余内存

三、list(双向链表)

3.1 基本特性

  • 底层实现:双向链表节点

  • 内存管理:每个元素独立分配

  • 访问方式:顺序访问

  • 适用场景:频繁在任意位置插入/删除

3.2 核心操作

增加元素
cpp 复制代码
void push_front(const T& value);    // 头部添加
void push_back(const T& value);     // 尾部添加
void emplace_front(Args&&... args); // 头部直接构造
void emplace_back(Args&&... args);  // 尾部直接构造
iterator insert(const_iterator pos, const T& value); // 指定位置插入

示例:

cpp 复制代码
list<string> tasks;
tasks.push_back("Task1");
tasks.emplace_front("Urgent");
tasks.insert(++tasks.begin(), "Important");
// tasks: ["Urgent", "Important", "Task1"]
删除元素
cpp 复制代码
void pop_front();                   // 删除头部元素
void pop_back();                    // 删除尾部元素
iterator erase(const_iterator pos); // 删除指定位置
void remove(const T& value);        // 删除所有匹配值
void unique();                      // 删除连续重复值

示例:

cpp 复制代码
tasks.pop_front();          // 删除"Urgent"
tasks.remove("Task1");      // 删除所有"Task1"
tasks.unique();             // 删除连续重复值
修改元素
cpp 复制代码
// 通过迭代器修改
auto it = tasks.begin();
*it = "New Task";
查找元素
cpp 复制代码
// 使用标准算法查找
auto it = find(tasks.begin(), tasks.end(), value);

3.3 特殊操作

cpp 复制代码
void sort();                     // 排序
void merge(list& other);         // 合并有序列表
void splice(const_iterator pos, list& other); // 移动元素
void reverse();                  // 反转链表

示例:

cpp 复制代码
list<int> list1 = {1, 3, 5};
list<int> list2 = {2, 4, 6};
list1.merge(list2);      // list1: [1,2,3,4,5,6], list2为空
list1.reverse();         // [6,5,4,3,2,1]

3.4 性能分析

操作 时间复杂度 说明
任意位置插入 O(1) 只需修改指针
任意位置删除 O(1) 只需修改指针
随机访问 O(n) 需要遍历
查找 O(n) 需要遍历
排序 O(n log n) 归并排序实现

3.5 最佳实践

  1. 使用emplace系列函数提高效率

  2. 优先使用remove()进行值删除

  3. 使用splice()高效移动元素

  4. 避免频繁遍历大型链表

四、set(有序集合)

4.1 基本特性

  • 底层实现:红黑树(自平衡二叉搜索树)

  • 元素特性:唯一键、自动排序

  • 适用场景:需要有序唯一元素、快速查找

4.2 核心操作

增加元素
cpp 复制代码
pair<iterator, bool> insert(const T& value);
iterator insert(iterator hint, const T& value);
pair<iterator, bool> emplace(Args&&... args);

示例:

cpp 复制代码
set<int> numbers;
auto [it1, success1] = numbers.insert(10); // 成功插入
auto [it2, success2] = numbers.insert(10); // 失败,元素已存在
numbers.emplace(20); // 直接构造插入
删除元素
cpp 复制代码
size_type erase(const T& value);  // 按键删除
iterator erase(const_iterator pos); // 按位置删除
iterator erase(const_iterator first, const_iterator last); // 范围删除

示例:

cpp 复制代码
numbers.erase(10); // 删除键为10的元素
auto it = numbers.find(20);
if (it != numbers.end()) {
    numbers.erase(it);
}
查找元素
cpp 复制代码
iterator find(const T& value);       // 查找元素
size_type count(const T& value);     // 统计元素数量(0或1)
iterator lower_bound(const T& value); // 返回第一个不小于value的元素
iterator upper_bound(const T& value); // 返回第一个大于value的元素
pair<iterator, iterator> equal_range(const T& value); // 返回匹配范围

示例:

cpp 复制代码
set<int> scores = {60, 70, 80, 90, 100};
auto it = scores.find(80); // 查找80分
auto lb = scores.lower_bound(75); // 第一个>=75的元素(80)
auto ub = scores.upper_bound(85); // 第一个>85的元素(90)
修改元素
  • 元素不可修改(会破坏排序)

  • 需要先删除后插入:

cpp 复制代码
auto it = scores.find(70);
if (it != scores.end()) {
    int newScore = 75;
    scores.erase(it);
    scores.insert(newScore);
}

4.3 性能分析

操作 时间复杂度 说明
插入 O(log n) 平衡树插入
删除 O(log n) 平衡树删除
查找 O(log n) 平衡树查找
遍历 O(n) 中序遍历
范围查询 O(log n + k) k为结果集大小

4.4 最佳实践

  1. 使用emplace()直接构造元素

  2. 使用lower_bound()/upper_bound()进行范围查询

  3. 自定义比较器实现特殊排序规则

  4. 避免存储大对象(考虑指针或智能指针)

五、map(有序映射)

5.1 基本特性

  • 底层实现:红黑树(存储键值对)

  • 元素特性:键唯一、按键自动排序

  • 适用场景:键值对关联、按键快速查找

5.2 核心操作

增加元素
cpp 复制代码
pair<iterator, bool> insert(const value_type& value);
iterator insert(iterator hint, const value_type& value);
pair<iterator, bool> emplace(Args&&... args);
T& operator[](const Key& key); // 访问或插入
T& operator[](Key&& key);
T& at(const Key& key); // 带边界检查的访问

示例:

cpp 复制代码
map<int, string> students;
students.insert({101, "Alice"}); // 插入键值对
students.emplace(102, "Bob");    // 直接构造
students[103] = "Charlie";       // 使用下标操作符插入
删除元素
cpp 复制代码
size_type erase(const Key& key);  // 按键删除
iterator erase(const_iterator pos); // 按位置删除
iterator erase(const_iterator first, const_iterator last); // 范围删除

示例:

cpp 复制代码
students.erase(102); // 删除键为102的学生
auto it = students.find(103);
if (it != students.end()) {
    students.erase(it);
}
修改元素
cpp 复制代码
// 通过迭代器修改值
auto it = students.find(101);
if (it != students.end()) {
    it->second = "Alice Smith"; // 修改值
}

// 通过下标操作符修改
students[101] = "A. Smith";
查找元素
cpp 复制代码
iterator find(const Key& key);       // 查找键
size_type count(const Key& key);     // 统计键出现次数(0或1)
iterator lower_bound(const Key& key); 
iterator upper_bound(const Key& key);
pair<iterator, iterator> equal_range(const Key& key);

示例:

cpp 复制代码
auto it = students.find(101); // 查找学号101
if (students.count(102) > 0) {
    // 学号102存在
}

5.3 特殊操作

cpp 复制代码
// C++17 try_emplace 和 insert_or_assign
auto [it, inserted] = students.try_emplace(104, "David"); // 只在键不存在时插入
auto [it, inserted] = students.insert_or_assign(101, "Alice Brown"); // 插入或更新

5.4 性能分析

操作 时间复杂度 说明
插入 O(log n) 平衡树插入
删除 O(log n) 平衡树删除
查找 O(log n) 平衡树查找
下标访问 O(log n) 可能触发插入
遍历 O(n) 中序遍历

5.5 最佳实践

  1. 使用try_emplace()避免不必要的拷贝

  2. 使用insert_or_assign()实现更新或插入

  3. 使用自定义类型作为键时提供比较函数

  4. 使用at()进行安全访问(避免意外插入)

六、容器对比与选择指南

6.1 功能对比矩阵

特性 vector list set map
随机访问 ✓ (O(1))
快速插入/删除 尾部O(1) 任意O(1) O(log n) O(log n)
自动排序 ✓ (按键)
唯一元素 ✓ (按键)
键值对存储
内存连续性
缓存友好

6.2 性能对比总结

操作 vector list set map
头部插入 O(n) O(1) O(log n) O(log n)
尾部插入 O(1) O(1) O(log n) O(log n)
中间插入 O(n) O(1) O(log n) O(log n)
随机访问 O(1) O(n) O(n) O(n)
按键查找 O(n) O(n) O(log n) O(log n)
值删除 O(n) O(1) O(log n) O(log n)
范围查询 O(n) O(n) O(log n + k) O(log n + k)

6.3 选择决策树

  1. 需要键值对存储吗?

    • 是 → 选择map

    • 否 → 进入2

  2. 需要元素唯一且有序吗?

    • 是 → 选择set

    • 否 → 进入3

  3. 需要频繁在任意位置插入/删除吗?

    • 是 → 选择list

    • 否 → 进入4

  4. 需要随机访问或内存连续性吗?

    • 是 → 选择vector

    • 否 → 根据其他需求选择

6.4 典型应用场景

vector 适用场景:

  • 需要随机访问元素(如数学向量)

  • 尾部操作频繁(如栈实现)

  • 数据缓存(缓存友好)

  • 需要最小内存占用

cpp 复制代码
// 向量运算示例
vector<double> vec1 = {1.0, 2.0, 3.0};
vector<double> vec2 = {4.0, 5.0, 6.0};
vector<double> result;
transform(vec1.begin(), vec1.end(), vec2.begin(), 
          back_inserter(result), plus<double>());
// result: [5.0, 7.0, 9.0]

list 适用场景:

  • 高频中间位置插入/删除(如任务队列)

  • 需要高效合并/拆分(如大文件处理)

  • 实现LRU缓存

cpp 复制代码
// LRU缓存实现
list<pair<int, string>> cache;
unordered_map<int, list<pair<int, string>>::iterator> index;

void access(int key) {
    if (index.find(key) != index.end()) {
        // 移动到前端
        cache.splice(cache.begin(), cache, index[key]);
    }
}

set 适用场景:

  • 需要唯一元素集合(如用户ID)

  • 需要自动排序(如排行榜)

  • 需要快速存在性检查

cpp 复制代码
// 单词统计
set<string> dictionary;
string word;
while (cin >> word) {
    dictionary.insert(word);
}
cout << "Unique words: " << dictionary.size();

map 适用场景:

  • 键值对关联数据(如学生信息)

  • 需要按键快速查找

  • 需要按键排序

cpp 复制代码
// 学生管理系统
map<int, Student> students;
students.emplace(101, Student("Alice", 90.5));
students.emplace(102, Student("Bob", 85.0));

// 查找学生
auto it = students.find(101);
if (it != students.end()) {
    cout << it->second.name << ": " << it->second.score;
}

七、高级技巧与最佳实践

7.1 自定义比较函数

cpp 复制代码
// set自定义排序
struct CaseInsensitiveCompare {
    bool operator()(const string& a, const string& b) const {
        return lexicographical_compare(
            a.begin(), a.end(), b.begin(), b.end(),
            [](char c1, char c2) {
                return tolower(c1) < tolower(c2);
            });
    }
};

set<string, CaseInsensitiveCompare> words;
words.insert("Apple");
words.insert("apple"); // 不会插入,因为视为相同

7.2 高效遍历与删除

cpp 复制代码
// 安全删除满足条件的元素(所有容器通用)
for (auto it = container.begin(); it != container.end(); ) {
    if (condition(*it)) {
        it = container.erase(it); // erase返回下一个迭代器
    } else {
        ++it;
    }
}

// C++20 简化写法
erase_if(container, [](const auto& item) {
    return condition(item);
});

7.3 容器适配器

cpp 复制代码
// 基于vector实现栈
stack<int, vector<int>> myStack;

// 基于deque实现队列
queue<int> myQueue;

// 基于vector实现优先队列
priority_queue<int, vector<int>, greater<int>> minHeap;

7.4 性能优化技巧

  1. vector预分配 :使用reserve()减少扩容开销

  2. 元素移动:使用移动语义减少拷贝

cpp 复制代码
vector<string> words;
string largeStr = "very long string...";
words.push_back(move(largeStr)); // 移动而非拷贝
  1. 自定义分配器:针对特定场景优化内存分配

  2. 结构体优化:小对象更高效

cpp 复制代码
// 优化前
map<string, Data> bigMap;

// 优化后:使用指针或智能指针
map<string, unique_ptr<Data>> optimizedMap;

7.5 C++17新特性

cpp 复制代码
// 结构化绑定遍历map
for (const auto& [key, value] : myMap) {
    cout << key << ": " << value << endl;
}

// map插入优化
myMap.try_emplace(key, arg1, arg2); // 只在键不存在时构造
myMap.insert_or_assign(key, value); // 插入或更新

// 节点操作(C++17)
map<int, string> src = {{1, "one"}, {2, "two"}};
map<int, string> dst;
auto node = src.extract(1); // 提取节点
dst.insert(move(node));     // 插入到另一个map

八、总结与综合示例

8.1 容器选择黄金法则

  1. 默认选择vector:除非有特殊需求

  2. 需要键值对选map:关联数据存储

  3. 需要唯一有序选set:集合操作

  4. 高频任意位置操作选list:链表特性

  5. 考虑性能与内存:vector连续内存优势

8.2 综合应用示例:学生成绩系统

cpp 复制代码
#include <iostream>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
using namespace std;

struct Student {
    int id;
    string name;
    double score;
    
    // 用于set排序
    bool operator<(const Student& other) const {
        return score > other.score; // 按成绩降序
    }
};

class GradeSystem {
private:
    vector<Student> students;          // 所有学生
    set<Student> rankedStudents;       // 按成绩排序
    map<int, Student*> idIndex;        // ID快速查找
    
public:
    void addStudent(int id, string name, double score) {
        students.push_back({id, name, score});
        auto& newStudent = students.back();
        rankedStudents.insert(newStudent);
        idIndex[id] = &newStudent;
    }
    
    void updateScore(int id, double newScore) {
        auto it = idIndex.find(id);
        if (it != idIndex.end()) {
            // 更新排名
            rankedStudents.erase(*(it->second));
            it->second->score = newScore;
            rankedStudents.insert(*(it->second));
        }
    }
    
    void printTopStudents(int n) {
        cout << "Top " << n << " students:\n";
        int count = 0;
        for (const auto& student : rankedStudents) {
            if (count++ >= n) break;
            cout << student.name << ": " << student.score << endl;
        }
    }
    
    Student* findStudent(int id) {
        auto it = idIndex.find(id);
        return (it != idIndex.end()) ? it->second : nullptr;
    }
};

int main() {
    GradeSystem system;
    system.addStudent(101, "Alice", 95.5);
    system.addStudent(102, "Bob", 88.0);
    system.addStudent(103, "Charlie", 92.5);
    
    system.updateScore(102, 90.5); // Bob成绩更新
    
    system.printTopStudents(2);
    /*
    输出:
    Top 2 students:
    Alice: 95.5
    Charlie: 92.5
    */
    
    if (auto student = system.findStudent(103)) {
        cout << "Found: " << student->name << endl;
    }
    
    return 0;
}

8.3 最终建议

  1. 理解底层实现:选择容器前了解其数据结构

  2. 考虑操作频率:分析最常用操作

  3. 使用现代C++特性:emplace、移动语义等

  4. 性能测试:关键路径进行性能分析

  5. 组合使用容器:解决复杂问题

cpp 复制代码
// 高效查找与顺序保持
vector<Employee*> employees; // 保持插入顺序
map<int, Employee*> idIndex; // ID快速查找

通过深入理解这四大容器的特性和适用场景,您将能够为不同需求选择最合适的数据结构,编写出高效、可维护的C++代码。

相关推荐
Paper Clouds23 分钟前
代码随想录|图论|15并查集理论基础
数据结构·算法·leetcode·深度优先·图论
Paper Clouds23 分钟前
代码随想录|图论|14有向图的完全可达性
数据结构·算法·深度优先·图论·宽度优先
有冠希没关系29 分钟前
Ffmpeg滤镜
c++
遇见尚硅谷38 分钟前
C语言:游戏代码分享
c语言·开发语言·算法·游戏
小刘|1 小时前
单例模式详解
java·开发语言·单例模式
超浪的晨1 小时前
Java 内部类详解:从基础到实战,掌握嵌套类、匿名类与局部类的使用技巧
java·开发语言·后端·学习·个人开发
晓13131 小时前
JavaScript加强篇——第八章 高效渲染与正则表达式
开发语言·前端·javascript
阳光开朗_大男孩儿2 小时前
nfs 锁机制demo
开发语言·多线程·多进程·文件锁
闻缺陷则喜何志丹2 小时前
【并集查找 虚拟节点】P1783 海滩防御|省选-
数据结构·c++·洛谷·并集查找·虚拟节点
Hello-Mr.Wang2 小时前
使用Spring Boot和PageHelper实现数据分页
java·开发语言·spring boot