STL 即标准模板库(Standard Template Library),是 C++ 标准库的一部分,里面包含了一些模板化的通用的数据结构和算法。STL 基于模版的实现,因此能够支持自定义的数据结构。
STL 中一共有 6 大组件:
- 容器 (container)
- 迭代器 (iterator)
- 算法 (algorithm)
- 分配器 (allocator)
- 仿函数 (functor)
- 容器适配器 (container adapter)
参考资料:
- [1] https://oi-wiki.org/lang/csl/container/
- [2] https://en.cppreference.com/w/cpp/container
- [3] http://c.biancheng.net/view/409.html
仿函数
仿函数 (Functor) 的本质就是):
int main()
{
vector<int> v = {2, 3, 4, 6, 7, 8, 9, 3, 2, 2, 2, 2, 3, 3, 3, 4, 5, 6};
for (auto i = v.begin(); i != v.end(); i++)
{
if (*i==2 || *i==3) v.erase(i), i--;
// correct code should be
// if (*i==2 || *i==3) i=v.erase(i), i--;
}
for (auto i = v.begin(); i != v.end(); i++)
cout << *i << ' ';
}
我很久之前用 Dev C++ (应该是内置了很古老的 MinGW)写代码的时候,印象中也遇到过这种情况,v.erase(i), i--
这样的操作是有问题的。 erase(i)
会使得 i
及其后面的迭代器失效,从而发生段错误。
但现在 MacOS (clang++ 12), Ubuntu16 (g++ 5.4), Windows (mingw 9.2) 上测试,这段代码都没有问题,并且能输出正确结果。编译选项为:
g++ test.cpp -std=c++11 -O0
实际上也不难理解,因为是连续内存,i
指向的内存位置,在 erase
之后被其他数据覆盖了(这里的行为就跟我们使用普通数组一样),但该位置仍然在 vector
的有效范围之内。在上述代码中,当 i = v.begin()
时,执行 erase
后,对 i
进行自减操作,这已经是一种未定义行为。
我猜应该是 C++11 后(或者是后来的编译器更新),对迭代器失效的这个问题进行了优化。
虽然能够正常运行,但我认为最好还是严谨一些,更严格地遵循迭代器的使用规则:if (*i==2 || *i==3) i=v.erase(i), i--;
.
以下为各类容器可能会发生迭代器失效的情况:
- 数组型 (
vector, deque
)insert(i)
和erase(i)
会发生数据挪动,使得i
后的迭代器失效,建议使用i = erase(i)
获取下一个有效迭代器。- 内存重新分配:当
vector
自动扩容时,可能会申请一块新的内存并拷贝原数据(也有可能是在当前内存的基础上,再扩充一段连续内存),因此所有的迭代器都将失效。
- 链表型 (
list, forward_list
):insert(i)
和erase(i)
操作不影响其他位置的迭代器,erase(i)
使得迭代器i
失效,指向数据无效,i = erase(i)
可获得下一个有效迭代器,或者使用erase(i++)
也可(在进入erase
操作前已完成自增)。 - 树型 (
set/map
):与链表型相同。 - 哈希型 (
unodered_{set_map}
):与链表型相同。