C++ STL 使用注意事项整理
Intro
简单整理了一些关于 C++ STL 的注意点。欢迎各路神仙在评论区提出意见。
虽然大多数东西可以手写不过某些东西最好少造轮子,善用 STL 可以节约很多考场时间,简化实现。
当然是时空限制和功能足够的前提下。
Tips
- 容易忽略的 C++11/17 可用但 C++98/14 不可用的特性:
- 关联式容器(
std::set/map/unordered_set/unordered_map
)的erase(it)
在 C++11 中提供了返回值,返回的是迭代器指向删除位置的下一个迭代器。 std::priority_queue
中的swap
方法在 C++11 中是 \(O(1)\) 的,但 C++98 只能用常规的std::swap(q1, q1)
的方式交换,而这样的复杂度是与优先队列的大小相关的。- 使用初始化列表(大括号,
{...}
)初始化容器的方式一般只在 C++11 中被支持。 - 所有
emplace
系的函数一般只在 C++11 中被支持。 std::vector/deque/string
中的shrink_to_fit
函数只在 C++11 中被支持。std::string
中的pop_back/front/back
只在 C++11 中被支持。- 所有容器的
cbegin/cend/crbegin/crend
都只在 C++11 中被支持。 - UPD:至 CSP2021 起,编译标准为 C++14。
- 形如
auto [x, y] = p
的方法是 C++17 中支持的(可能直接编译可以通过只有 warning 但不推荐)。
- 关联式容器(
- 开
bool
数组是如果发现空间不是很对可以考虑std::bitset
或std::vector<bool>
。不过仅仅是单次访问std::bitset
没有bool
数组快。 - 常数较大的一些 STL 容器:
std::stack/std::queue/std::deque/std::list/std::valarray/std::string...
,这些东西能手写尽量手写。 std::multiset
的count()
函数的复杂度为 \(O(\log n + ans)\),因此多多考虑用std::map
实现std::multiset
的功能。- 直接对
std::set/std::multiset/std::map/std::multimap
进行<algorithm>
库中的二分查找的复杂度为 \(O(n)\),正解应该用其自带的lower_bound()/upper_bound()
函数,复杂度为 \(O(\log n)\)。 std::priority_queue
默认为大根堆,小根堆可以取负、重载运算符或std::greater
。std::multimap
不支持[]
下标访问。- 在使用
std::vector
时可以尝试用reserve()
预测数组的大小以提高效率。实测push_back()
次数较多时reserve()
后能快一倍。 - 许多 STL 容器都特化了
swap()
函数,可以实现 \(O(1)\) 交换。 std::bitset
重载了流运算符,即可以用cin/cout
输入输出。当然也可以用这个输出整数的二进制。std::nth_element
会改变元素在容器中的位置。- 对
std::vector
等序列容器进行下标访问是最好将size()
函数的返回值转为int
。一个成功避免大问题的一个例子:for (int i = (int)v.size() - 2; i >= 0; i--) v[i] += v[i + 1]
。 - 使用
std::map<Key, Value>
的[]
下标访问方法是,如果无对应Key
元素的话会新建一个,值为调用构造函数Value()
的结果。 - STL 容器进行修改操作时需要注意迭代器被非法化的情况,被非法化的迭代器不能再使用。如使用
std::set
的erase(it)
后it
就被非法化了。std::vector
等也需要注意这种问题。各种容器增删操作非法化情况表。 - 判断容器是否为空尽量使用
empty()
而不是size() == 0
,因为某些容器的size()
函数的复杂度并不是 \(O(1)\)(如一些list
实现)。 - 使用排序 STL 时,比较函数必须满足 严格弱序,否则可能出现玄学错误。
- 对迭代器
it
做+ a - b
的操作尽量写成it + (a - b)
而不是it + a - b
,因为it + a
可能出现越界情况。 __gnu_cxx::rope
中的erase(size_t __p)
是错(编写者锅了)的!它的注释中写的是 “Erase, single character”,但实际上写的是erase(__p, __p + 1)
,那么你实际上会把__p
开始的__p + 1
个位置全部删除。- 一个小 trick:代码里出现一大坨
begin/end
是不是很烦?你可以#define all(x) (x).begin(), (x).end()
。要注意(x)
会算两边,如果是临时算出来的不应该这么做。
本文来自博客园,作者:-Wallace-,转载请注明原文链接:https://www.cnblogs.com/-Wallace-/p/cpp-stl.html