(转载)众多C++容器如何挑选?

本文详细介绍了C++ STL中的各种容器类型及其适用场景,包括无规律容器如vector、deque,有规律容器如priority_queue,以及关联容器等,并给出了实际开发中选择合适容器的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:https://blog.youkuaiyun.com/deng821776892/article/details/105663142/

前言

相信大家在学习C++过程中都会学习STL容器,至于容器实现的原理可以去看侯捷老师《STL源码剖析》。网上也有很多资源,为了支持正版这里就不放出来了,有需要可以评论区留言。STL容器的实现很复杂但是使用还是很一致的,相较于Java来说,C++的函数都是一致的。比如在所有容器里面调用大小值都是使用size()函数。但是Java就不是这样,处理字符串的时候是使用size()函数,处理list集合却是使用length()函数。这一点看起来很简单,但是对于开发人员来说意味着需要多记忆一个内容。在C++里面绝大多数的容器都是拥有相同的成员函数,针对那一两个异类,我们可以单独进行分析即可。
*但是这么多容器我应该选择哪一个呢?*今天就给大家弄明白。

无规律容器

对于无规律容器来说,所有数据在内部都是无规律存放的。

vector

常见的操作:

  • 可以使用中括号的下标来访问其成员;
  • 可以使用 data 来获得指向其内容的裸指针;
  • 可以使用 capacity 来获得当前分配的存储空间的大小,以元素数量计;
  • 可以使用 reserve 来改变所需的存储空间的大小,成功后 capacity()会改变;
  • 可以使用 resize 来改变其大小,成功后 size() 会改变;
  • 可以使用 pop_back 来删除最后一个元素;
  • 可以使用 push_back 在尾部插入一个元素;
  • 可以使用 insert 在指定位置前插入一个元素;
  • 可以使用 erase 在指定位置删除一个元素;
  • 可以使用 emplace 在指定位置构造一个元素;
  • 可以使用 emplace_back 在尾部新构造一个元素。

但是vector有一个特点就是在当前分配的空间占满之后,会重新分配一个两倍大小的空间,然后将原来的元素拷贝过去。
有的人可能会问为什么不是移动过去呢? 因为移动的话子在这过程中出现异常情况,那么原来的数据都会丢失,不满足强异常安全如果我们需要使用移动构造函数,可以进行声明然后将函数设置为noexcept
由于vector最大的问题就是大小增长带来的移动问题,所以尽可能提前预留好足够的空间,这样可以有效地提升vector的性能。

deque

deque是指的double-ended queue
deque的接口和vector相比有以下区别:

  • deque 提供 push_front、emplace_front 和 pop_front 成员函数。
  • deque 不提供 data、capacity 和 reserve 成员函数。
    这是deque的内部布局:
    在这里插入图片描述
    可以看到的是,大部分都是顺序存储。所以deque满足vector的一些优点,而且能够克服vector随着大小增长导致的内存数据移动问题。 并且可以头尾增删元素。
list&forward_list

list在C++里面代表双向链表。
list内部结构:
在这里插入图片描述
同vector相比:

  • list 不提供使用下标访问其元素;
  • list 提供 push_front、emplace_front 和 pop_front 成员函数;
  • list 不提供 data、capacity 和 reserve 成员函数。

list的特点:不适合于遍历容器,但是对于在中间频繁插入或删除元素十分有效。
list由于自身结构的特点,一部分标准算法无法实施,所以提供了成员函数作为替代:

mergeremoveremove_ifreversesortunique

既然有了一个list为什么还要来一个forward_list呢? 由于list至少要有两个指针域会增加内存大小,而forward_list仅仅需要一个指针域。对于一些对内存敏感的项目,forward_list是一个不错的选择。
forward_list内部结构:
在这里插入图片描述

queue&stack

至于queue和stack都是依赖于deque的容器,也称为容器适配器。相较于deque可以双端操作来说,queue和stack都砍掉了一部分功能的操作。
queue只能在后面push(),然后在前面pop()
stack只能在后面push(),然后在后面pop()

为什么对于queue(stack)的pop() 只能发挥void,而不是返回容器的top元素呢?
因为pop()返回元素时,如果要满足异常安全,实现起来十分复杂。但是如果将这两步分离,则尽管pop()可能出现异常但是我们已经不关心内部的元素值了,所以出现异常是满足异常安全的。而top()是一个只读操作,满足异常安全。

有规律容器

有规律容器,数据存放的位置与数据本身有关。

priority_queue

priority_queue和queue类似都是底子是其它容器,是一个容器适配器。他的特点在于,如果使用less作为其比较模版的话,最大的数值会出现在容器的“顶部”。如果我要最小值出现在容器顶部,那么可以将比较模版修改为greater。既然说到了less比较模版,那么就来看一下less吧。

less是一个函数对象,他是通过重载operator()实现的,行为就是对指定对象进行<比较操作。

有序关联容器

有序关联容器包括set,map,multiset,multimap,内部是由红黑树实现的。红黑树

关联容器没有前后之分,但是依然提供了insert,emplace等函数。而且由于红黑树结构的特点,标准的find函数没办法实现查找功能。所以加入了几个查找函数:

  • find(k) 可以找到任何一个等价于查找键 k 的元素(!(x < k || k < x))
  • lower_bound(k)
    找到第一个不小于查找键 k 的元素(!(x < k))
  • upper_bound(k) 找到第一个大于查找键 k 的元素(k < x)
无序关联容器

无序关联容器,底层是用hash函数实现的。哈希函数大家都不陌生,但是针对复杂的类,hash函数需要进行特化,使得对于不同的对象值,得到的哈希结构尽可能均匀分布。

无序关联容器包括:unordered_set,unordered_map,unordered_multiset,unordered_multimap

无序关联容器的特点在于其性能,插入删除操作时间复杂度均为O(log(n))

array

这个array是为了和C语言里面的数组兼容而设置的。但是他和C语言的数组还有一些区别:

  • C 数组没有 begin 和 end 成员函数(虽然可以使用全局的 begin 和 end 函数);
  • C 数组没有 size 成员函数;
  • C 数组作为参数有退化行为,传递给另外一个函数后那个函数不再能获得 C 数组的长度和结束位置。

选择方法

根据功能选择:

需要先进先出,应选择queue;需要先进后出,应选择stack。

根据主要操作:

需要大量插入操作,应选择list;需要大量遍历操作,应选择vector,array,deque。

根据数据特征:

存在大小关系,应选择有序关联容器;数据之间不存在强关系,应选择无序关联容器。

根据数据量是否固定:

数据量不固定,应选择vector;数据量固定,应选择array。

总结

到这里都将近3000字了,但是STL的故事却不是这三两千字能够说得清楚的。虽然平时的使用中对于STL内部的结构并不需要十分清楚。但是了解的内部结构对于理解容器的一些操作有好处。正如侯捷老师说的那句:源码之前,了无秘密。

但是希望通过这一篇文章能够让大家对于C++容器能够有一个高屋建瓴的认识,对于C++容器大厦的结构有所了解。也希望通过这一篇文章能够让大家能够回答标题的那个问题,众多C++容器如何挑选?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值