(未完待修改)
容器所属:容器属于STL中的一部分
容器库是类模板与算法的汇集,允许程序员简单地访问常见数据结构,例如队列、链表和栈。有三类容器——顺序容器、关联容器和无序关联容器——每种都被设计为支持不同组的操作。
容器管理为其元素分配的存储空间,并提供直接或间接地通过迭代器(拥有类似指针属性的对象)访问它们的函数。
大多数容器拥有至少几个常见的成员函数,并共享功能。特定应用的最佳容器不仅依赖于提供的功能,还依赖于对于不同工作量的效率。
可分为三大类:
1、序列式容器
array、vector、deque、list、forward_list
2、关联式容器
set/multiset、map/multimap
3、无需容器
unordered_set/unordered_multiset、unordered_map/unordered_multimap
容器提供的函数:
Sequence containers(序列式容器)
Associative containers(关联式容器)
容器数据结构解析:
vector:采用的数据结构非常简单,线性连续空间。
vector是动态空间,随着数据的加入,内部机制会扩充空间来容纳新元素,vector的实现技术,关键在于对其大小的控制以及重新配置时的数据移动效率。
所谓动态增加大小,并不是在原有的空间之后连接新的空间,而是以原来大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后在原内容之后构造新元素,并释放原空间。
在C++11之前缩减vector容量的小窍门,两个容器交换内容,两者的容量也会交换,虽然保留了元素,但是缩减了容量。
C++11提供了shrink_to_fit()函数。
注意点: vector的任何操作一旦引起空间重新配置,指向原vector的所有迭代器都会失效,务必小心。
list:不仅仅是一个双向链表,而且是一个环状双向链表,只需一个指针,便可遍历一个整个链表。
list在空间上是不连续的,每次新增一个元素或者删除一个元素,就配置一个或者释放一个元素空间,空间运用精准一点不浪费,对元素的插入或删除永远是常数时间。
deque:是一种双向开口的连续空间(逻辑上),可以再头部尾部进行插入和删除操作
deque是由一段一段的连续空间组成,通过一个所谓的map来维护空间的连续性,map是一块连续的空间每个元素都是一个指针,指向一块较大的连续性空间,此空间是deque真正存储数据的空间。
在deque的中间插入删除元素都会导致迭代器的失效,deque的内存冲分配优于vector,不必重新分配时复制所有元素。
set/multiset:会根据特定规则准则,自动将元素排序,通常以红黑树实现。
map/multimap:会根据key来自动排序,通过红黑树来实现
map提供了下标操作符,支持元素的直接访问,优点是方便的添加元素,缺点容易误添加元素。
如:cout<<mymap['hello']; 如果不存在hello那么此时自动添加了一个新的元素进来。
unordered_set/unordered_multiset、unordered_map/unordered_multimap:以hashtable来实现。
各容器的使用时机:
迭代器非法化:
只读方法决不非法化迭代器或引用。修改容器内容的方法可能非法化迭代器和/或引用,总结于此表格。
类别 | 容器 | 插入后…… | 擦除后…… | 条件 | ||
---|---|---|---|---|---|---|
迭代器合法? | 引用合法? | 迭代器合法? | 引用合法? | |||
顺序容器 | array | N/A | N/A | |||
vector | 否 | N/A | 插入更改容量 | |||
是 | 是 | 被修改元素前 | ||||
否 | 否 | 于被修改元素或其后 | ||||
deque | 否 | 是 | 是,除了被擦除元素 | 修改首或尾元素 | ||
否 | 否 | 仅修改中部 | ||||
list | 是 | 是,除了被擦除元素 | ||||
forward_list | 是 | 是,除了被擦除元素 | ||||
关联容器 | set | 是 | 是,除了被擦除元素 | |||
无序关联容器 | unordered_set | 否 | 是 | N/A | 插入导致重哈希 | |
是 | 是,除了被擦除元素 | 无重哈希 |
此处插入指代任何添加一或多个元素到容器的方法,而擦除指代任何从容器移除一或多个元素的方法。
- 插入方法的例子是 std::set::insert 、 std::map::emplace 、 std::vector::push_back 和 std::deque::push_front 。
- 注意 std::unordered_map::operator[] 也算,因为它可能插入元素到 map 中。
- 擦除方法的例子是 std::set::erase 、 std::vector::pop_back 、 std::deque::pop_front 和 std::map::clear 。
clear
非法化所有迭代器和引用。因为它擦除所有元素,这在技术上遵照上述规则。
尾后迭代器需要特别留意。通常像指向未被擦除元素的正常迭代器一般非法化此迭代器。故 std::set::end 决不被非法化, std::unordered_set::end 仅在重哈希时被非法化, std::vector::end 始终被非法化(因为它始终出现在被修改元素后),以此类推。
有一个例外:删除 std::deque 末元素的擦除操作会非法化尾后迭代器,尽管它不是容器的被擦除元素(或者说根本不是元素)。与 std::deque 迭代器的通用规则结合后,最终结果是不非法化 std::deque::end 的唯一修改操作是删除首元素,而非末元素的擦除。
线程安全
-
能同时在不同容器上由不同线程调用所有容器函数。更广泛而言, C++ 标准库函数不读取能通过其他线程访问的对象,除非这些对象能直接或间接地经由函数参数,包含 this 指针访问。
-
能同时在同一容器上由不同线程调用 const 成员函数。而且,成员函数
begin()
、end()
,rbegin()
、rend()
、front()
、back()
、data()
、find()
、lower_bound()
、upper_bound()
、equal_range()
、at()
和除了关联容器中的operator[]
对于线程安全的目标表现如同 const (即它们亦能同时在同一容器上由不同线程调用)。更广泛而言, C++ 标准库函数不修改对象,除非这些对象能直接或间接地经由函数参数,包含 this 指针访问。 -
同一容器中不同元素能由不同线程同时修改,除了 std::vector<bool> 的元素(例如, std::future 对象的 vector 能从多个线程接收值)。
-
迭代器操作(例如自增迭代器)读但不修改底层容器,而且能与同一容器上的其他迭代器操作同时由 const 成员函数执行。非法化任何迭代器的容器操作修改容器,且不能与任何在既存迭代器上的操作同时执行,即使这些迭代器未被非法化。
-
同一容器上的元素可以同时由不指定为访问这些元素的函数修改。更广泛而言, C++ 标准库函数不间接读取能从其参数访问的对象(包含容器的其他对象),除非其规定要求如此。
-
任何情况下,容器操作(还有算法,或其他 C++ 标准库函数)可于内部并行化,只要不更改用户可见的结果(例如 std::transform 可并行化,但指定了按顺序观览序列的每个元素的 std::for_each 不行)