C++ 容器(STL)实战:从入门到精通的快速上手指南

目录

第一部分:C++容器概述

第二部分:序列容器详解

2.1 vector:动态数组的王者

2.2 deque:双端队列的灵活选手

2.3 list:双向链表的经典

2.4 array:固定大小数组的现代版 (C++11)

2.5 forward_list:单向链表的轻量版 (C++11)

第三部分:关联容器详解

3.1 set/multiset:有序集合

3.2 map/multimap:键值对的字典

3.3 unordered_set/unordered_multiset:哈希集合 (C++11)

3.4 unordered_map/unordered_multimap:哈希字典 (C++11)

第四部分:容器适配器

4.1 stack:栈

4.2 queue:队列

4.3 priority_queue:优先队列

第五部分:容器比较与选择

第六部分:高级主题

6.1 自定义分配器

6.2 容器与算法交互

6.3 迭代器失效

6.4 多线程与容器

6.5 性能优化

第七部分:常见错误与调试

结语


 

class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

引言

为什么写这个?因为容器是C++程序员的“饭碗工具”。你想想,数组太原始,链表自己实现又麻烦,容器直接帮你搞定动态内存、迭代、排序等。学好了它,你的代码效率能翻倍。文章会从基础讲起,覆盖主要容器类型,每个都配示例,性能分析和使用场景。全文会很详尽,别急,慢慢看。

第一部分:C++容器概述

C++的标准模板库(STL)提供了丰富的容器类,这些容器本质上是封装了数据结构的模板类,帮助我们存储和管理元素。STL容器可以分为三大类:序列容器(sequence containers)、关联容器(associative containers)和容器适配器(container adapters)。

  • 序列容器:按元素插入顺序存储,像vector、list、deque。这些适合需要按顺序访问的场景。
  • 关联容器:基于键值或排序存储,像set、map、unordered_set。这些自动排序或哈希,查找快。
  • 容器适配器:基于其他容器实现的简化版,像stack、queue、priority_queue。不支持迭代器,但操作简单。

所有容器都支持模板,所以可以存任何类型的数据,比如int、string或自定义类。但要注意,如果存自定义类,需要实现比较运算符或提供比较函数,尤其是关联容器。

容器头文件都在<algorithm>、 <vector> 等标准头里,使用前要#include。命名空间是std,所以常用using namespace std; 或 std::前缀。

我这些年用容器最多的地方是数据处理。比如在游戏中用vector存玩家列表,在数据库接口用map存键值对。记住,选对容器能让程序跑得飞起,选错就卡顿。

第二部分:序列容器详解

序列容器是STL的入门级选手,我们从最常见的vector开始。

2.1 vector:动态数组的王者

vector是我用得最多的容器,相当于可变长数组。底层是连续内存块,随机访问快,但插入删除中间元素时要移动数据,效率低。

头文件:#include <vector>

基本操作

  • 构造:vector<int> v; 或 vector<int> v(5, 10); // 5个10
  • 大小:v.size() 返回元素数,v.capacity() 返回预分配空间。
  • 访问:v[0] 或 v.at(0) (后者检查边界)。
  • 添加:v.push_back(1); 尾部加,v.emplace_back(1); 就地构造。
  • 删除:v.pop_back(); 尾部删,v.erase(it); 指定位置删。
  • 迭代:for(auto& elem : v) 或使用begin()/end()。

性能:随机访问O(1),尾部插入O(1)摊销,中间插入O(n)。

适用场景:需要频繁随机访问或尾部操作的列表,比如缓冲区、日志记录。

示例代码:下面是个简单示例,模拟一个成绩管理系统。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> scores; // 空vector
    scores.push_back(90);
    scores.push_back(85);
    scores.push_back(95);

    // 遍历输出
    cout << "Scores: ";
    for(size_t i = 0; i < scores.size(); ++i) {
        cout << scores[i] << " ";
    }
    cout << endl;

    // 插入到中间
    auto it = scores.begin() + 1;
    scores.insert(it, 88); // 现在是90,88,85,95

    // 删除最后一个
    scores.pop_back();

    // 使用reserve预分配
    scores.reserve(10); // 避免多次扩容

    cout << "After modifications: ";
    for(auto& s : scores) {
        cout << s << " ";
    }
    cout << endl;

    return 0;
}

运行这个,输出是Scores: 90 85 95 After modifications: 90 88 85

在实际项目中,我用vector存网络包数据,因为连续内存利于memcpy。注意,如果元素是对象,vector会调用拷贝构造,效率低时用unique_ptr包裹。

vector的容量管理是关键。当size==capacity时,push_back会 realloc,通常翻倍增长。但这可能导致迭代器失效,所以别在循环中随意插入。

常见坑:下标越界用[]会崩溃,用at()安全但慢。resize() vs reserve():前者改变size并初始化,后者只预分配。

扩展讲讲vector<bool>,这是个特化版,用bit存储,节省空间,但不支持引用返回,很多人踩坑。建议用vector<char>代替。

2.2 deque:双端队列的灵活选手

deque(double-ended queue)支持两端高效操作,底层是分块连续内存,像vector和list的混合。

头文件:#include <deque>

基本操作

  • 类似vector,但有push_front/pop_front。
  • 访问:d[0] 或 d.front()/back()。

性能:两端插入O(1),随机访问O(1)但常数大,中间插入O(n)。

适用场景:需要前后端操作,如滑动窗口、队列模拟。

示例代码:实现一个最近使用列表(LRU cache简化版)。

#include <iostream>
#include <deque>
using namespace std;

int main() {
    deque<string> recentFiles;
    recentFiles.push_back("file1.txt");
    recentFiles.push_back("file2.txt");
    recentFiles.push_front("file0.txt"); // 加到前端

    cout << "Recent files: ";
    for(const auto& f : recentFiles) {
        cout << f << " ";
    }
    cout << endl;

    // 移除 oldest
    recentFiles.pop_front();

    // 添加新,检查大小
    if(recentFiles.size() > 5) recentFiles.pop_front();
    recentFiles.push_back("file3.txt");

    cout << "Updated: ";
    for(const auto& f : recentFiles) {
        cout << f << " ";
    }
    cout << endl;

    return 0;
}

输出:Recent files: file0.txt file1.txt file2.txt Updated: file1.txt file2.txt file3.txt

我曾在音频处理用deque存样本,因为能高效shift。deque不支持capacity,但有max_size()。

与vector比,deque随机访问慢点,但两端灵活。别用deque当stack/queue,用适配器更好。

2.3 list:双向链表的经典

list是双向链表,插入删除快,但随机访问慢。

头文件:#include <list>

基本操作

  • l.push_back(1); l.push_front(1);
  • l.insert(it, 2); l.erase(it);
  • 无[]访问,只有迭代器。

性能:插入删除O(1),访问O(n)。

适用场景:频繁插入删除,不需随机访问,如任务队列。

示例代码:模拟播放列表。

#include <iostream>
#include <list>
using namespace std;

int main() {
    list<string> playlist = {"Song1", "Song2", "Song3"};

    // 插入到指定位置
    auto it = playlist.begin();
    ++it; // 指向Song2
    playlist.insert(it, "Song1.5");

    // 删除Song3
    playlist.pop_back();

    // 遍历
    cout << "Playlist: ";
    for(const auto& song : playlist) {
        cout << song << " ";
    }
    cout << endl;

    // splice合并另一个list
    list<string> newSongs = {"Song4", "Song5"};
    playlist.splice(playlist.end(), newSongs); // newSongs变空

    cout << "After splice: ";
    for(const auto& song : playlist) {
        cout << song << " ";
    }
    cout << endl;

    return 0;
}

输出:Playlist: Song1 Song1.5 Song2 After splice: Song1 Song1.5 Song2 Song4 Song5

list的splice是神器,能O(1)转移元素。我在多线程任务管理用过list,因为删除不失效其他迭代器。

注意,list迭代器是bidirectional,不支持+ n跳转。size()是O(1)在C++11后。

2.4 array:固定大小数组的现代版 (C++11)

array是固定大小的容器,封装了C数组。

头文件:#include <array>

基本操作

  • array<int, 5> a = {1,2,3,4,5};
  • a[0], a.at(0), a.front()/back()。

性能:所有O(1),栈上分配。

适用场景:大小固定,如坐标点。

示例代码

#include <iostream>
#include <array>
using namespace std;

int main() {
    array<double, 3> point = {1.0, 2.0, 3.0};

    cout << "Point: (" << point[0] << ", " << point[1] << ", " << point[2] << ")" << endl;

    // 填充
    point.fill(0.0);

    cout << "After fill: (" << point.front() << ", " << point.at(1) << ", " << point.back() << ")" << endl;

    return 0;
}

输出:Point: (1, 2, 3) After fill: (0, 0, 0)

array比C数组安全,有size()。我在嵌入式用它存配置。

2.5 forward_list:单向链表的轻量版 (C++11)

forward_list是单向链表,节省空间。

头文件:#include <forward_list>

基本操作:类似list,但只push_front, insert_after等。

性能:插入O(1),无size() O(n)。

适用场景:内存紧,单向遍历。

示例代码

#include <iostream>
#include <forward_list>
using namespace std;

int main() {
    forward_list<int> fl = {3,2,1};

    fl.push_front(0); // 0,3,2,1

    auto it = fl.begin();
    fl.insert_after(it, 4); // 0,4,3,2,1

    cout << "List: ";
    for(int num : fl) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

输出:List: 0 4 3 2 1

forward_list适合哈希桶实现,我很少用,除非内存优化。

第三部分:关联容器详解

关联容器自动管理顺序或哈希,查找快。

3.1 set/multiset:有序集合

set存储唯一元素,自动排序。multiset允许重复。

头文件:#include <set>

基本操作

  • s.insert(1); 返回pair<iterator, bool>
  • s.find(1); 返回iterator或end()
  • s.erase(1); 或 s.erase(it)

性能:插入/查找/删除 O(log n),红黑树底层。

适用场景:去重、排序集合,如唯一ID。

示例代码:管理独特访客。

#include <iostream>
#include <set>
using namespace std;

int main() {
    set<string> visitors;
    visitors.insert("Alice");
    visitors.insert("Bob");
    auto res = visitors.insert("Alice"); // 失败,res.second=false

    cout << "Visitors: ";
    for(const auto& v : visitors) {
        cout << v << " ";
    }
    cout << endl;

    if(visitors.find("Bob") != visitors.end()) {
        cout << "Bob is here." << endl;
    }

    // multiset示例
    multiset<int> scores = {90, 85, 90};
    cout << "Score count of 90: " << scores.count(90) << endl;

    return 0;
}

输出:Visitors: Alice Bob Bob is here. Score count of 90: 2

set自定义比较:set<int, greater<int>> descending;

我在日志分析用set去重事件。

3.2 map/multimap:键值对的字典

map是有序键值对,键唯一。multimap允许重复键。

头文件:#include <map>

基本操作

  • m[ "key" ] = 1; // 若无,插入
  • m.insert(make_pair("key", 1));
  • m.find("key")->second

性能:O(log n)

适用场景:配置表、缓存。

示例代码:词频统计。

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    map<string, int> wordCount;
    wordCount["apple"] = 5;
    wordCount["banana"]++;
    wordCount.emplace("cherry", 3);

    cout << "Word counts:" << endl;
    for(const auto& pair : wordCount) {
        cout << pair.first << ": " << pair.second << endl;
    }

    // multimap
    multimap<string, string> phoneBook;
    phoneBook.insert({"John", "123"});
    phoneBook.insert({"John", "456"});

    auto range = phoneBook.equal_range("John");
    cout << "John's numbers: ";
    for(auto it = range.first; it != range.second; ++it) {
        cout << it->second << " ";
    }
    cout << endl;

    return 0;
}

输出:Word counts: apple: 5 banana: 1 cherry: 3 John's numbers: 123 456

map的[]会默认插入0值,小心。用count()检查存在。

我用map在服务器存session。

3.3 unordered_set/unordered_multiset:哈希集合 (C++11)

unordered_set是无序集合,哈希存储。

头文件:#include <unordered_set>

基本操作:类似set,但无序。

性能:平均O(1),最坏O(n)如果哈希冲突。

适用场景:快速查找,不需排序,如会员检查。

示例代码

#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    unordered_set<int> uniqueNums = {1,2,3,2}; // 自动去重:1,2,3

    uniqueNums.insert(4);

    if(uniqueNums.count(3)) {
        cout << "3 is present." << endl;
    }

    cout << "Elements: ";
    for(int num : uniqueNums) {
        cout << num << " "; // 顺序不定
    }
    cout << endl;

    // unordered_multiset允许重复
    unordered_multiset<int> multi = {1,1,2};
    cout << "Count of 1: " << multi.count(1) << endl;

    return 0;
}

输出:3 is present. Elements: 4 1 2 3 (顺序可能变) Count of 1: 2

自定义哈希:struct Hash { size_t operator()(const T&) const; };

负载因子load_factor(),调整rehash。

我在高频交易用unordered_set查订单。

3.4 unordered_map/unordered_multimap:哈希字典 (C++11)

类似map,但无序。

头文件:#include <unordered_map>

基本操作:类似map。

性能:O(1)平均。

适用场景:快速键值查找,如数据库索引。

示例代码:简单缓存。

#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;

int main() {
    unordered_map<string, double> prices;
    prices["apple"] = 1.5;
    prices.emplace("banana", 0.8);

    cout << "Apple price: " << prices["apple"] << endl;

    // 如果键不存在,[]插入默认0
    cout << "Cherry price (default): " << prices["cherry"] << endl; // 0

    // 用at()抛异常如果不存在
    try {
        cout << prices.at("orange") << endl;
    } catch(const out_of_range&) {
        cout << "Orange not found." << endl;
    }

    // unordered_multimap
    unordered_multimap<string, string> contacts;
    contacts.insert({"Alice", "email@"});
    contacts.insert({"Alice", "phone"});

    auto range = contacts.equal_range("Alice");
    cout << "Alice's contacts: ";
    for(auto it = range.first; it != range.second; ++it) {
        cout << it->second << " ";
    }
    cout << endl;

    return 0;
}

输出:Apple price: 1.5 Cherry price (default): 0 Orange not found. Alice's contacts: email@ phone (顺序可能变)

unordered_map在大数据下高效,但哈希冲突需注意。bucket_count()查看桶数。

第四部分:容器适配器

这些是基于其他容器的简化接口。

4.1 stack:栈

头文件:#include <stack>

基本操作:s.push(1); s.pop(); s.top();

底层默认deque,可指定如stack<int, vector<int>>。

示例:括号匹配。

#include <iostream>
#include <stack>
#include <string>
using namespace std;

bool isBalanced(const string& str) {
    stack<char> s;
    for(char c : str) {
        if(c == '(' || c == '[' || c == '{') {
            s.push(c);
        } else if(c == ')' || c == ']' || c == '}') {
            if(s.empty()) return false;
            char top = s.top();
            s.pop();
            if((c == ')' && top != '(') || (c == ']' && top != '[') || (c == '}' && top != '{')) {
                return false;
            }
        }
    }
    return s.empty();
}

int main() {
    cout << "Balanced? " << boolalpha << isBalanced("{[()]}") << endl; // true
    cout << "Balanced? " << boolalpha << isBalanced("{[(]}") << endl; // false
    return 0;
}

栈简单,我用它实现undo功能。

4.2 queue:队列

头文件:#include <queue>

基本操作:q.push(1); q.pop(); q.front()/back();

底层deque。

示例:任务队列。

#include <iostream>
#include <queue>
using namespace std;

int main() {
    queue<string> tasks;
    tasks.push("Task1");
    tasks.push("Task2");

    while(!tasks.empty()) {
        cout << "Processing: " << tasks.front() << endl;
        tasks.pop();
    }

    return 0;
}

输出:Processing: Task1 Processing: Task2

4.3 priority_queue:优先队列

默认最大堆。

基本操作:pq.push(1); pq.pop(); pq.top();

示例:Top K问题。

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

int main() {
    priority_queue<int> pq; // max heap
    vector<int> nums = {3,1,4,1,5,9};
    for(int n : nums) pq.push(n);

    cout << "Top 3: ";
    for(int i=0; i<3; ++i) {
        cout << pq.top() << " ";
        pq.pop();
    }
    cout << endl;

    // min heap
    priority_queue<int, vector<int>, greater<int>> minpq;
    for(int n : nums) minpq.push(n);
    cout << "Smallest: " << minpq.top() << endl;

    return 0;
}

输出:Top 3: 9 5 4 Smallest: 1

优先队列在调度算法中常用。

第五部分:容器比较与选择

  • 访问速度:vector/array > deque > list/forward_list
  • 插入删除:list/forward_list O(1) > deque两端 > vector尾部 > set/map O(log n) > unordered O(1)
  • 内存:vector连续,list节点散布。
  • 有序:set/map有序,unordered无序。

选择原则:需随机访问用vector,频繁插删用list,快速查找用unordered_map/set,键值用map。

在项目中,我先用vector原型,优化时换unordered。

第六部分:高级主题

6.1 自定义分配器

容器支持allocator模板参数,自定义内存管理。如用pool allocator优化小对象。

示例:allocator<T> alloc; vector<int, allocator<int>> v(alloc);

6.2 容器与算法交互

STL算法如sort、find用在容器上。

示例:sort(v.begin(), v.end());

注意,关联容器不能sort,因为已排序。

6.3 迭代器失效

vector插入可能失效所有迭代器,list只失效删除的。

用erase返回的新it。

6.4 多线程与容器

STL容器非线程安全,用mutex保护。

C++11后,有concurrent容器但非标准。

6.5 性能优化

  • 用emplace代替insert/push,避免拷贝。
  • reserve预分配。
  • 缩容:vector<int>().swap(v);

第七部分:常见错误与调试

  • 越界访问:用at()。
  • 迭代器失效:别在循环中erase,用erase返回值。
  • 自定义类比较:实现operator< 或 functor。
  • 哈希冲突:好哈希函数。
  • 内存泄漏:容器存指针,记得delete。

调试用gdb看size/capacity。

结语

C++容器是强大工具,掌握它能写出高效代码。这些年,我从vector入门,到unordered_map优化,受益匪浅。建议多练代码,分析性能。欢迎评论交流!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值