C++ 中容器合集

序列容器

string

string 是模板 basic_string 对于 char 类型的特化,可以认为是一个只存放字符 char 类型数据的容器。“真正”的容器类与 string 的最大不同点是里面可以存放任意类型的对象。
string 具有下列成员函数:

  • begin 可以得到对象起始点
  • end 可以得到对象的结束点
  • empty 可以得到容器是否为空
  • size 可以得到容器的大小
  • swap 可以和另外一个容器交换其内容
    string

不管是内存布局,还是成员函数,string 和 vector 是非常相似的。
string 当然是为了存放字符串。和简单的 C 字符串不同:

  • string 负责自动维护字符串的生命周期
  • string 支持字符串的拼接操作(如之前说过的 + 和 +=)
  • string 支持字符串的查找操作(如 find 和 rfind)
  • string 支持从 istream 安全地读入字符串(使用 getline)
  • string 支持给期待 const char* 的接口传递字符串内容(使用 c_str)
  • string 支持到数字的互转(stoi 系列函数和 to_string)
  • 等等

一般不建议在接口中使用 const string&,除非确知调用者已经持有 string:如果函数里不对字符串做复杂处理的话,使用 const char* 可以避免在调用者只有 C 字符串时编译器自动构造 string,这种额外的构造和析构代价并不低。反过来,如果实现较为复杂、希望使用 string 的成员函数的话,那就应该考虑下面的策略:

  • 如果不修改字符串的内容,使用 const string& 或 C++17 的 string_view 作为参数类型。后者是最理想的情况,因为即使在只有 C 字符串的情况,也不会引发不必要的内存复制。
  • 如果需要在函数内修改字符串内容、但不影响调用者的该字符串,使用 string 作为参数类型(自动拷贝)。
  • 如果需要改变调用者的字符串内容,使用 string& 作为参数类型(通常不推荐)。

vector

动态数组,它基本相当于 Java 的 ArrayList 和 Python 的 list。
和 string 相似,vector 的成员在内存里连续存放,同时 begin、end、front、back 成员函数指向的位置也和 string 一样,大致如下图所示:vector
所有容器的共同点

  • 容器都有开始和结束点
  • 容器会记录其状态是否非空
  • 容器有大小
  • 容器支持交换

除了容器类的共同点,vector 允许下面的操作(不完全列表):

  • 可以使用中括号的下标来访问其成员(同 string)
  • 可以使用 data 来获得指向其内容的裸指针(同 string)
  • 可以使用 capacity 来获得当前分配的存储空间的大小,以元素数量计(同 string)
  • 可以使用 reserve 来改变所需的存储空间的大小,成功后 capacity() 会改变(同 string)
  • 可以使用 resize 来改变其大小,成功后 size() 会改变(同 string)
  • 可以使用 pop_back 来删除最后一个元素(同 string)
  • 可以使用 push_back 在尾部插入一个元素(同 string)
  • 可以使用 insert 在指定位置前插入一个元素(同 string)
  • 可以使用 erase 在指定位置删除一个元素(同 string)
  • 可以使用 emplace 在指定位置构造一个元素
  • 可以使用 emplace_back 在尾部新构造一个元素
    当 push_back、insert、reserve、resize 等函数导致内存重分配时,或当 insert、erase 导致元素位置移动时,vector 会试图把元素“移动”到新的内存区域。vector 通常保证强异常安全性,如果元素类型没有提供一个保证不抛异常的移动构造函数,vector 通常会使用拷贝构造函数。因此,对于拷贝代价较高的自定义元素类型,我们应当定义移动构造函数,并标其为 noexcept,或只在容器中放置对象的智能指针。
    对于 vector 里的内容,结果是一样的;但使用 push_back 会额外生成临时对象,多一次(移动或拷贝)构造和析构。如果是移动的情况,那会有小幅性能损失;如果对象没有实现移动的话,那性能差异就可能比较大了。

deque

deque 的意思是 double-ended queue,双端队列。它主要是用来满足下面这个需求:

  • 容器不仅可以从尾部自由地添加和删除元素,也可以从头部自由地添加和删除。
    deque 的接口和 vector 相比,有如下的区别:
  • deque 提供 push_front、emplace_front 和 pop_front 成员函数。
  • deque 不提供 data、capacity 和 reserve 成员函数。
    deque
  • 如果只从头、尾两个位置对 deque 进行增删操作的话,容器里的对象永远不需要移动。
  • 容器里的元素只是部分连续的(因而没法提供 data 成员函数)。
  • 由于元素的存储大部分仍然连续,它的遍历性能是比较高的。
  • 由于每一段存储大小相等,deque 支持使用下标访问容器元素,大致相当于 index[i / chunk_size][i % chunk_size],也保持高效。

list

list 在 C++ 里代表双向链表。和 vector 相比,它优化了在容器中间的插入和删除:

  • list 提供高效的、O(1) 复杂度的任意位置的插入和删除操作。
  • list 不提供使用下标访问其元素。
  • list 提供 push_front、emplace_front 和 pop_front 成员函数(和 deque 相同)。
  • list 不提供 data、capacity 和 reserve 成员函数(和 deque 相同)。
    list
    虽然 list 提供了任意位置插入新元素的灵活性,但由于每个元素的内存空间都是单独分配、不连续,它的遍历性能比 vector 和 deque 都要低。

forward_list

从 C++11 开始,前向列表 forward_list 成了标准的一部分。
forward_list
对于 forward_list,没有从指定的位置之前插入一个元素。标准库提供了一个 insert_after 作为替代。此外,它跟 list 相比还缺了下面这些成员函数:

  • back
  • size
  • push_back
  • emplace_back
  • pop_back

queue

先进先出(FIFO)的数据结构。
queue 缺省用 deque 来实现。它的接口跟 deque 比,有如下改变:

  • 不能按下标访问元素
  • 没有 begin、end 成员函数
  • 用 emplace 替代了 emplace_back,用 push 替代了 push_back,用 pop 替代了 pop_front;没有其他的 push_…、pop_…、emplace…、insert、erase 函数
    queue

stack

栈 stack 是后进先出(LIFO)的数据结构。
stack 缺省也是用 deque 来实现,但它的概念和 vector 更相似。它的接口跟 vector 比,有如下改变:

  • 不能按下标访问元素
  • 没有 begin、end 成员函数
  • back 成了 top,没有 front
  • 用 emplace 替代了 emplace_back,用 push 替代了 push_back,用 pop 替代了 pop_back;没有其他的 push_…、pop_…、emplace…、insert、erase 函数
    stack

priority_queue

priority_queue 也是一个容器适配器。和其他容器适配器不同是在于它用到了比较函数对象(默认是 less)。它和 stack 相似,支持 push、pop、top 等有限的操作,但容器内的顺序既不是后进先出,也不是先进先出,而是(部分)排序的结果。在使用缺省的 less 作为其 Compare 模板参数时,最大的数值会出现在容器的“顶部”。如果需要最小的数值出现在容器顶部,则可以传递 greater 作为其 Compare 模板参数。

关联容器

关联容器有 set(集合)、map(映射)、multiset(多重集)和 multimap(多重映射)。在 C++ 外这些容器常常是无序的;在 C++ 里关联容器则被认为是有序的。
关联容器是一种有序的容器。名字带“multi”的允许键重复,不带的不允许键重复。set 和 multiset 只能用来存放键,而 map 和 multimap 则存放一个个键值对。

与序列容器相比,关联容器没有前、后的概念及相关的成员函数,但同样提供 insert、emplace 等成员函数。此外,关联容器都有 find、lower_bound、upper_bound 等查找函数,结果是一个迭代器:

  • find(k) 可以找到任何一个等价于查找键 k 的元素(!(x < k || k < x))
  • lower_bound(k) 找到第一个不小于查找键 k 的元素(!(x < k))
  • upper_bound(k) 找到第一个大于查找键 k 的元素(k < x)
    如果在声明关联容器时没有提供比较类型的参数,缺省使用 less 来进行排序。如果键的类型提供了比较算符 < 的重载,我们不需要做任何额外的工作。否则,我们就需要对键类型进行 less 的特化,或者提供一个其他的函数对象类型。
    对于自定义类型,我推荐尽量使用标准的 less 实现,通过重载 <(及其他标准比较运算符)对该类型的对象进行排序。存储在关联容器中的键一般应满足严格弱序关系:
  • 对于任何该类型的对象 x:!(x < x)(非自反)
  • 对于任何该类型的对象 x 和 y:如果 x < y,则 !(y < x)(非对称)
  • 对于任何该类型的对象 x、y 和 z:如果 x < y 并且 y < z,则 x < z(传递性)
  • 对于任何该类型的对象 x、y 和 z:如果 x 和 y 不可比(!(x < y) 并且 !(y < x))并且 y 和 z 不可比,则 x 和 z 不可比(不可比的传递性)

无序关联容器

从 C++11 开始,每一个关联容器都有一个对应的无序关联容器,它们是:

  • unordered_set
  • unordered_map
  • unordered_multiset
  • unordered_multimap
    这些容器和关联容器非常相似,主要的区别就在于它们是“无序”的。这些容器不要求提供一个排序的函数对象,而要求一个可以计算哈希值的函数对象。
    从实际的工程角度,无序关联容器的主要优点在于其性能。关联容器和 priority_queue 的插入和删除操作,以及关联容器的查找操作,其复杂度都是 O(log(n)),而无序关联容器的实现使用哈希表 [5],可以达到平均 O(1)!但这取决于我们是否使用了一个好的哈希函数:在哈希函数选择不当的情况下,无序关联容器的插入、删除、查找性能可能成为最差情况的 O(n),那就比关联容器糟糕得多了。

array

C 数组在 C++ 里继续存在,主要是为了保留和 C 的向后兼容性。C 数组本身和 C++ 的容器相差是非常大的:

  • C 数组没有 begin 和 end 成员函数(虽然可以使用全局的 begin 和 end 函数)
  • C 数组没有 size 成员函数(得用一些模板技巧来获取其长度)
  • C 数组作为参数有退化行为,传递给另外一个函数后那个函数不再能获得 C 数组的长度和结束位置
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值