STL容器概述
STL容器
1、容器概述
1.1、容器分类
1.1.1、顺序容器:提供对元素序列的访问,顺序容器为元素连续分配内存或将元素组织为链表,元素的类型是容器成员value_type。
顺序容器 | 说明 |
---|---|
vector<T, A> | 空间连续分配的T类型元素序列;默认选择容器 |
list<T, A> | T类型元素双向链表;当需要插入/删除元素但不移动已有元素是选择它。 |
forward_list<T, A> | T类型元素单向链表;很短的或空序列的理想选择 |
deque<T, A> | T类型元素双向队列;向量和链表的混合;对于大多数应用,都比向量和链表其中之一要慢。 |
模板参数A是一个分配器,容器用它来分配和释放内存, 默认值 std::allocator
1.1.2、有序关联容器:提供基于关键字的关联查询。
有序关联容器 | 说明 |
---|---|
map<K, V, C, A> | 从K到V的有序映射;一个(K, V)对序列 |
multimap<K, V, C, A> | 从K到V的有序映射;允许重复关键字 |
set<K, C, A> | K的有序集合 |
multiset<K, C, A> | K的有序集合;允许重复关键字 |
C是比较类型,默认值 std::less(K); A是分配器类型,默认值 std::allocator<std::pair<const K, T>>。这些容器通常采用平衡二叉树(通常红黑树)实现。
1.1.3、无序关联容器:
有序关联容器 | 说明 |
---|---|
unordered_map<K, V, H, E, A> | 从K到V的无序映射; |
unordered_multimap<K, V, H, E, A> | 从K到V的无序映射;允许重复关键字 |
unordered_set<K, H, E, A> | K的无序集合 |
unordered_multiset<K, H, E, A> | K的无序集合;允许重复关键字 |
H是关键字类型K的哈希函数类型,默认值 std::hash
关联容器都是链接结构(树),节点类型为其成员value_type。
1.1.4、容器适配器:提供对底层容器的特殊访问。
容器适配器 | 说明 |
---|---|
priority_queue<T, C, Cmp> | T的优先队列;Cmp是优先级函数类型 |
queue<T, C> | T的队列,支持push() 和 pop()操作 |
stack<T, C> | T的栈,支持push() 和 pop()操作 |
一个priority_queue的默认优先级函数Cmp为std::less
1.2、容器对元素的要求
1.2.1、比较操作
注意 C 风格字符串(即const char*)上的 < 比较的是指针值,因此如果用 C 风格字符串作为关键字,关联容器将不能正常工作,为了让这样的关联容器正常工作,就必须使用基于字典序的比较操作。
struct Cstring_less
{
bool opreator()(const char *p, const char *q) const
{
return strcmp(p, q) < 0;
}
};
map<char*, int, Cstring_less> m; //map使用strcmp()比较const
2、操作
2.1、标准库容器操作复杂性
标准容器操作复杂性 | |||||
[] | 列表 | 头部 | 尾部 | 迭代器 | |
vector | 常量 | O(n)+ | 常量+ | 随机 | |
list | 常量 | 常量 | 常量 | 双向 | |
forward_list | 常量 | 常量 | 前向 | ||
deque | 常量 | O(n) | 常量 | 常量 | 随机 |
stack | 常量 | ||||
queue | 常量 | 常量 | |||
priority_queue | O(log(n)) | O(log(n)) | |||
map | O(log(n)) | O(log(n))+ | 双向 | ||
multimap | O(log(n))+ | 双向 | |||
set | O(log(n))+ | 双向 | |||
multimap | O(long(n))+ | 双向 | |||
unordered_map | 常量+ | 常量+ | 前向 | ||
unordered_multimap | 常量+ | 前向 | |||
unordered_set | 常量+ | 前向 | |||
unordered_multiset | 常量+ | 前向 | |||
string | 常量 | O(n)+ | O(n)+ | 常量+ | 随机 |
array | 常量 | 随机 | |||
内置数组 | 常量 | 随机 | |||
valarray | 常量 | 随机 | |||
bitset | 常量 | 随机 |
头部操作:表示在第一个元素之前的插入和删除操作。
尾部操作:表示在最后一个元素之后的插入和删除操作。
列表操作:表示在任意位置的插入和删除操作。
迭代器:“随机”表示随机访问迭代器,“向前”表示前向迭代器,“双向”表示双向迭代器。
时间复杂度:
- 常量又表示为O(1),表示时间复杂度是个常量,通常代表时间复杂度低。
- O(n)表示操作花费非时间与元素数目成正比,元素增加n倍,操作时间也增加n倍。通常代表时间复杂度较高。
- O(n*n)表示元素增加n倍,操作时间就增加n^2倍。通常代表时间复杂度很高,n很大时,对资源消耗来说是灾难。
- O(logn)表示元素增加n倍,操作时间增加logn倍(以2为底的对数)。通常代表时间复杂度较低。
- O(nlogn)表示元素增加n倍,操作时间增加nlogn倍。时间复杂度高于O(n)低于O(n*n)。
时间复杂度后加 “+” 表示时间复杂度可能会更高,如vector增加元素时,可能需要重新分配资源。
值得注意的是,操作效率还取决于计算机的内存和处理器构架的细节,比如通过链接获取下一个元素(list情形)的代价会远高于在一个vector中获取下一个元素的代价(元素是连续存储)。因此对操作效率的评估不能完全简单的依赖于对复杂性的评估,而是要进行实际的测试。
2.2、容器赋值、移动
-
赋值操作并不拷贝或移动分配器,目标容器获得一组新的元素,但会保留其旧的分配器,新元素的空间也是用此分配器分配的。
-
一个构造函数或是一次元素拷贝可能会抛出异常,来指出它无法完成这个任务。
-
初始化器的潜在二义性。
void use() { vector<int> vi{1, 3, 5, 7, 9}; //使用5个整数初始化vector vector<string> vs(7); //vector初始化为7个空字符串,初始化大小 vector<int> vi2; vi2 = {2, 4, 6, 8}; //将4个整数赋予vi2 vi2.assign(&vi[1], &vi[4]); //将序列3,5,7赋予vi2 vector<string> vs2; vs2 = {"hello", "world"}; //赋予vs2两个字符串 vs2.assign("First", "Second"); //运行时错误 }
以上代码中,向vs2赋值时发生的错误时传递了一对指针(而不是一个initalizer_list),而两个指针并非指向相同的数组。记住,对大小初始化器使用(), 而对其它所有初始化器都使用{}。
-
容器通常都很大,因此几乎总是以引用的方式传递容器实参。但是,由于容器都是资源句柄,因此可以高效的以返回值的方式返回容器(隐含使用移动操作)。类似的不想使用别名时,可以用移动的方式传递容器实参。例如:
void task(vector<int>&& v); //以返回值的方式返回容器,隐式使用移动操作 vector<int> user(vector<int> &large) { vector<int> res; //... task(move(large)); //将数据所有权传递给task(),用移动的方式传递容器实参。 //... return res; }
2.3、容器的大小和容量
大小:指容器中的元素数目。
容量:指重新分配更多内存之前容器能够保存的元素数目。
大小和容量 | |
x = c.size() | x是c的元素数目 |
c.empty() | c为空吗? |
x = c.max_size() | x是c的最大可能元素数目 |
x = c.capacity() | x是为c分配的空间大小;只适用于vector和string |
c.reserve(n) | 为c预留n个元素的空间;只适用于vector和string |
c.resize(n) | 将c的大小改变为n;将增加的元素初始化为默认元素值;只适用于顺序容器和string |
c.resize(n, v) | 将c的大小改变为n;将增加的元素初始化为v;只适用于顺序容器和string |
c.shrink_to_fit() | 令c.capacity()等于c.size();只适用于vector、deque、string |
c.clear() | 删除c的所有元素 |
注意,在改变大小或容量时,元素可能会被移动到新的存储位置。这意味着指向元素的迭代器(以及指针和引用)可能会失效(即指向旧元素的位置)。
指向关联容器(如map)元素的迭代器只有当所指元素从容器中删除(erase())时才会失效。与之相反,指向顺序容器(如vector)元素的迭代器当元素重新分配空间(如resize()、reverse()、push_back())或所指元素在容器中移动(如在前一个位置发生了erase()或insert())时也会失效。
2.4、迭代器
容器可以看做按容器迭代器定义的顺序或相反的顺序排列的元素序列。对于一个关联容器,元素的顺序由容器比较标准决定。
迭代器 | |
p = c.begin() | p指向c的首元素 |
p = c.end() | p指向c的尾后元素 |
p = c.cbegin() | p指向c的首元素,常量迭代器 |
p = c.cend() | p指向c的尾后元素,常量迭代器 |
p = c.rbegin() | p指向c的反序的首元素 |
p = c.rend() | p指向c的反序的尾后元素 |
p = c.crbegin() | p指向c的反序的首元素,常量迭代器 |
p = c.crend() | p指向c的反序的尾后元素,常量迭代器 |
更多STL容器细节参考:cplusplus