C++17标准:第26章 容器库【TEMP】

本文详细介绍了C++标准库中的容器组件,包括容器的基本概念、序列容器、关联容器等,并阐述了各种容器的特点和应用场景。

26 容器库

译注:翻译进行中。部分过长表格将在翻译完成后修订。将考虑构建英文修订版本。


26.1 简介

  • 1 本节描述C++程序中可以用来组织信息收集的组件。
  • 2 下面几节描述了容器的要求,以及序列容器和关联容器的组件,总结在表82中。

表82——容器库总结

子章节头文件
26.2 要求
26.3 序列容器<array>
<deque>
<forward_list>
<list>
<vector>
26.4 关联容器<map>
<set>
26.5 无序关联容器<unordered_map>
<unordered_set>
26.6 容器适配器<queue>
<stack>

26.2 容器要求

26.2.1 容器要求简介

  • 1 容器是存储其它对象的对象。它们通过构造函数、析构函数、插入和删除操作来控制这些对象的分配和释放。
  • 2 本节中所有的复杂度要求仅以包含对象的操作数表示。【例:类型vector<vector<int>>的构造函数有线性复杂度,即使拷贝每个包含的vector<int>的复杂度本身就是线性的。——例结束
  • 3 本节中的组件都声明了一个allocator_type,存储在这些组件中的对象应使用allocator_traits<allocator_type>::rebind_traits<U>::construct函数构造并使用allocator_traits<allocator_type>::rebind_traits<U>::destroy函数销毁(23.10.8.2),且Uallocator_type::value_type或一种容器使用的内置类型。这些函数仅为容器的元素类型调用,不为容器使用的内置类型调用。【注:这意味着,例如,基于节点的容器可能需要构造包含对齐缓冲区的节点,并调用construct将元素放置到缓冲区中。——注结束
  • 4 在表838485中,X表示一个包含对象类型为T的容器类,ab表示类型为X的值,u表示一个标识符,r表示类型X的一个非常量值,rv表示类型X的一个非常量右值。

表83——容器要求

表达式返回类型操作
语义
断言/说明
前置/后置条件
复杂度
X::value_typeT要求:TX中是Erasable(见26.2.1下方)编译时
X::referenceT&编译时
X::const_referenceconst T&编译时
X::iterator值类型为T的迭代器类型任何满足前向迭代器的迭代器类别。
可转换为X::const_iterator
编译时
X::const_iterator值类型为T的常量迭代器类型任何满足前向迭代器的迭代器类别。编译时
X::difference_type有符号整数类型X::iteratorX::const_iterator的差距类型相同编译时
X::size_type无符号整数类型size_type能表示difference_type的任意非负值编译时
X u;后置条件:u.empty()常量
X()后置条件:X().empty()常量
X(a)要求:TXCopyInsertable(见下)。
后置条件:a == X(a)
线性
X u(a);
X u = a;
要求:TXCopyInsertable(见下)。
后置条件:u == a
线性
X u(rv);
X u = rv;
后置条件:u应与在此次构造前rv拥有的值相等(说明B)
a = rvX&a存在的所有元素被移动赋值或销毁a应与在此次赋值前rv拥有的值相等线性
(&a)->~X()void析构函数被应用于a的每一个元素;任何获得的内存被释放线性
a.begin()iterator;对常量aconst_iterator常量
a.end()iterator;对常量aconst_iterator常量
a.cbegin()const_iteratorconst_cast<X const&>
(a).begin();
常量
a.cend()const_iteratorconst_cast<X const&>
(a).end();
常量
a == b可转换为bool==是一种相等关系。
equal(a.begin(),
a.end(),
b.begin(),
b.end())
要求:TEqualityComparablea.size()
!= b.size()
则为常量,否则为线性
a != b可转换为bool相当于!(a == b)线性
a.swap(b)void交换ab的内容(说明A)
swap(a, b)voida.swap(b)(说明A)
r = aX&后置条件:r == a线性
a.size()size_typedistance(a.begin(),
a.end())
常量
a.max_size()size_type对最大可能容器的
distance(a.begin(),
a.end())
常量
a.empty()可转换为boola.begin() == a.end()常量
  • 那些标记了“(说明A)”或“(说明B)”的条目对于array有线性复杂度,对于所有其它标准容器有常量复杂度。【注:算法equal()在第28章中定义。——注结束
  • 5 成员函数size()返回容器中的元素个数。元素个数被构造函数、插入和删除的规则所定义。
  • 6 begin()返回一个指向容器中首元素的迭代器,end()返回一个容器的逾尾值的迭代器。如果容器为空,那么begin() == end()
  • 7 在下列表达式中:
i == j
i != j
i < j
i <= j
i >= j
i > j
i - j
  • ij表示容器的iterator类型的对象,其中一个或两个都可以被指向同一个元素且在语义上没有变化的容器的const_iterator类型的对象替换。
  • 8 除非另有说明,在本章节中定义的所有容器都使用分配器获得内存(见20.5.3.5)。【注:特别地,容器和迭代器不存储对分配元素的引用而是通过分配器的指针类型,即,作为P类型对象或pointer_traits<P>::template rebind<未指定>,且Pallocator_traits<allocator_type>::pointer——注结束】这些容器类型的复制构造函数通过调用属于被复制的容器的分配器的allocator_traits<allocator_type>::select_on_container_copy_construction获得一个分配器。移动构造函数通过属于被移动容器的分配器的移动构造获得一个分配器。此类分配器的移动构造不应通过异常退出。这些容器类型的所有其它构造函数带有一个const allocator_type&参数。【注:如果构造函数的调用使用可选的分配器参数的默认值,那么Allocator类型必须支持值初始化。——注结束】在每个容器对象的生命周期中或直到分配器被替换为止,此分配器的副本用于由这些构造函数和所有成员函数执行的任何内存分配和元素构造。分配器只能通过赋值或swap()来替换。只要在相应容器的实现中,allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value
    allocator_traits<allocator_type>::propagate_on_container_move_assignment::valueallocator_traits<allocator_type>::propagate_on_container_swap::value为真,分配器替换通过复制赋值、移动赋值或分配器的交换来执行。在本章定义的所有容器类型中,成员get_allocator()返回一个用于构造容器的分配器的副本,或如果该分配器已被替换,则是最近替换的副本。
  • 9 表达式a.swap(b),对于除array外标准容器类型的容器ab,应在不调用任何单个容器元素的移动、复制或交换操作的情况下,交换ab的值。任何属于abComparePredHash类型的左值应是可交换的,应通过调用描述在20.5.3.2中的swap来被交换。如果allocator_traits<allocator_type>::propagate_on_container_swap::value为真,那么allocator_type类型的左值应是可交换的,且ab的分配器也应通过调用描述在20.5.3.2中的swap来被交换。否则,分配器不应被交换,且行为是未定义的,除非a.get_allocator() == b.get_allocator()。每一个在交换前指向一个容器中元素的迭代器都应在交换后指向另一个容器中的同一元素。在交换前,值为a.end()的迭代器是否拥有交换后的值b.end()是不确定的。
  • 10 如果容器的迭代器类型属于双向或随机访问迭代器类别(27.2),则称该容器为可逆的,并满足表84中的附加要求。

表84——可逆容器要求

表达式返回类型断言/说明
前置/后置条件
复杂度
X::reverse_iterator值类型为T的迭代器类型reverse_iterator<iterator>编译时
X::const_reverse_iterator值类型为T的常量迭代器类型reverse_iterator<const_iterator>编译时
a.rbegin()reverse_iterator;对常量a
const_reverse_iterator
reverse_iterator(end())常量
a.rend()reverse_iterator;对常量a
const_reverse_iterator
reverse_iterator(end())常量
a.crbegin()const_reverse_iteratorconst_cast<X const&>(a).rbegin()常量
a.crend()const_reverse_iteratorconst_cast<X const&>(a).rend()常量
  • 11 除非另有规定(见26.2.6.126.2.7.126.3.8.426.3.11.5),本节中定义的所有容器类型满足以下附加要求:
  • (11.1) ——当插入单个元素时,如果异常被insert()emplace()函数抛出,此函数无作用。
  • (11.2) ——如果异常被push_back()push_front()emplace_back()emplace_front()函数抛出,此函数无作用。
  • (11.3) ——erase()clear()pop_back()pop_front()函数不抛出异常。
  • (11.4) ——复制构造函数或返回的迭代器的赋值操作符不抛出异常。
  • (11.5) ——swap()函数不抛出异常。
  • (11.6) ——swap()函数不使指向被交换容器元素的引用、指针或迭代器失效。【注:因为end()迭代器不指向任何元素,所以它可能会失效。——注结束
  • 12 除非另有规定(明确地或通过其他函数定义一个函数),调用容器成员函数或将容器作为参数传递给库函数,不应使指向容器中对象的迭代器失效或改变容器中对象的值。
  • 13 连续容器是支持随机访问迭代器且成员类型iteratorconst_iterator为连续迭代器的容器。
  • 1485列出了为某些容器类型而不是其它类型提供的操作。提供所列出操作的容器应实现表85中描述的语义,除非另有说明。

表85——可选容器操作

表达式返回类型操作
语义
断言/说明
前置/后置条件
复杂度
a < b可转换为boollexicographical_compare(a.begin(),
a.end(), b.begin(), b.end())
要求:<T的值定义。<是一种全序关系。线性
a > b可转换为boolb < a线性
a <= b可转换为bool!(a > b)线性
a >= b可转换为bool!(a < b)线性
  • 注:算法lexicographical_compare()在第28章中定义。——注结束
  • 15array外,本章和24.3.2中定义的容器满足表86中描述的分配器可感容器的附加要求。
  • 给定分配器类型A,给定具有与T相同的value_type和与allocator_traits<A>::rebind_alloc<T>相同的分配器类型的容器类型X,给定A类型的左值mT*类型的指针pT(可能为const)类型的表达式vT类型的右值rv,定义以下术语。如果X不是分配器可感的,下面的术语被定义就像Aallocator<T>一样——无需创建任何分配器对象,且allocator<T>的用户特化不被实例化:
  • (15.1) ——TX中是DefaultInsertable,意味着下列表达式是规范的:
allocator_traits<A>::construct(m, p)
  • (15.2) ——如果X的元素被此表达式的赋值初始化,它是默认插入的
allocator_traits<A>::construct(m, p)
  • 其中p为元素分配在X中未初始化的存储地址。
  • (15.3) ——TX中是MoveInsertable,意味着下列表达式是规范的:
allocator_traits<A>::construct(m, p, rv)
  • 且它的赋值导致以下后置条件存在:在赋值前,*p的值等于rv的值。【注:rv仍是一个有效对象。它的状态是不确定的。——注结束
  • (15.4) ——TX中是CopyInsertable,意味着除TMoveInsertableX中外,下列表达式是规范的:
allocator_traits<A>::construct(m, p, v)
  • 且它的赋值导致以下后置条件存在:v的值未改变且等于*p
  • (15.5) TargsX中是EmplaceInsertable,对于零个或更多参数的args,意味着下列表达式是规范的:
allocator_traits<A>::construct(m, p, args)
  • (15.6) TX中是Erasable,意味着下列表达式是规范的:
allocator_traits<A>::destroy(m, p)
  • 注:容器调用allocator_traits<A>::construct(m, p, args)来使用args构造在p位置的元素,其中m == get_allocator()allocator中的默认construct将调用::new((void*)p) T(args),但特化的分配器可以选择一个不同的定义。——注结束
  • 16 在表86中,X表示一个value_typeT且使用A类型分配器的分配器可感的容器类,u表示一个变量,ab表示X类型的非常量左值,t表示X类型的一个左值或常量右值,rv表示X类型的一个非常量右值,且mA类型的值。

表86——分配器可感容器要求

表达式返回类型断言/说明
前置/后置条件
复杂度
allocator_typeA要求:allocator_type::value_typeX::value_type相同。编译时
get_allocator()A常量
X()
X u;
要求:ADefaultConstructible
后置条件:u.empty()返回trueu.get_allocator() == A()
常量
X(m)
X u(m);
后置条件:u.empty()返回trueu.get_allocator() == m常量
X(t, m)
X u(t, m);
要求:TX中是CopyInsertable
后置条件:u == tu.get_allocator() == m
线性
X rv
X u(rv);
后置条件:在构造前,u应有与rv相同的元素;在构造前,
u.get_allocator()的值应与rv.get_allocator()的值相同。
常量
X(rv, m)
X u(rv, m);
要求:TX中是MoveInsertable
后置条件:在构造前,u应与rv有相同的元素或其副本,
u.get_allocator() == m
m == rv.get_
allocator()则为常
量,否则为线性
a = tX&要求:TX中是CopyInsertableCopyAssignable
后置条件:a == t
线性
a = rvX&要求:allocator_traits<allocator_type>::
propagate_on_container_move_assignment::valuefalse
TX中是MoveInsertableMoveAssignable
a的所有存在元素被移动赋值或销毁。
后置条件:在赋值前,a应等于rv的值
线性
a.swap(b)void交换ab的内容常量
  • 17 某些容器的成员函数和推导影响行为取决于类型是否规定为输入迭代器或分配器。实现决定一个类型不能为输入迭代器的程度是不确定的,除非最小整数类型不符合输入迭代器的标准。同样地,一个实现决定一个类型不能是一个分配器的程度是不确定的,除非作为一个最小类型A不符合分配器的条件,除非它满足以下两个条件:
  • (17.1) ——限定标识符A::value_type是有效的并表示一个类型(17.8.2)。
  • (17.2) ——当视为一个未赋值的表达式时,表达式declval<A&>().allocate(size_t{})是合法的。

26.2.2 容器数据竞争

  • 1 为了避免数据竞争(20.5.5.9),实现应考虑以下函数为constbeginendrbeginrendfrontbackdatafindlower_boundupper_boundequal_rangeat,及除了关联或无序关联容器之外的operator[]
  • 2 虽然20.5.5.9,但是当同一容器中不同元素中包含的对象的内容同时修改时,实现需要避免数据竞争,vector<bool>除外。
  • 3 【注:对于一个有大于一个大小的vector<int> xx[1] = 5*x.begin() = 10可以在没有数据竞争的情况下同时执行,但是x[0] = 5*x.begin() = 10同时执行可能会导致数据竞争。作为一般规则的例外,对于一个vector<bool> yy[0] = true可以与y[1] = true竞争。——注结束】

26.2.3 序列容器

  • 1 序列容器将一个有限集合的对象组织成严格的线性排列。库提供了四种基本的序列容器:vectorforward_listlistdeque。此外,array被提供为一个提供有限序列操作的序列容器,因为它有固定数量的元素。该库还提供容器适配器,使构建抽象数据类型变得容易,例如stackqueue,通过使用基本顺序容器种类(或者通过使用用户可能定义的其他顺序容器)。
  • 2 序列容器为程序员提供了不同的复杂性权衡,并应被相应地使用。vectorarray是应被默认使用的序列容器类型。当序列中部有频繁插入和删除时,listforward_list应被使用。当大多数插入和删除发生在序列的开头或结尾时,deque是选择的数据结构。
  • 3 在表8788中,X表示一个序列容器类;a表示一个包含元素类型T的类型X的一个值;u表示正被声明的变量的名称;如果限定标识符X::allocator_type是有效的且表示一个类型,A表示X::allocator_type,如果不,A表示allocator<T>ij
    表示满足输入迭代器要求的迭代器且指向可隐式转换为value_type的元素;[i, j)表示一个有效的区间;il指定一个类型initializer_list<value_type>的对象;n表示类型X::size_type的一个值;p表示a的一个有效常量迭代器;q表示a的一个有效可解引用常量迭代器;[q1, q2)表示a中常量迭代器的一个有效区间;t表示X::value_type的一个左值或常量右值;rv表示X::value_type的一个非常量右值。Args表示一个模板参数包;args表示一个有Args&&模式的函数参数包。
  • 4 表达式的复杂度是与序列相关的。

表87——序列容器要求(额外容器)

表达式返回类型断言/说明
前置/后置条件
X(n, t)
X u(n, t);
要求:TX中应CopyInsertable
后置条件:distance(begin(), end()) == n
tn份副本构造一个序列容器
X(i, j)
X u(i, j);
要求:T*iX中应EmplaceConstructible。对于vector
如果迭代器不满足前向迭代器要求(27.2.5),TX中也应MoveInsertable
在区间[i, j)中的每个迭代器应恰好被解引用一次。
后置条件:distance(begin(), end()) == distance(i, j)
构造一个与区间[i, j)相等的序列容器
X(il)相当于X(il.begin(), il.end())
a = ilX&要求:TXCopyInsertableCopyAssignable。将区间[il.begin(),
il.end())赋值给aa的所有存在元素被赋值或销毁。
返回:*this
a.emplace(p, args)iterator要求:TargsXEmplaceConstructible。对于vectordeque
TX中也MoveInsertableMoveAssignable
效果:p前插入一个用std::forward<Args>(args)...构造的T类型对象。
a.insert(p, t)iterator要求:TX中应CopyInsertable。对于vectordeque
T也应CopyAssignable
效果:p前插入一份t的副本。
a.insert(p, rv)iterator要求:TX中应MoveInsertable。对于vectordeque
T也应MoveAssignable
效果:p前插入一份rv的副本。
a.insert(p, n, t)iterator要求:TX中应CopyInsertableCopyAssignable
p前插入tn份副本
a.insert(p, i, j)iterator要求:T*iX中应EmplaceConstructible。对于vectordeque
TX中也应MoveConstructibleMoveAssignable且可交换(20.5.3.2)。
在区间[i, j)中的每个迭代器应恰好被解引用一次。
要求:ij不是a中的迭代器。
p前插入[i, j)中元素的副本
a.insert(p, il)iteratora.insert(p, il.begin(), il.end())
a.erase(q)iterator要求:对于vectordequeTMoveAssignable
效果:删除q指向的元素。
a.erase(q1, q2)iterator要求:对于vectordequeTMoveAssignable
效果:删除区间[q1, q2)中的元素。
a.clear()void销毁a中的所有元素。使指向a的元素的所有引用、指针和迭代器失效,
且可能使逾尾迭代器失效。
后置条件:a.empty()返回true
复杂度:线性。
a.assign(i, j)void要求:T*iX中应EmplaceConstructible。对于vector
如果迭代器不满足前向迭代器要求(27.2.5),TX中也应MoveInsertable
在区间[i, j)中的每个迭代器应恰好被解引用一次。
要求:ij不是a中的迭代器。
[i, j)的一份副本替换a中元素。使指向a的元素的所有引用、
指针和迭代器失效。对于vectordeque,也使逾尾迭代器失效。
a.assign(il)voida.assign(il.begin(), il.end())
a.assign(n, t)void要求:TX中应CopyInsertableCopyAssignable
要求:T不是a中的引用。
tn份副本替换a中元素。对于vectordeque
也使逾尾迭代器失效。
  • 5 a.insert(p, t)返回的迭代器指向插入到a中的t的副本。
  • 6 a.insert(p, rv)返回的迭代器指向插入到a中的rv的副本。
  • 7 a.insert(p, n, t)返回的迭代器指向插入到a中的第一个元素的副本;或如果n == 0,则为p
  • 8 a.insert(p, i, j)返回的迭代器指向插入到a中的第一个元素的副本;或如果i == j,则为p
  • 9 a.insert(p, il)返回的迭代器指向插入到a中的第一个元素的副本;或如果il为空,则为p
  • 10 a.emplace(p, args)返回的迭代器指向从argsa中构造的新元素。
  • 11 a.erase(q)返回的迭代器指向在元素被删除之前紧跟在q后面的元素。如果不存在这样的元素,则返回a.end()
  • 12 a.erase(q1, q2)返回的迭代器指向在任何元素被删除之前q2指向的元素。如果不存在这样的元素,则返回a.end()
  • 13 对于每一个在本章和第24章中定义的序列容器:
    • (13.1) 如果构造函数
template <class InputIterator>
  X(InputIterator first, InputIterator last,
    const allocator_type& alloc = allocator_type());
    • 被用一个不符合输入迭代器的要求的InputIterator类型调用,那么构造函数将不参与重载解决方案。
    • (13.2) 如果有下列形式的成员函数:
template<class InputIterator>
  返回类型 F(const_iterator p,
             InputIterator first, InputIterator last); // 例如insert

template <class InputIterator>
  返回类型 F(InputIterator first, InputIterator last); // 例如append、assign

template <class InputIterator>
  返回类型 F(const_iterator i1, const_iterator i2,
             InputIterator first, InputIterator last); // 例如replace
    • 被用一个不符合输入迭代器的要求的InputIterator类型调用,那么这些函数将不参与重载解决方案。
    • (13.3) 序列容器的推导器将不参与重载解决方案,如果它有一个InputIterator模板参数和为参数推导出的一个不符合输入迭代器的要求的类型,或它有一个Allocator模板参数和为参数推导出的一个不符合分配器的要求的类型。
  • 1488列出了为某些类型的序列容器而不是其他类型提供的操作。实现应为“容器”列中所示的所有容器类型提供这些操作,并应以均摊的常量时间执行这些操作。

表88——可选序列容器操作

表达式返回类型操作语义容器
a.front()reference;对常量aconst_reference*a.begin()basic_stringarraydequeforward_listlistvector
a.back()reference;对常量aconst_reference{ auto tmp = a.end();
--tmp;
return *tmp; }
basic_stringarraydequelistvector
a.emplace_front(args)reference在前端加入用std::forward<Args>(args)...构造的T类型对象。
要求:TargsX中应EmplaceConstructible
返回:a.front()
dequeforward_listlist
a.emplace_back(args)reference在后端加入用std::forward<Args>(args)...构造的T类型对象。
要求:TargsX中应EmplaceConstructible。对于vectorTX中也应MoveInsertable
返回:a.back()
dequelistvector
a.push_front(t)void在前端加入t的一份副本。
要求:TX中应CopyInsertable
dequeforward_listlist
a.push_front(rv)void在前端加入rv的一份副本。
要求:T<X中应MoveInsertable
dequeforward_listlist
a.push_back(t)void在后端加入t的一份副本。
要求:TX中应CopyInsertable
basic_stringdequelistvector
a.push_back(rv)void在后端加入rv的一份副本。
要求:TX中应MoveInsertable
basic_stringdequelistvector
a.pop_front()void销毁首元素。
要求:a.empty()应为false
dequeforward_listlist
a.pop_back()void销毁末元素。
要求:a.empty()应为false
basic_stringdequelistvector
a[n]reference;对常量aconst_reference*(a.begin() + n)basic_stringarraydequevector
a.at(n)reference;对常量aconst_reference*(a.begin() + n)basic_stringarraydequevecto
  • 15 成员函数at()提供对容器元素的边界检查访问。若n >= a.size()at()抛出out_of_range

26.2.4 节点句柄

26.2.4.1 node_handle概览
  • 1 一个节点句柄是一个接受来自关联容器(26.2.6)或无序关联容器(26.2.7)的单个元素的所有权的对象。它可以用来将所有权转移到具有兼容节点的另一个容器中。具有兼容节点的容器具有相同的节点句柄类型。元素可以在表89的同一行中的容器类型之间在任一方向上传送。
  • 2 如果节点句柄不是空的,那么它包含一个分配器,该分配器等于提取元素时容器的分配器。如果节点句柄为空,则不包含分配器。

表89——具有兼容节点的容器

map<K, T, C1, A>map<K, T, C2, A>
map<K, T, C1, A>multimap<K, T, C2, A>
set<K, C1, A>set<K, C2, A>
set<K, C1, A>multiset<K, C2, A>
unordered_map<K, T, H1, E1, A>unordered_map<K, T, H2, E2, A>
unordered_map<K, T, H1, E1, A>unordered_multimap<K, T, H2, E2, A>
unordered_set<K, H1, E1, A>unordered_set<K, H2, E2, A>
unordered_set<K, H1, E1, A>unordered_multiset<K, H2, E2, A>
  • 3 node_handle句柄仅用于说明。允许实现提供等效的功能,而不提供具有此名称的类。
  • 4 如果一个pair的用户定义特化存在于pair<const Key, T>pair<Key, T>,其中Key是容器的key_typeT是容器的mapped_type,则涉及节点句柄的操作行为是未定义的。
template<未指定>
    class node_handle {
    public:
        // 这些类型声明在表90和表91中描述。
        using value_type = 见下; // 映射容器不存在
        using key_type = 见下; // 集合容器不存在
        using mapped_type = 见下; // 集合容器不存在
        using allocator_type = 见下;

    private:
        using container_node_type = 未指定;
        using ator_traits = allocator_traits<allocator_type>;

        typename ator_traits::rebind_traits<container_node_type>::pointer ptr_;
        optional<allocator_type> alloc_;

    public:
        constexpr node_handle() noexcept : ptr_(), alloc_() {}
        ~node_handle();
        node_handle(node_handle&&) noexcept;
        node_handle& operator=(node_handle&&);

        value_type& value() const; // 映射容器不存在
        key_type& key() const; // 集合容器不存在
        mapped_type& mapped() const; // 集合容器不存在

        allocator_type get_allocator() const;
        explicit operator bool() const noexcept;
        bool empty() const noexcept;

        void swap(node_handle&)
            noexcept(ator_traits::propagate_on_container_swap::value ||
            ator_traits::is_always_equal::value);

        friend void swap(node_handle& x, node_handle& y) noexcept(noexcept(x.swap(y))) {
            x.swap(y);
        }
    };
26.2.4.2 node_handle构造函数、复制和赋值
node_handle(node_handle&& nh) noexcept;
  • 1 效果:构造一个使用nh.ptr_初始化ptr_node_handle。使用nh.alloc_移动构造alloc_。赋值nh.ptr_nullptr,赋值nh.alloc_nullopt
node_handle& operator=(node_handle&& nh);
  • 2 要求:!alloc_ator_traits::propagate_on_container_move_assignmenttrue,或alloc_ == nh.alloc_
  • 3 效果:
    • (3.1) ——如果ptr_ != nullptr,通过调用ator_traits::destroy销毁ptr_指向的container_node_type对象中的value_type子对象,然后通过调用ator_traits::rebind_traits<container_node_type>::deallocate释放ptr_
    • (3.2) ——赋值ptr_nh.ptr_。如果!alloc_ator_traits::propagate_on_container_move_assignmenttrue,移动赋值alloc_nh.alloc_
    • (3.3) ——赋值nh.ptr_nullptr,赋值nh.alloc_nullopt
  • 4 返回:*this
  • 5 抛出:无。
26.2.4.3 node_handle析构函数
~node_handle();
  • 1 效果:如果ptr_ != nullptr,通过调用ator_traits::destroy销毁ptr_指向的container_node_type对象中的value_type子对象,然后通过调用ator_traits::rebind_traits<container_node_type>::deallocate释放ptr_
26.2.4.4 node_handle观察器
value_type& value() const;
  • 1 要求:empty() == false
  • 2 返回:ptr_指向的container_node_type对象中的value_type子对象的一个引用。
  • 3 抛出:无。
key_type& key() const;
  • 4 要求:empty() == false
  • 5 返回:ptr_指向的container_node_type对象中的value_type子对象的key_type成员的一个非常量引用。
  • 6 抛出:无。
  • 7 备注:允许通过返回的引用修改键。
mapped_type& mapped() const;
  • 8 要求:empty() == false
  • 9 返回:ptr_指向的container_node_type对象中的value_type子对象的mapped_type成员的一个引用。
  • 10 抛出:无。
allocator_type get_allocator() const;
  • 11 要求:empty() == false
  • 12 返回:*alloc_
  • 13 抛出:无。
explicit operator bool() const noexcept;
  • 14 返回:ptr_ != nullptr
bool empty() const noexcept;
  • 15 返回:ptr_ == nullptr
26.2.4.5 node_handle修改器
void swap(node_handle& nh)
  noexcept(ator_traits::propagate_on_container_swap::value ||
           ator_traits::is_always_equal::value);
  • 1 要求:!alloc_,或!nh.alloc_,或ator_traits::propagate_on_container_swaptrue,或alloc_ == nh.alloc_
  • 2 效果:调用swap(ptr_, nh.ptr_)。如果!alloc_,或!nh.alloc_,或ator_traits::propagate_on_container_swaptrue,调用swap(alloc_, nh.alloc_)

26.2.5 插入返回类型

  • 1 具有唯一键的关联容器和具有唯一键的无序容器具有一个成员函数insert,它返回嵌套类型insert_return_type。此返回类型是本节中指定的类型的特化。
template <class Iterator, class NodeType>
struct INSERT_RETURN_TYPE
{
    Iterator position;
    bool inserted;
    NodeType node;
};
  • 2 名称INSERT_RETURN_TYPE仅用作说明。INSERT_RETURN_TYPE具有上面指定的模板参数、数据成员和特殊成员。它没有指定的基类或成员。

26.2.6 关联容器

  • 1 关联容器提供基于键的数据快速检索。库中提供了四种基本的关联容器:setmultisetmapmultimap
  • 2 每个关联容器在Key和在Key的元素上引起一个严格弱序(28.7)的排序关系Compare上被参数化。此外,mapmultimapKey关联了一个任意映射类型TCompare类型的对象称为容器的比较对象
  • 3 “等价键”一词指的是比较所带来的等价关系,是键上的operator==。也就是说,k1k2两个键,如果对于比较对象compcomp(k1, k2) == false && comp(k2, k1) == false,则考虑为等价的。对于同一容器中的k1k2两个键,调用comp(k1, k2)总应返回相同的值。
  • 4 如果每个键最多包含一个元素,关联容器支持唯一键。否则,它支持等价键setmap类支持唯一键;multisetmultimap类支持等价键。对于multisetmultimapinsertemplaceerase保持等价元素的相对顺序。
  • 5 对于setmultiset,值类型与键类型相同。对于mapmultimap,它等于pair<const Key, T>
  • 6 一个关联容器的iterator属于双向迭代器类别。对于值类型与键类型相同的关联容器,iteratorconst_iterator都是常量迭代器。iteratorconst_iterator是否是相同类型是不明确的。【注:在这种情况下,iteratorconst_iterator具有相同的语义,且迭代器可转换为常量迭代器。用户可以通过在函数参数列表中总使用const_iterator来避免违反一个定义原则。——注结束】
  • 7 关联容器满足分配器可感容器(26.2.1)的所有要求,除了对于mapmultimap在表83中对于value_type的要求以key_typevalue_type替代。【注:例如,在某些情况中key_typemapped_type要求CopyAssignable,即使相关的value_typepair<const key_type, mapped_type>并非CopyAssignable。——注结束】
  • 8 在表90中,X表示一个关联容器类;a表示一个X类型的值;a2表示具有与X类型兼容的节点的类型的值(表89);b表示一个X类型的可能为const的值;u表示正被声明的变量的名称;当X支持唯一键时,a_uniq表示一个X类型的值;当X支持多重键时,a_eq表示一个X类型的值;当限定标识符X::key_compare::is_transparent有效且表示一个类型(17.8.2)时,a_tran表示一个X类型的可能为const的值;ij满足输入迭代器要求且指向可隐式转换为value_type的元素;[i, j)表示一个合法区间;p表示a的一个合法常量迭代器;q表示a的一个可解引用的合法常量迭代器;r表示a的一个可解引用的合法迭代器;[q1, q2)表示a中常量迭代器的一个合法区间;il指定一个initializer_list<value_type>类型的对象;t表示类型X::value_type的一个值;k表示类型X::key_type的一个值,c表示X::key_compare类型的一个可能为const的值;kl是一个关于c(r, kl)使a被划分(28.7)的值,其中re的键值且ea中;ku是一个关于!c(ku, r)使a被划分(28.7)的值;ke是一个关于c(r, ke)!c(ke, r)使a被划分(28.7)的值,其中c(r, ke)暗指!c(ke, r)。如果有,A表示X使用的存储分配器,否则为allocator<X::value_type>m表示一个可转换为A的类型的分配器;nh表示X::node_type类型的一个非常量右值。

表90——关联容器要求(额外容器)

表达式返回类型断言/说明
前置/后置条件
复杂度
X::key_typeKey编译时
X::mapped_type(只有mapmultimapT编译时
X::value_type(只有setmultisetKey要求:value_typeX中是Erasable编译时
X::value_type(只有mapmultimappair<const Key, T>要求:value_typeX中是Erasable编译时
X::key_compareCompare要求:key_compareCopyConstructible编译时
X::value_compare二元谓词类型对于setmultiset,与key_compare相同;对于mapmultimap,是由一对中第一分量(即Key)引起的顺序关系编译时
X::node_typenode_handle类模板的特化,使得公共嵌套类型与X中的对应类型相同。26.2.4编译时
X(c)
X u(c);
效果:构造一个空容器。使用c的副本作为比较对象。常量
X()
X u;
要求:key_compareDefaultConstructible
效果:构造一个空容器。使用Compare()作为比较对象。
常量
X(i,j,c)
X u(i,j,c);
要求:value_type*iX中是EmplaceConstructible
效果:构造一个空容器,向其中插入范围[i, j)中的元素;使用c作为比较对象。
通常为N log N,其中N有值distance(i, j);若[i, j)被用value_comp()排序则为线性
X(i,j)
X u(i,j);
要求:key_compareDefaultConstructiblevalue_type*iX中是EmplaceConstructible
效果:同上,但使用Compare()作为比较对象
同上
X(il)X(il.begin(), il.end())相同X(il.begin(), il.end())相同
X(il,c)X(il.begin(), il.end(), c)相同X(il.begin(), il.end(), c)相同
a = ilX&要求:value_typeX中是CopyInsertableCopyAssignable
效果:赋值区间[il.begin(), il.end())a中。a的所有存在元素被赋值或销毁。
通常为N log N,其中N有值il.size() + a.size();若[il.begin(), il.end())被用value_comp()排序则为线性
b.key_comp()X::key_compare返回构造b的比较对象。常量
b.value_comp()X::value_compare返回由比较对象构造的value_compare对象常量
a_uniq.emplace(args)pair<iterator, bool>要求:value_typeargsa中应是EmplaceConstructible
效果:插入由std::forward<Args>(args)...构造的value_type对象t,如果且仅如果容器中没有元素有和t键等价的键。返回的双对的bool成员是true,如果且仅如果本次插入发生;且双对的iterator成员指向有和t键等价的键的元素。
对数
a_eq.emplace(args)iterator要求:value_typeargsa中应是EmplaceConstructible
效果:插入由std::forward<Args>(args)...构造的value_type对象t,返回指向新插入元素的迭代器。如果一个区间包含和t等价的元素存在于a_eq中,t被插入到那个区间的末尾。
对数

@TODO: 849@

<think>好的,我现在需要为用户准备一份关于C++容器的超级详细笔记。首先,我得回忆一下C++标准中的容器种类,确保覆盖所有内容。标准容器主要分为顺序容器、关联容器、无序关联容器容器适配器这几大类。 顺序容器包括vector、list、deque、array、forward_list和string。我得逐一分析它们的特点、内部实现、常用操作和时间复杂度。比如,vector是动态数组,支持随机访问,尾部插入高效,中间插入可能涉及元素移动。而list是双向链表,插入删除快,但随机访问慢。需要对比它们的优缺点,帮助用户选择合适的情境。 接下来是关联容器,比如set、multiset、map、multimap。这些通常是基于红黑树实现的,所以元素是有序的。需要解释键的唯一性,multiset和multimap允许重复键的情况。操作如插入、删除、查找的时间复杂度都是对数级别,这点很重要。 然后是C++11引入的无序关联容器,unordered_set、unordered_multiset、unordered_map、unordered_multimap。这些基于哈希表实现,元素无序,但查找效率平均情况下是常数时间。需要讨论哈希函数的重要性,冲突处理的方法,比如链地址法,以及负载因子的影响。 容器适配器部分包括stack、queue和priority_queue。它们不是独立的容器,而是基于其他容器实现的接口。比如stack默认用deque实现,也可以指定vector或list。需要说明它们的行为,比如LIFO、FIFO,以及优先队列的堆结构。 接下来要考虑各个容器的常用成员函数,比如size(), empty(), begin(), end(), insert(), erase()等。对于特定容器,如vector的push_back(), capacity(), reserve(), resize();map的operator[]和insert的区别,以及如何正确使用emplace方法提高效率。 内存管理也是一个重点,尤其是vector的动态扩容机制。当元素数量超过容量时,vector会重新分配内存,通常容量翻倍,这可能导致迭代器失效。需要提醒用户注意在迭代过程中修改容器可能引发的错误。 异常安全性方面,不同容器在操作时的异常保证不同。比如vector的push_back在发生异常时保持强异常安全,而某些操作可能只有基本保证。需要指出这些细节,帮助用户编写健壮的代码。 迭代器失效的情况是另一个关键点。例如,在vector中间插入元素会导致后面的迭代器失效;而删除元素时,被删除元素的迭代器及其之后的都会失效。对于list这样的链表结构,插入删除通常不会影响其他元素的迭代器,除非直接删除当前元素。 性能比较方面,需要制作表格或列表,对比不同容器在插入、删除、查找等操作上的时间复杂度,帮助用户根据具体需求选择最合适的容器。例如,如果需要频繁查找,关联容器或无序容器可能更合适;而频繁在头部插入则可能选择list或deque。 C++11及以后版本的新特性也需要涵盖,比如emplace系列函数,移动语义对容器的影响,以及智能指针在容器中的使用。比如使用emplace_back可以直接在容器中构造对象,避免不必要的拷贝。 应用场景举例部分,可以通过实际例子说明如何选择容器。例如,需要维护一个有序且快速查找的数据集合,可以使用set;需要键值对快速查找且不关心顺序,使用unordered_map;需要双端队列操作则选择deque。 最后,总结最佳实践,比如预分配vector的容量以减少重新分配次数,避免在循环中频繁查询容器大小,使用适当的算法(如sort、find)结合容器特性提升效率。同时提醒常见陷阱,如迭代器失效、未初始化的访问等。 需要检查是否有遗漏的容器或新特性,比如C++17引入的std::string_view虽然不属于容器,但可能和string有关联。另外,容器相关的非成员函数,比如swap,erase_if(C++20)等也值得提及。 确保内容准确无误,参考最新的C++标准(如C++20),必要时比较不同版本间的差异。最后组织内容结构,使其逻辑清晰,便于用户理解和查阅。</think>以下为关于C++容器的超级详细笔记,按模块化结构组织,包含数学公式和代码示例: # C++标准容器完全指南 $\boxed{\text{最新标准C++20 核心特性全覆盖}}$ ## 一、容器分类体系 $$ \text{容器} \begin{cases} \text{顺序容器} \\ \text{关联容器} \\ \text{无序关联容器} \\ \text{容器适配器} \end{cases} $$ ### 1.1 顺序容器特性对比 | 容器 | 数据结构 | 访问方式 | 插入效率 | 内存布局 | |---------------|----------------|----------|------------------------|----------| | vector | 动态数组 | 随机访问 | $O(1)$尾部/$O(n)$中间 | 连续 | | deque | 分块数组 | 随机访问 | $O(1)$头尾 | 伪连续 | | list | 双向链表 | 双向迭代 | $O(1)$任意位置 | 非连续 | | forward_list | 单向链表 | 前向迭代 | $O(1)$ | 非连续 | | array | 固定数组 | 随机访问 | 不可插入 | 连续 | | string | 字符专用容器 | 随机访问 | $O(1)$尾部 | 连续 | ### 1.2 关联容器红黑树实现 对于有序集合/映射: $$ \text{查找时间复杂度} = O(\log n) $$ $$\text{平衡因子} \Rightarrow \text{树高度} \leq 2\log_2(n+1)$$ ### 1.3 哈希表实现原理 无序容器负载因子: $$ \lambda = \frac{\text{元素数量}}{\text{桶数量}} $$ 当$\lambda > \text{max_load_factor()}$时自动rehash ## 二、核心容器深度解析 ### 2.1 vector内存管理 扩容策略: $$ \text{新容量} = \max(\text{需求容量}, \text{当前容量} \times \text{增长因子}) $$ 多数实现采用$growth\_factor = 2$ ```cpp vector<int> v; v.reserve(100); // 直接分配100元素空间 while(v.size() < 100) { v.emplace_back(rand()%100); // 无重新分配 } ``` ### 2.2 map的键排序特性 对于自定义类型作为key: ```cpp struct Point { int x, y; bool operator<(const Point& other) const { return std::tie(x, y) < std::tie(other.x, other.y); } }; std::map<Point, std::string> location_map; ``` ### 2.3 unordered_map自定义哈希 ```cpp struct MyHash { size_t operator()(const Point& p) const { return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1); } }; std::unordered_map<Point, std::string, MyHash> custom_hash_map; ``` ## 三、时间复杂度大全表 $$ \begin{array}{c|cccc} \text{操作/容器} & \text{vector} & \text{list} & \text{map} & \text{unordered\_map} \\ \hline \text{随机访问} & O(1) & O(n) & - & - \\ \text{头插入} & O(n) & O(1) & - & - \\ \text{尾插入} & O(1) & O(1) & - & - \\ \text{查找} & O(n) & O(n) & O(\log n) & O(1)^{*} \\ \text{删除} & O(n) & O(1) & O(\log n) & O(1)^{*} \end{array} $$ (* 平均情况) ## 四、迭代器失效规则 重要法则: $$ \text{修改操作} \Rightarrow \begin{cases} \text{vector/deque} & \text{插入导致capacity变化⇒全部失效} \\ \text{list/map} & \text{仅被删除元素迭代器失效} \\ \text{unordered容器} & \text{rehash后全部失效} \end{cases} $$ ## 五、高级技巧与最佳实践 ### 5.1 移动语义优化 ```cpp std::vector<std::string> createStrings() { std::vector<std::string> temp; // ...填充数据 return temp; // NRVO优化或移动构造 } ``` ### 5.2 类型萃取应用 ```cpp template<typename T> void smartReserve(std::vector<T>& v, size_t n) { if constexpr(std::is_nothrow_move_constructible_v<T>) { v.reserve(n); // 安全扩容 } else { // 使用更安全但较慢的扩容策略 } } ``` ### 5.3 视图类型(C++20) $$ \text{string\_view} \subset \text{span} \subset \text{range} $$ ```cpp std::vector<int> data{1,2,3,4,5}; std::span<int> window(data.data()+1, 3); // 无所有权视图 ``` ## 六、性能关键点 ### 6.1 元素访问效率对比 $$ \text{cache命中率} \propto \frac{1}{\text{访问时间}} $$ 实验数据: ``` 连续容器 vs 节点容器 L1 cache访问时间 ≈ 0.5ns RAM访问时间 ≈ 100ns ``` ### 6.2 预分配策略比较 $$\text{vector.resize() vs reserve()}$$ - resize():立即构造对象 - reserve():仅分配内存 ## 七、特殊容器应用 ### 7.1 bitset位操作 $$ \text{存储效率} = \frac{\text{位数}}{sizeof(bitset)*8} \times 100\% $$ ```cpp constexpr size_t N = 1024; std::bitset<N> flags; flags.set(42); // 置位第42位 ``` ### 7.2 多索引容器 使用Boost.MultiIndex或自定义组合: ```cpp std::map<int, std::string> primary; std::unordered_map<std::string, int> secondary; // 保持两个容器同步 void insert(int id, std::string name) { primary.emplace(id, name); secondary.emplace(name, id); } ``` ## 八、容器选择决策树 $$ \text{选择流程} \begin{cases} \text{需要快速查找?} \rightarrow \text{关联/无序容器} \\ \text{需要顺序保持?} \rightarrow \text{顺序容器} \\ \text{高频头尾操作?} \rightarrow \text{deque/list} \\ \text{内存敏感?} \rightarrow \text{array/vector} \end{cases} $$ ## 九、C++20重要更新 1. contains() 方法: ```cpp if (my_map.contains(key)) { ... } ``` 2. 安全类型擦除视图:`std::span` 3. 范围支持:`std::ranges::sort(vec);` ## 十、经典问题解决方案 ### 10.1 高效去重方案 ```cpp // vector去重 std::sort(vec.begin(), vec.end()); auto last = std::unique(vec.begin(), vec.end()); vec.erase(last, vec.end()); // 保持原始顺序(C++20) auto [first, last] = std::ranges::unique(vec); vec.erase(first, last); ``` ### 10.2 对象池模式 ```cpp class ObjectPool { std::vector<std::unique_ptr<Object>> pool; std::stack<size_t> free_list; public: Object* acquire() { if(free_list.empty()) { pool.emplace_back(std::make_unique<Object>()); return pool.back().get(); } size_t idx = free_list.top(); free_list.pop(); return pool[idx].get(); } }; ``` 本笔记持续更新,建议配合编译器实践验证。最佳学习路径:理解原理→分析复杂度→编写测试代码→性能剖析→生产环境应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值