【C++】STL容器

1、C++ STL容器概述

C++标准模板库(STL)提供了一组通用的、高效的容器类,用于存储和管理数据。这些容器可以分为以下几大类:

  1. 顺序容器(Sequence Containers): 元素按照线性顺序排列,可以通过位置(索引)访问。

    • std::vector:动态数组,可在尾部快速添加/删除元素,支持随机访问。
    • std::deque:双端队列,可在头部和尾部快速添加/删除元素,支持随机访问。
    • std::list:双向链表,可在任意位置快速插入/删除元素,不支持随机访问。
    • std::forward_list:单向链表,只能向前遍历,插入/删除比std::list更快,但功能更少。
    • std::array:固定大小数组,编译时确定大小,性能与C风格数组相当。
  2. 关联容器(Associative Containers): 元素按照键(key)排序,通过键来访问。

    • 有序关联容器(基于红黑树实现):
      • std::set:集合,存储唯一的键,按键排序。
      • std::map:映射,存储键值对(key-value pair),按键排序,键唯一。
      • std::multiset:多重集合,允许存储重复的键,按键排序。
      • std::multimap:多重映射,允许存储重复的键,按键排序,键值对可以重复。
    • 无序关联容器(基于哈希表实现):
      • std::unordered_set:无序集合,存储唯一的键,不按键排序(哈希)。
      • std::unordered_map:无序映射,存储键值对,不按键排序(哈希),键唯一。
      • std::unordered_multiset:无序多重集合,允许存储重复的键,不按键排序(哈希)。
      • std::unordered_multimap:无序多重映射,允许存储重复的键,不按键排序(哈希),键值对可以重复。
  3. 容器适配器(Container Adapters): 基于其他容器实现,提供特定的接口。

    • std::stack:栈,后进先出(LIFO)。
    • std::queue:队列,先进先出(FIFO)。
    • std::priority_queue:优先队列,元素按优先级排序(默认最大堆)。

2、各种容器的区别及代码示例

下面将详细介绍每种容器的特点、适用场景以及代码示例:

1. 顺序容器

  • std::vector

    • 特点: 动态数组,内存连续,支持快速随机访问([]操作符),在尾部添加/删除元素效率高(摊销常数时间),但在中间插入/删除元素效率低(线性时间)。

    • 适用场景: 需要频繁随机访问元素,对尾部操作较多,对中间插入/删除操作较少。

    • 代码示例:

      #include <iostream>
      #include <vector>
      
      int main() {
          std::vector<int> vec = {1, 2, 3, 4, 5};
      
          // 访问元素
          std::cout << "vec[2]: " << vec[2] << std::endl; // 输出: vec[2]: 3
      
          // 在尾部添加元素
          vec.push_back(6);
      
          // 遍历元素
          for (int x : vec) {
              std::cout << x << " "; // 输出: 1 2 3 4 5 6
          }
          std::cout << std::endl;
      
          // 在中间插入元素 (效率较低)
          vec.insert(vec.begin() + 2, 10); // 在索引2的位置插入10
      
          // 删除元素 (效率较低, 除非是删除最后一个元素)
          vec.erase(vec.begin() + 1); // 删除索引1的元素
      
          return 0;
      }
      
  • std::deque

    • 特点: 双端队列,内存不一定连续,支持快速随机访问,在头部和尾部添加/删除元素效率高(常数时间),但在中间插入/删除元素效率低(线性时间)。

    • 适用场景: 需要频繁在头部和尾部进行操作,对随机访问也有要求。

    • 代码示例:

      #include <iostream>
      #include <deque>
      
      int main() {
          std::deque<int> dq = {1, 2, 3};
      
          // 在头部添加元素
          dq.push_front(0);
      
          // 在尾部添加元素
          dq.push_back(4);
      
          // 访问元素
          std::cout << "dq[1]: " << dq[1] << std::endl; // 输出: dq[1]: 1
      
          // 遍历元素
          for (int x : dq) {
              std::cout << x << " "; // 输出: 0 1 2 3 4
          }
          std::cout << std::endl;
      
          return 0;
      }
      
  • std::list

    • 特点: 双向链表,内存不连续,不支持随机访问,在任意位置插入/删除元素效率高(常数时间),但查找元素效率低(线性时间)。

    • 适用场景: 需要频繁在任意位置插入/删除元素,不需要随机访问。

    • 代码示例:

      #include <iostream>
      #include <list>
      
      int main() {
          std::list<int> lst = {1, 2, 3};
      
          // 在任意位置插入元素
          auto it = lst.begin();
          std::advance(it, 1); // 将迭代器移动到第二个位置
          lst.insert(it, 10); // 在第二个位置插入10
      
          // 删除元素
          lst.erase(lst.begin()); // 删除第一个元素
      
          // 遍历元素
          for (int x : lst) {
              std::cout << x << " "; // 输出: 2 10 3
          }
          std::cout << std::endl;
      
          return 0;
      }
      
  • std::forward_list

    • 特点: 单向链表,内存不连续,只支持单向遍历,在任意位置插入/删除元素效率高(比std::list更快),但功能更少(例如,没有size()方法)。

    • 适用场景: 需要频繁在任意位置插入/删除元素,只需要单向遍历,对内存占用有较高要求。

    • 代码示例:

      #include <iostream>
      #include <forward_list>
      
      int main() {
          std::forward_list<int> flst = {1, 2, 3};
      
          // 在任意位置插入元素
          auto it = flst.begin();
          std::advance(it, 1); // 将迭代器移动到第二个位置
          flst.insert_after(it, 10); // 在第二个位置之后插入10
      
          // 删除元素
          flst.erase_after(flst.begin()); // 删除第一个元素之后的元素
           // 遍历元素
          for (int x : flst) {
              std::cout << x << " ";
          }
          std::cout << std::endl;
          return 0;
      }
      
  • std::array

    • 特点: 固定大小数组,内存连续,编译期确定大小,性能与C风格数组相当,支持快速随机访问
    • 使用场景: 数组大小在编译期确定,并希望获得与C风格数组相近的性能。
    • 代码示例:
       #include <iostream>
       #include <array>
    
       int main() {
         std::array<int,5> arr = {1,2,3,4,5};
          // 访问元素
           std::cout << "arr[2]: " << arr[2] << std::endl; // 输出: arr[2]: 3
           // 遍历元素
           for (int x : arr) {
               std::cout << x << " "; // 输出: 1 2 3 4 5
           }
           std::cout << std::endl;
       }
    

2. 关联容器

  • 有序关联容器(std::setstd::mapstd::multisetstd::multimap

    • 特点: 基于红黑树实现,元素按键自动排序,查找、插入、删除操作的平均时间复杂度为O(log n)。

    • 代码示例:

      #include <iostream>
      #include <set>
      #include <map>
      
      int main() {
          // std::set (集合,键唯一)
          std::set<int> s = {3, 1, 4, 1, 5, 9}; // 自动排序并去重
          for (int x : s) {
              std::cout << x << " "; // 输出: 1 3 4 5 9
          }
          std::cout << std::endl;
      
          // std::map (映射,键值对,键唯一)
          std::map<std::string, int> m = {{"apple", 1}, {"banana", 2}, {"orange", 3}};
          m["grape"] = 4; // 插入新的键值对
          std::cout << m["banana"] << std::endl; // 输出: 2
            for (const auto& pair : m) {
              std::cout << pair.first << ": " << pair.second << " ";
          } //apple: 1 banana: 2 grape: 4 orange: 3
          std::cout << std::endl;
          // std::multiset (多重集合,允许重复键)
          std::multiset<int> ms = {3, 1, 4, 1, 5, 9};
          for (int x : ms) {
              std::cout << x << " "; // 输出: 1 1 3 4 5 9
          }
          std::cout << std::endl;
      
          // std::multimap (多重映射,允许重复键)
          std::multimap<std::string, int> mm = {{"apple", 1}, {"banana", 2}, {"apple", 3}};
           for (const auto& pair : mm) {
              std::cout << pair.first << ": " << pair.second << " ";
          } // apple: 1 apple: 3 banana: 2
          std::cout << std::endl;
          return 0;
      }
      
  • 无序关联容器(std::unordered_setstd::unordered_mapstd::unordered_multisetstd::unordered_multimap

    • 特点: 基于哈希表实现,元素不排序,查找、插入、删除操作的平均时间复杂度为O(1),最坏情况下为O(n)。

    • 代码示例:

      #include <iostream>
      #include <unordered_set>
      #include <unordered_map>
      
      int main() {
          // std::unordered_set (无序集合,键唯一)
          std::unordered_set<int> us = {3, 1, 4, 1, 5, 9}; // 不排序,去重
          for (int x : us) {
              std::cout << x << " "; // 输出顺序不确定
          }
          std::cout << std::endl;
      
          // std::unordered_map (无序映射,键值对,键唯一)
          std::unordered_map<std::string, int> um = {{"apple", 1}, {"banana", 2}, {"orange", 3}};
          um["grape"] = 4; // 插入新的键值对
          std::cout << um["banana"] << std::endl; // 输出: 2
      
          // std::unordered_multiset (无序多重集合,允许重复键)
          // std::unordered_multimap (无序多重映射,允许重复键)
          // ... (用法类似,不再赘述)
           for (const auto& pair : um) {
              std::cout << pair.first << ": " << pair.second << " ";
          }//输出顺序不确定
          std::cout << std::endl;
          return 0;
      }
      

3. 容器适配器

  • std::stack

    • 特点: 栈,后进先出(LIFO),默认基于std::deque实现。

    • 代码示例:

      #include <iostream>
      #include <stack>
      
      int main() {
          std::stack<int> s;
          s.push(1);
          s.push(2);
          s.push(3);
      
          while (!s.empty()) {
              std::cout << s.top() << " "; // 输出: 3 2 1
              s.pop();
          }
          std::cout << std::endl;
      
          return 0;
      }
      
  • std::queue

    • 特点: 队列,先进先出(FIFO),默认基于std::deque实现。

    • 代码示例:

      #include <iostream>
      #include <queue>
      
      int main() {
          std::queue<int> q;
          q.push(1);
          q.push(2);
          q.push(3);
      
          while (!q.empty()) {
              std::cout << q.front() << " "; // 输出: 1 2 3
              q.pop();
          }
          std::cout << std::endl;
      
          return 0;
      }
      
  • std::priority_queue

    • 特点: 优先队列,元素按优先级排序(默认最大堆,最大元素在队首),默认基于std::vector实现。

    • 代码示例:

      #include <iostream>
      #include <queue>
      
      int main() {
          std::priority_queue<int> pq; // 默认最大堆
          pq.push(3);
          pq.push(1);
          pq.push(4);
          pq.push(1);
          pq.push(5);
      
          while (!pq.empty()) {
              std::cout << pq.top() << " "; // 输出: 5 4 3 1 1
              pq.pop();
          }
          std::cout << std::endl;
           // 最小堆
          std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
          minHeap.push(3);
          minHeap.push(1);
          minHeap.push(4);
          while (!minHeap.empty()) {
              std::cout << minHeap.top() << " "; // 1 3 4
              minHeap.pop();
          }
          std::cout << std::endl;
          return 0;
      }
      

容器详细对比及代码示例

下面将详细对比每种容器的添加、删除、扩容等操作,并提供代码示例。为了代码简洁,示例中主要展示关键操作,省略了部分头文件包含和命名空间声明。

容器添加元素删除元素扩容机制随机访问插入/删除效率 (非首尾)适用场景
vectorpush_back() (尾部)pop_back() (尾部), erase() (任意位置)当容量不足时,自动分配更大的内存空间(通常是原来的两倍),并将现有元素复制到新空间。支持需要快速随机访问,且主要在尾部进行插入/删除操作的情况。
dequepush_back() (尾部), push_front() (头部)pop_back() (尾部), pop_front() (头部), erase() (任意位置)类似于 vector,但可以在头部和尾部都进行高效的插入/删除。通常采用多个固定大小的缓冲区,当某个缓冲区满时,会分配新的缓冲区。支持需要在两端都进行频繁插入/删除操作,且需要随机访问的情况。
listpush_back() (尾部), push_front() (头部), insert() (任意位置)pop_back() (尾部), pop_front() (头部), erase() (任意位置)不需要扩容,每次插入/删除元素都只涉及指针的修改。不支持需要在任意位置频繁插入/删除元素,且不需要随机访问的情况。
forward_listpush_front() (头部), insert_after() (任意位置)pop_front() (头部), erase_after() (任意位置)不需要扩容,每次插入/删除元素都只涉及指针的修改。不支持list 类似,但更节省内存,适用于不需要双向迭代的情况。
array不支持添加不支持删除不支持扩容,大小在编译时确定。支持-需要固定大小数组,且不需要动态调整大小的情况。
setinsert()erase()通常使用红黑树实现,插入/删除元素时会自动调整树结构以保持平衡。不支持对数需要存储唯一元素,并自动排序的情况。
multisetinsert()erase()set 类似,但允许键重复。不支持对数需要存储可重复元素,并自动排序的情况。
mapinsert()[] 操作符erase()set 类似,但存储的是键值对。不支持对数需要存储键值对,并根据键自动排序的情况。
multimapinsert()erase()map 类似,但允许键重复。不支持对数需要存储可重复键值对,并根据键自动排序的情况。
unordered_setinsert()erase()使用哈希表实现,插入/删除元素时会根据哈希函数计算位置。当负载因子超过一定阈值时,会自动进行 rehash 操作,重新分配更大的哈希表。不支持平均常数,最坏线性需要存储唯一元素,且不关心顺序,对插入/删除效率要求较高的情况。
unordered_multisetinsert()erase()unordered_set 类似,但允许键重复。不支持平均常数,最坏线性需要存储可重复元素,且不关心顺序,对插入/删除效率要求较高的情况。
unordered_mapinsert()[] 操作符erase()unordered_set 类似,但存储的是键值对。不支持平均常数,最坏线性需要存储键值对,且不关心顺序,对插入/删除效率要求较高的情况。
unordered_multimapinsert()erase()unordered_map 类似,但允许键重复。不支持平均常数,最坏线性需要存储可重复键值对,且不关心顺序,对插入/删除效率要求较高的情况。
stackpush()pop()基于其他容器(如 deque)实现,没有自己的扩容机制。不支持-需要后进先出 (LIFO) 的数据结构的情况。
queuepush()pop()基于其他容器(如 deque)实现,没有自己的扩容机制。不支持-需要先进先出 (FIFO) 的数据结构的情况。
priority_queuepush()pop()通常使用堆实现,插入元素时会自动调整堆结构以保持优先级顺序。不支持对数需要按照优先级访问元素的情况。

代码示例

#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <forward_list>
#include <array>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <stack>
#include <queue>

int main() {
    // vector
    std::vector<int> vec;
    vec.push_back(10);
    vec.push_back(20);
    vec.erase(vec.begin()); // 删除第一个元素

    // deque
    std::deque<int> deq;
    deq.push_back(10);
    deq.push_front(5);
    deq.pop_front();

    // list
    std::list<int> lst;
    lst.push_back(10);
    lst.push_front(5);
    lst.insert(lst.begin(), 7); // 在开头插入
    lst.erase(lst.begin());

    // forward_list
    std::forward_list<int> flst;
    flst.push_front(10);
    flst.insert_after(flst.before_begin(), 5); // 在开头插入
    flst.erase_after(flst.before_begin());

    // array
    std::array<int, 3> arr = {1, 2, 3}; // 大小固定

    // set
    std::set<int> st;
    st.insert(10);
    st.insert(5);
    st.insert(10); // 重复元素不会被插入
    st.erase(5);

    // map
    std::map<int, std::string> mp;
    mp.insert({1, "one"});
    mp[2] = "two"; // 使用 [] 操作符插入
    mp.erase(1);

    // unordered_set
    std::unordered_set<int> ust;
    ust.insert(10);
    ust.insert(5);
    ust.erase(5);

    // unordered_map
    std::unordered_map<int, std::string> ump;
    ump.insert({1, "one"});
    ump[2] = "two";
    ump.erase(1);

    // stack
    std::stack<int> stk;
    stk.push(10);
    stk.pop();

    // queue
    std::queue<int> que;
    que.push(10);
    que.pop();

    // priority_queue
    std::priority_queue<int> pq;
    pq.push(10);
    pq.push(5);
    pq.push(15);
    std::cout << pq.top() << std::endl; // 输出 15 (最大值)
    pq.pop();

    return 0;
}

总结

选择合适的容器取决于你的具体需求:

  • 如果需要频繁的随机访问,vectordeque 是更好的选择。
  • 如果需要频繁在任意位置插入/删除元素,listforward_list 更合适。
  • 如果需要存储唯一元素并自动排序,set 是理想选择。
  • 如果需要存储键值对并根据键自动排序,map 是首选。
  • 如果对插入/删除效率要求很高,且不关心元素顺序,可以考虑无序关联容器 (unordered_set, unordered_map 等)。
  • 如果需要特定的数据访问模式 (LIFO 或 FIFO),可以使用容器适配器 (stack, queue)。
  • 如果需要按照优先级访问元素,priority_queue 是最佳选择。

希望这份详细的对比和代码示例能帮助你更好地理解和使用 C++ STL 容器!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值