C++ STL 标准模板库完全指南:从容器到迭代器

引言

在C++编程中,标准模板库是不可或缺的核心组成部分。无论你是参加算法竞赛、开发大型项目,还是准备技术面试,STL都能极大地提升你的开发效率。

STL发展历程中的几个重要版本:

版本 开发者/应用场景 特点
HP版本 惠普公司原始开发 是STL的原始实现,后被纳入C++标准
SGI版本 Linux系统常用实现 开源实现,被GCC编译器采用
PJ版本 Visual Studio配套实现 Windows平台主流实现

今天我们重点讲解的是在实际开发中最常用的容器------vectorlist,以及配套的迭代器、算法等核心组件。


第一部分:STL的核心组成

一、STL的四大组件

组件 作用 举例
容器 存储数据的数据结构 vectorlistmapset
迭代器 统一访问容器元素的接口 begin()end()
算法 通用的数据处理函数 sortfindcopy
仿函数 行为类似函数的对象 greaterless

二、迭代器(Iterator)

迭代器是面向对象化的指针,支持解引用、移动等操作,是连接容器与算法的桥梁。

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

int main() {
    vector<int> arr = {1, 2, 3, 4, 5};
    
    // 迭代器的基本使用
    vector<int>::iterator it = arr.begin();
    cout << "第一个元素: " << *it << endl;
    
    // 遍历所有元素
    for (it = arr.begin(); it != arr.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}

三、算法(Algorithm)

算法库提供了大量常用操作,直接调用即可,无需重复造轮子。

分类 算法函数 功能
排序 sort() 排序
查找 find() 查找元素
拷贝 copy() 复制元素
删除 remove() 删除元素
统计 count() 计数
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> arr = {5, 2, 8, 1, 9, 3};
    
    // 排序(默认升序)
    sort(arr.begin(), arr.end());
    // 结果:1 2 3 5 8 9
    
    // 降序排序
    sort(arr.begin(), arr.end(), greater<int>());
    // 结果:9 8 5 3 2 1
    
    return 0;
}

第二部分:vector容器详解

一、vector的底层结构

vector是动态数组,在内存中开辟连续空间存储元素。其底层由三个关键指针管理:

指针 含义
start 堆空间首地址
finish 最后一个元素的后继地址(决定size)
end_of_storage 空间容量末尾地址(决定capacity)

重要概念:

  • size():实际存储的元素个数

  • capacity():预分配的空间大小(size <= capacity

二、vector的扩容机制

当空间不足时,vector会自动扩容(通常按2倍或1.5倍增长)。

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

int main() {
    vector<int> arr;
    
    cout << "初始: size=" << arr.size() << ", capacity=" << arr.capacity() << endl;
    
    for (int i = 0; i < 10; i++) {
        arr.push_back(i);
        cout << "push_back(" << i << "): size=" << arr.size() 
             << ", capacity=" << arr.capacity() << endl;
    }
    
    return 0;
}

扩容过程的影响:

  • 存储对象时:扩容需要逐个迁移对象并调用拷贝构造函数,性能开销大

  • 存储指针时:仅需迁移指针地址,性能更优(但需注意内存释放问题)

三、vector的初始化方式

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

int main() {
    // 1. 默认构造
    vector<int> v1;
    
    // 2. 指定大小
    vector<int> v2(10);           // 10个元素,值为0
    vector<int> v3(10, 5);        // 10个元素,值都为5
    
    // 3. 列表初始化(C++11)
    vector<int> v4 = {1, 2, 3, 4, 5};
    vector<int> v5{1, 2, 3, 4, 5};
    
    // 4. 拷贝构造
    vector<int> v6(v4);
    
    // 5. 迭代器范围构造
    vector<int> v7(v4.begin(), v4.end());
    
    return 0;
}

四、vector的元素访问方式

访问方式 语法 边界检查 效率
下标运算符 arr[i] ❌ 无检查 最高
at()方法 arr.at(i) ✅ 抛出异常 较高
front() arr.front() 空容器未定义
back() arr.back() 空容器未定义
data() arr.data() 返回底层数组指针
cpp 复制代码
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;

int main() {
    vector<int> arr = {10, 20, 30, 40, 50};
    
    // 下标访问(无边界检查)
    cout << arr[0] << endl;      // 10
    // arr[10] = 100;            // 越界,未定义行为
    
    // at()方法(有边界检查)
    try {
        cout << arr.at(2) << endl;   // 30
        cout << arr.at(10) << endl;  // 抛出out_of_range异常
    } catch (const out_of_range& e) {
        cout << "越界: " << e.what() << endl;
    }
    
    // 首尾元素
    cout << "第一个元素: " << arr.front() << endl;  // 10
    cout << "最后一个元素: " << arr.back() << endl;  // 50
    
    return 0;
}

五、vector的遍历方式

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

int main() {
    vector<int> arr = {1, 2, 3, 4, 5};
    
    // 方式1:下标遍历
    cout << "下标遍历: ";
    for (size_t i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    // 方式2:迭代器遍历
    cout << "迭代器遍历: ";
    for (vector<int>::iterator it = arr.begin(); it != arr.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // 方式3:范围for循环(C++11)
    cout << "范围for(值传递): ";
    for (auto x : arr) {      // 拷贝每个元素
        cout << x << " ";
    }
    cout << endl;
    
    // 方式4:范围for(引用传递,避免拷贝)
    cout << "范围for(引用传递): ";
    for (const auto& x : arr) {  // 只读,不拷贝
        cout << x << " ";
    }
    cout << endl;
    
    // 修改元素需要引用
    for (auto& x : arr) {
        x *= 2;
    }
    
    return 0;
}

效率对比:

  • 值传递 for (auto x : arr):每次迭代都会拷贝元素,效率低

  • 引用传递 for (auto& x : arr):不拷贝,效率高

  • const引用 for (const auto& x : arr):只读场景,效率高且语义明确

六、vector的容量管理

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

int main() {
    vector<int> arr;
    
    // 预分配容量(避免频繁扩容)
    arr.reserve(100);
    cout << "reserve后: capacity=" << arr.capacity() << endl;
    
    // 添加元素
    for (int i = 0; i < 50; i++) {
        arr.push_back(i);
    }
    
    cout << "size=" << arr.size() << ", capacity=" << arr.capacity() << endl;
    
    // 释放多余内存
    arr.shrink_to_fit();
    cout << "shrink_to_fit后: capacity=" << arr.capacity() << endl;
    
    return 0;
}
方法 作用 说明
reserve(n) 预分配容量 避免频繁扩容,不改变size
shrink_to_fit() 释放多余内存 将capacity缩小到size
clear() 清空元素 size变为0,capacity不变

七、vector的插入与删除

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

int main() {
    vector<int> arr = {1, 2, 3, 4, 5};
    
    // 尾部操作(高效,O(1))
    arr.push_back(6);     // 尾部插入
    arr.pop_back();       // 尾部删除
    
    // 中间操作(低效,O(n))
    arr.insert(arr.begin() + 2, 100);   // 在位置2插入100
    arr.erase(arr.begin() + 3);          // 删除位置3的元素
    
    // 遍历
    for (int x : arr) {
        cout << x << " ";
    }
    cout << endl;
    
    return 0;
}

复杂度总结:

操作 时间复杂度 说明
push_back() / pop_back() O(1) 尾部操作,高效
insert() / erase()(中间) O(n) 需要移动元素,低效

第三部分:迭代器详解

一、迭代器的基本使用

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

int main() {
    vector<int> arr = {10, 20, 30, 40, 50};
    
    // 正向迭代器
    cout << "正向遍历: ";
    for (vector<int>::iterator it = arr.begin(); it != arr.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // 只读迭代器
    cout << "只读遍历: ";
    for (vector<int>::const_iterator it = arr.cbegin(); it != arr.cend(); ++it) {
        cout << *it << " ";
        // *it = 100;  // 错误!const_iterator不能修改
    }
    cout << endl;
    
    // 反向迭代器
    cout << "反向遍历: ";
    for (vector<int>::reverse_iterator it = arr.rbegin(); it != arr.rend(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    return 0;
}

二、迭代器类型总结

迭代器类型 获取方式 可读 可写 方向
iterator begin() / end() 正向
const_iterator cbegin() / cend() 正向
reverse_iterator rbegin() / rend() 反向

三、迭代器失效问题

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

int main() {
    vector<int> arr = {1, 2, 3, 4, 5};
    
    // 获取迭代器
    auto it = arr.begin() + 2;  // 指向元素3
    cout << "*it = " << *it << endl;
    
    // 插入操作可能导致迭代器失效
    arr.insert(arr.begin(), 0);  // 头部插入,可能导致所有迭代器失效
    
    // 危险!it可能已经失效
    // cout << "*it = " << *it << endl;
    
    // 重新获取迭代器
    it = arr.begin() + 3;
    cout << "重新获取后: *it = " << *it << endl;
    
    return 0;
}

迭代器失效场景:

  • 插入元素导致扩容时,所有迭代器失效

  • 删除元素后,被删除位置之后的迭代器失效


第四部分:list容器详解

一、list的底层结构

list是双向链表,节点在内存中不连续,通过指针连接。

cpp 复制代码
// list节点的简化结构
template<typename T>
struct ListNode {
    T data;           // 数据域
    ListNode* prev;   // 前驱指针
    ListNode* next;   // 后继指针
};

// 带哨兵节点的双向链表
// head指向头节点(哨兵),size记录元素个数
cpp 复制代码
list内存布局(逻辑结构):
┌──────┐    ┌──────┐    ┌──────┐    ┌──────┐
│ 节点1 │◄──►│ 节点2 │◄──►│ 节点3 │◄──►│ 节点4 │
│ data │    │ data │    │ data │    │ data │
└──────┘    └──────┘    └──────┘    └──────┘
   ↑                                      ↑
   └──────────────┐  ┌──────────────────┘
                  ▼  ▼
              ┌──────────┐
              │ 哨兵节点  │
              └──────────┘

二、list的基本操作

cpp 复制代码
#include <iostream>
#include <list>
using namespace std;

int main() {
    // 初始化
    list<int> lst = {1, 2, 3, 4, 5};
    
    // 插入操作(高效,O(1))
    lst.push_back(6);     // 尾部插入
    lst.push_front(0);    // 头部插入
    
    // 删除操作(高效,O(1))
    lst.pop_back();       // 尾部删除
    lst.pop_front();      // 头部删除
    
    // 访问(只能访问首尾)
    cout << "第一个元素: " << lst.front() << endl;
    cout << "最后一个元素: " << lst.back() << endl;
    
    // ❌ list不支持下标访问
    // cout << lst[2] << endl;  // 错误!
    
    // 遍历(使用迭代器或范围for)
    cout << "遍历: ";
    for (int x : lst) {
        cout << x << " ";
    }
    cout << endl;
    
    return 0;
}

三、list vs vector

特性 vector list
底层结构 动态数组(连续内存) 双向链表(非连续)
随机访问 ✅ O(1) ❌ O(n)
中间插入/删除 ❌ O(n) ✅ O(1)
尾部插入/删除 ✅ O(1)(扩容时O(n)) ✅ O(1)
内存占用 较少(无指针开销) 较大(前后指针)
适用场景 频繁访问、尾部操作 频繁中间插入删除

四、list的特殊操作

cpp 复制代码
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int main() {
    list<int> lst = {5, 2, 8, 1, 3, 2, 5, 2};
    
    // list专属排序(不能用标准库的sort)
    lst.sort();                    // 升序排序
    // lst.sort(greater<int>());   // 降序排序
    
    // 去重(必须先排序)
    lst.unique();                  // 删除相邻重复元素
    
    // 删除指定值的所有元素
    lst.remove(2);                 // 删除所有值为2的元素
    
    // 合并两个有序list
    list<int> lst2 = {10, 20, 30};
    lst.merge(lst2);               // lst2被清空,元素合并到lst
    
    // 反转
    lst.reverse();
    
    for (int x : lst) {
        cout << x << " ";
    }
    cout << endl;
    
    return 0;
}

第五部分:关联容器(set/map)

一、set容器(集合)

set中的元素唯一且自动排序,底层基于红黑树实现。

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

int main() {
    vector<int> arr = {5, 2, 8, 1, 5, 2, 9, 3};
    
    // 使用set去重
    set<int> s;
    for (int x : arr) {
        s.insert(x);
    }
    
    cout << "去重后: ";
    for (int x : s) {
        cout << x << " ";
    }
    cout << endl;
    // 输出:1 2 3 5 8 9(自动排序)
    
    // 查找元素
    if (s.find(5) != s.end()) {
        cout << "5存在" << endl;
    }
    
    return 0;
}

二、map容器(映射)

map存储键值对,键唯一且自动排序。

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

int main() {
    vector<int> arr = {1, 2, 3, 2, 1, 3, 4, 2, 1};
    
    // 统计每个元素出现的次数
    map<int, int> freq;
    for (int x : arr) {
        freq[x]++;      // 使用[]操作符
    }
    
    // 遍历map
    for (const auto& kv : freq) {
        cout << kv.first << "出现" << kv.second << "次" << endl;
    }
    
    return 0;
}

三、unordered_set/unordered_map

无序版本,底层基于哈希表实现,查找更快(O(1)),但不保证顺序。

容器 底层结构 有序性 查找复杂度
set / map 红黑树 有序 O(log n)
unordered_set / unordered_map 哈希表 无序 O(1) 平均
cpp 复制代码
#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    unordered_set<int> us = {5, 2, 8, 1, 9};
    
    // 顺序不确定
    for (int x : us) {
        cout << x << " ";
    }
    cout << endl;
    
    return 0;
}

总结

一、容器特性对比

容器 底层 随机访问 中间插入/删除 适用场景
vector 动态数组 ✅ O(1) ❌ O(n) 频繁访问、尾部操作
list 双向链表 ❌ O(n) ✅ O(1) 频繁中间插入删除
deque 双端队列 ✅ O(1) 两端O(1) 双端操作
set / map 红黑树 插入/查找O(log n) 有序唯一集合
unordered_set / unordered_map 哈希表 插入/查找O(1) 快速查找、去重

二、迭代器类型

迭代器 获取方式 支持操作
iterator begin() / end() 读+写
const_iterator cbegin() / cend() 只读
reverse_iterator rbegin() / rend() 反向遍历

三、vector常用操作速查

操作 方法 复杂度
尾部插入 push_back() O(1)
尾部删除 pop_back() O(1)
中间插入 insert() O(n)
中间删除 erase() O(n)
访问元素 [] / at() O(1)
大小 size() O(1)
容量 capacity() O(1)
预分配 reserve() O(n)

四、list常用操作速查

操作 方法 复杂度
头部插入 push_front() O(1)
头部删除 pop_front() O(1)
尾部插入 push_back() O(1)
尾部删除 pop_back() O(1)
排序 sort() O(n log n)
去重 unique() O(n)
合并 merge() O(n)

STL是C++标准库的核心,掌握STL能够极大地提升开发效率。

STL使用三步法:

  1. 掌握基本使用方法

  2. 熟练运用各类容器和算法

  3. 理解源码实现并实现功能扩展

学习建议:

  1. 优先级:vector > list > map > unordered_map

  2. 迭代器遍历时注意引用传递避免拷贝

  3. 使用reserve()预分配容量避免频繁扩容

  4. 优先使用emplace_back()替代push_back()减少拷贝

  5. 根据场景选择合适的关联容器(有序选map,无序选unordered_map

相关推荐
winner88811 小时前
C++ 构造函数、析构函数、虚函数、虚析构
开发语言·c++
想唱rap1 小时前
应用层协议与序列化
linux·运维·服务器·网络·数据结构·c++·算法
北山有鸟1 小时前
IS_ERR 判断出错后,再用 PTR_ERR 把它强制转换回 int 型的错误码作为函数的返回值。
java·开发语言
许长安1 小时前
protobuf 使用详解
c++·经验分享·笔记·中间件
格林威1 小时前
工业视觉检测:提供可视化UI调试工具的实现方式是什么?
开发语言·人工智能·数码相机·ui·计算机视觉·视觉检测·工业相机
Soley1 小时前
用 Boost.Log 封装一个更顺手的 C++17 日志库:GoodLog
c++
phltxy1 小时前
深度解析:Spring Cloud Gateway 从入门到实战
java·开发语言
HAPPY酷2 小时前
从Public到Private:UE5 C++类创建路径差异全解析
java·c++·ue5
无敌昊哥战神2 小时前
【LeetCode 37】解数独 (Sudoku Solver) —— 回溯法详解 (Python/C/C++)
c语言·c++·python·算法·leetcode