STL 即标准模板库(Standard Template Library),是 C++ 标准库的一部分,里面包含了一些模板化的通用的数据结构和算法。STL 基于模版的实现,因此能够支持自定义的数据结构。

STL 中一共有 6 大组件:

  • 容器 (container)
  • 迭代器 (iterator)
  • 算法 (algorithm)
  • 分配器 (allocator)
  • 仿函数 (functor)
  • 容器适配器 (container adapter)

参考资料:

仿函数

仿函数 (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}):与链表型相同。
01-24 04:56