C++ STL 之迭代器体系详解

C++ STL 之迭代器体系详解

迭代器是 STL 算法和容器之间的胶水层------六大类别通过 tag dispatching 在编译期决定算法实现路径;失效规则决定你哪里能安全保存迭代器;适配器把赋值、流、移动变成统一的迭代器接口。


一、用法速查

1.1 插入迭代器

把赋值操作变成插入操作,算法(copy / transform)向容器填充数据的桥梁。

适配器 效果 底层调用
back_inserter(c) 尾部追加 c.push_back(value)
front_inserter(c) 头部插入 c.push_front(value)
inserter(c, it) it 之前插入 c.insert(it, value)
cpp 复制代码
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <algorithm>
#include <iterator>
using namespace std;

int main() {
    vector<int> src{1, 2, 3}, dst;
    copy(src.begin(), src.end(), back_inserter(dst));
    for (int x : dst) cout << x << " ";     // 1 2 3
    cout << "\n";

    list<int> lst;
    copy(src.begin(), src.end(), front_inserter(lst));
    for (int x : lst) cout << x << " ";     // 3 2 1(逆序)
    cout << "\n";

    vector<int> v{1, 2, 5, 6};
    auto it = find(v.begin(), v.end(), 5);
    copy(src.begin(), src.end(), inserter(v, it));
    for (int x : v) cout << x << " ";       // 1 2 1 2 3 5 6
    cout << "\n";
    return 0;
}

inserter 对 map 的特殊意义map::insert(it, value) 是 hint 插入------hint 恰好正确时复杂度从 O(log n) 降到 O(1) amortized。有序序列上连续使用,总复杂度从 O(n log n) 降到 O(n):

cpp 复制代码
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    vector<pair<int, string>> data{{3, "c"}, {1, "a"}, {2, "b"}, {4, "d"}};
    sort(data.begin(), data.end());

    map<int, string> mp;
    auto hint = mp.end();
    for (auto &p : data)
        hint = mp.insert(hint, p);          // O(1) amortized

    for (auto &[k, v] : mp) cout << k << ":" << v << " ";
    cout << "\n";                           // 1:a 2:b 3:c 4:d
}

1.2 流迭代器

把流包装成迭代器,算法可以直接对接控制台或文件。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <sstream>
using namespace std;

int main() {
    string data = "10 20 30 40 50";
    istringstream iss(data);
    vector<int> v{istream_iterator<int>(iss), istream_iterator<int>()};
    for (int x : v) cout << x << " ";       // 10 20 30 40 50
    cout << "\n";

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, ", "));
    cout << "\n";                           // 10, 20, 30, 40, 50,
    return 0;
}

EOF 哨兵机制istream_iterator<T>() 默认构造的迭代器是流结束哨兵。读取到 EOF 或格式错误时,自动置为与哨兵相等的状态。vector 范围构造函数据此自动判停。

1.3 反向迭代器

rbegin() 指向末元素,rend() 指向首元素之前。++ 意味着向头部移动。

cpp 复制代码
#include <iostream>
#include <vector>
#include <iterator>
using namespace std;

int main() {
    vector<int> v{1, 2, 3, 4, 5};

    for (auto it = v.rbegin(); it != v.rend(); ++it)
        cout << *it << " ";                 // 5 4 3 2 1
    cout << "\n";

    auto ri = v.rbegin();                   // ri 指向 5
    auto fi = ri.base();                    // fi = v.end(),越界,勿解引用
    auto fi2 = prev(ri.base());             // 指向 5
    cout << *fi2 << "\n";                   // 5
    return 0;
}

base() 的 +1 偏移是高频考点:

复制代码
正向:   begin() ->   1   2   3   4   5   -> end()
                      ^               ^
                     rend()         rbegin()

rbegin() == reverse_iterator(end())
rend()   == reverse_iterator(begin())

1.4 移动迭代器

C++11 引入 make_move_iterator,让算法在遍历时把元素移动而非拷贝。核心场景:构造 move-only 类型的容器,或批量移动大对象避免拷贝开销。

cpp 复制代码
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <memory>
using namespace std;

int main() {
    vector<unique_ptr<int>> src;
    src.push_back(make_unique<int>(1));
    src.push_back(make_unique<int>(2));
    src.push_back(make_unique<int>(3));

    vector<unique_ptr<int>> dst{
        make_move_iterator(src.begin()),
        make_move_iterator(src.end())
    };

    cout << "src size: " << src.size() << "\n";    // 3(元素已空)
    cout << "dst[0]: " << *dst[0] << "\n";         // 1

    list<string> words{"hello", "world", "move"};
    vector<string> moved{
        make_move_iterator(words.begin()),
        make_move_iterator(words.end())
    };
    return 0;
}

二、底层原理

2.1 六种迭代器标签

C++ 标准定义六种迭代器类别,构成严格的继承层次。编译器据此在编译期为算法选择最优实现。
#mermaid-svg-nMRlVxDU1GbmVF4S{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nMRlVxDU1GbmVF4S .error-icon{fill:#552222;}#mermaid-svg-nMRlVxDU1GbmVF4S .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nMRlVxDU1GbmVF4S .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nMRlVxDU1GbmVF4S .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nMRlVxDU1GbmVF4S .marker.cross{stroke:#333333;}#mermaid-svg-nMRlVxDU1GbmVF4S svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nMRlVxDU1GbmVF4S p{margin:0;}#mermaid-svg-nMRlVxDU1GbmVF4S .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nMRlVxDU1GbmVF4S .cluster-label text{fill:#333;}#mermaid-svg-nMRlVxDU1GbmVF4S .cluster-label span{color:#333;}#mermaid-svg-nMRlVxDU1GbmVF4S .cluster-label span p{background-color:transparent;}#mermaid-svg-nMRlVxDU1GbmVF4S .label text,#mermaid-svg-nMRlVxDU1GbmVF4S span{fill:#333;color:#333;}#mermaid-svg-nMRlVxDU1GbmVF4S .node rect,#mermaid-svg-nMRlVxDU1GbmVF4S .node circle,#mermaid-svg-nMRlVxDU1GbmVF4S .node ellipse,#mermaid-svg-nMRlVxDU1GbmVF4S .node polygon,#mermaid-svg-nMRlVxDU1GbmVF4S .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nMRlVxDU1GbmVF4S .rough-node .label text,#mermaid-svg-nMRlVxDU1GbmVF4S .node .label text,#mermaid-svg-nMRlVxDU1GbmVF4S .image-shape .label,#mermaid-svg-nMRlVxDU1GbmVF4S .icon-shape .label{text-anchor:middle;}#mermaid-svg-nMRlVxDU1GbmVF4S .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nMRlVxDU1GbmVF4S .rough-node .label,#mermaid-svg-nMRlVxDU1GbmVF4S .node .label,#mermaid-svg-nMRlVxDU1GbmVF4S .image-shape .label,#mermaid-svg-nMRlVxDU1GbmVF4S .icon-shape .label{text-align:center;}#mermaid-svg-nMRlVxDU1GbmVF4S .node.clickable{cursor:pointer;}#mermaid-svg-nMRlVxDU1GbmVF4S .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nMRlVxDU1GbmVF4S .arrowheadPath{fill:#333333;}#mermaid-svg-nMRlVxDU1GbmVF4S .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nMRlVxDU1GbmVF4S .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nMRlVxDU1GbmVF4S .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nMRlVxDU1GbmVF4S .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nMRlVxDU1GbmVF4S .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nMRlVxDU1GbmVF4S .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nMRlVxDU1GbmVF4S .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nMRlVxDU1GbmVF4S .cluster text{fill:#333;}#mermaid-svg-nMRlVxDU1GbmVF4S .cluster span{color:#333;}#mermaid-svg-nMRlVxDU1GbmVF4S div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nMRlVxDU1GbmVF4S .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nMRlVxDU1GbmVF4S rect.text{fill:none;stroke-width:0;}#mermaid-svg-nMRlVxDU1GbmVF4S .icon-shape,#mermaid-svg-nMRlVxDU1GbmVF4S .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nMRlVxDU1GbmVF4S .icon-shape p,#mermaid-svg-nMRlVxDU1GbmVF4S .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nMRlVxDU1GbmVF4S .icon-shape .label rect,#mermaid-svg-nMRlVxDU1GbmVF4S .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nMRlVxDU1GbmVF4S .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nMRlVxDU1GbmVF4S .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nMRlVxDU1GbmVF4S :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入迭代器

input_iterator_tag
前向迭代器

forward_iterator_tag
输出迭代器

output_iterator_tag
双向迭代器

bidirectional_iterator_tag
随机访问迭代器

random_access_iterator_tag
连续迭代器

contiguous_iterator_tag

C++17

类别 能力 代表类型
输入迭代器 单向读,单次 pass istream_iterator
输出迭代器 单向写,单次 pass ostream_iterator
前向迭代器 单向读/写,multi-pass forward_list, unordered_*
双向迭代器 双向 ++ / -- list, map, set
随机访问迭代器 += / -= / [] vector, deque, array
连续迭代器 (C++17) 连续内存,可转 T* vector, string, array

2.2 Tag Dispatching(标签分派)

STL 算法通过迭代器标签在编译期 选择不同实现。以 std::advance(it, n) 为例------n 运行时传入,仍需选出最优路径:

cpp 复制代码
namespace detail {

template<class InputIt, class Distance>
void advance_impl(InputIt& it, Distance n, input_iterator_tag) {
    while (n > 0) { --n; ++it; }           // O(n)
}

template<class BidirIt, class Distance>
void advance_impl(BidirIt& it, Distance n, bidirectional_iterator_tag) {
    if (n > 0) while (n--) ++it;
    else       while (n++) --it;           // O(|n|)
}

template<class RandomIt, class Distance>
void advance_impl(RandomIt& it, Distance n, random_access_iterator_tag) {
    it += n;                               // O(1)
}

}

template<class It, class Distance>
void advance(It& it, Distance n) {
    detail::advance_impl(it, n,
        typename iterator_traits<It>::iterator_category());
}

编译器通过 iterator_traits<It>::iterator_category() 返回的标签(空 struct),在重载决议中匹配对应版本。继承关系保证了 random_access_iterator_tag 不会误匹配到 bidirectional 版本------这就是"标签分派"的机制核心。

std::distance(first, last) 同理:

  • RandomAccessIterator:return last - first; ------ O(1)
  • InputIterator:循环计数 ------ O(n)

2.3 迭代器失效完整表

#mermaid-svg-U0BKGSUz2a74W5O6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-U0BKGSUz2a74W5O6 .error-icon{fill:#552222;}#mermaid-svg-U0BKGSUz2a74W5O6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-U0BKGSUz2a74W5O6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-U0BKGSUz2a74W5O6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-U0BKGSUz2a74W5O6 .marker.cross{stroke:#333333;}#mermaid-svg-U0BKGSUz2a74W5O6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-U0BKGSUz2a74W5O6 p{margin:0;}#mermaid-svg-U0BKGSUz2a74W5O6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-U0BKGSUz2a74W5O6 .cluster-label text{fill:#333;}#mermaid-svg-U0BKGSUz2a74W5O6 .cluster-label span{color:#333;}#mermaid-svg-U0BKGSUz2a74W5O6 .cluster-label span p{background-color:transparent;}#mermaid-svg-U0BKGSUz2a74W5O6 .label text,#mermaid-svg-U0BKGSUz2a74W5O6 span{fill:#333;color:#333;}#mermaid-svg-U0BKGSUz2a74W5O6 .node rect,#mermaid-svg-U0BKGSUz2a74W5O6 .node circle,#mermaid-svg-U0BKGSUz2a74W5O6 .node ellipse,#mermaid-svg-U0BKGSUz2a74W5O6 .node polygon,#mermaid-svg-U0BKGSUz2a74W5O6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-U0BKGSUz2a74W5O6 .rough-node .label text,#mermaid-svg-U0BKGSUz2a74W5O6 .node .label text,#mermaid-svg-U0BKGSUz2a74W5O6 .image-shape .label,#mermaid-svg-U0BKGSUz2a74W5O6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-U0BKGSUz2a74W5O6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-U0BKGSUz2a74W5O6 .rough-node .label,#mermaid-svg-U0BKGSUz2a74W5O6 .node .label,#mermaid-svg-U0BKGSUz2a74W5O6 .image-shape .label,#mermaid-svg-U0BKGSUz2a74W5O6 .icon-shape .label{text-align:center;}#mermaid-svg-U0BKGSUz2a74W5O6 .node.clickable{cursor:pointer;}#mermaid-svg-U0BKGSUz2a74W5O6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-U0BKGSUz2a74W5O6 .arrowheadPath{fill:#333333;}#mermaid-svg-U0BKGSUz2a74W5O6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-U0BKGSUz2a74W5O6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-U0BKGSUz2a74W5O6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U0BKGSUz2a74W5O6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-U0BKGSUz2a74W5O6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U0BKGSUz2a74W5O6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-U0BKGSUz2a74W5O6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-U0BKGSUz2a74W5O6 .cluster text{fill:#333;}#mermaid-svg-U0BKGSUz2a74W5O6 .cluster span{color:#333;}#mermaid-svg-U0BKGSUz2a74W5O6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-U0BKGSUz2a74W5O6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-U0BKGSUz2a74W5O6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-U0BKGSUz2a74W5O6 .icon-shape,#mermaid-svg-U0BKGSUz2a74W5O6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U0BKGSUz2a74W5O6 .icon-shape p,#mermaid-svg-U0BKGSUz2a74W5O6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-U0BKGSUz2a74W5O6 .icon-shape .label rect,#mermaid-svg-U0BKGSUz2a74W5O6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U0BKGSUz2a74W5O6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-U0BKGSUz2a74W5O6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-U0BKGSUz2a74W5O6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是









迭代器失效了吗?
容器是连续存储?
触发扩容/缩容?
节点式容器

list/forward_list/map/set
全部迭代器失效

vector/deque
插入/删除在中间?
插入点之后全部失效

vector: 迭代器+引用全失效

deque: 引用也全部失效
两端操作仅当前迭代器失效

deque 两端: 引用不失效
仅被删除元素失效

插入永不失效
unordered_* 触发 rehash?
全部失效
仅被删除元素失效

插入不失效

容器 操作 迭代器失效 指针/引用失效
vector 插入/删除 全部失效(扩容或元素移动) 同左
vector reserve 扩容 全部失效 同左
deque 中间插入 全部失效 全部失效
deque 中间删除 全部失效 仅被删元素失效
deque 两端 push/pop 仅被操作元素失效 不失效
list 任何插入/删除 仅被删元素失效 同左
forward_list 任何插入/删除 仅被删元素失效 同左
map/set/multimap/multiset 插入 不失效 不失效
map/set/... 删除 仅被删元素失效 仅被删元素失效
unordered_* 插入(不 rehash) 不失效 不失效
unordered_* 插入(rehash) 全部失效 全部失效
unordered_* 删除 仅被删元素失效 仅被删元素失效

重点 :deque 中间插入时,引用和迭代器全部 失效------因为 deque 需要在中间的 block 做元素搬迁。两端操作则只失效迭代器(deque 的迭代器存储了 block 指针,两端插入可能改变 block map),但指针/引用保持有效(元素所在的 block 不被销毁)。

2.4 reverse_iterator::base() 的 +1 偏移

cpp 复制代码
template<class Iter>
class reverse_iterator {
    Iter current;   // 指向正向迭代器中"当前元素的下一个"位置
public:
    explicit reverse_iterator(Iter x) : current(x) {}
    Iter base() const { return current; }

    reference operator*() const {
        Iter tmp = current;
        return *--tmp;          // 解引用时取 current 的前一个
    }
};

为什么必须是 +1?

假设 v = {1, 2, 3, 4, 5}ri = v.rbegin() 指向 5,内部 current = v.end()------5 的下一个

如果 base() 返回直接指向同一元素的迭代器:

cpp 复制代码
v.erase(ri.base());             // 本意删 5,实际删 end() ------ UB

正确的删除方式:

cpp 复制代码
v.erase(prev(ri.base()));       // 删 end() 的前一个,即 5

黄金法则reverse_iterator(it).base() == it + 1

2.5 适配器实现本质

cpp 复制代码
template<class Container>
class back_insert_iterator {
protected:
    Container* container;
public:
    using iterator_category = output_iterator_tag;
    using value_type = void;
    using difference_type = void;
    using pointer = void;
    using reference = void;

    explicit back_insert_iterator(Container& c) : container(&c) {}

    back_insert_iterator& operator=(const typename Container::value_type& value) {
        container->push_back(value);
        return *this;
    }

    back_insert_iterator& operator*() { return *this; }
    back_insert_iterator& operator++() { return *this; }
    back_insert_iterator& operator++(int) { return *this; }
};

template<class Container>
back_insert_iterator<Container> back_inserter(Container& c) {
    return back_insert_iterator<Container>(c);
}

operator* 返回 *thisoperator++ 空操作------算法中的 *it = value; ++it 实际只触发 push_backfront_inserterpush_back 换成 push_frontinserter(c, it) 换成 container->insert(it, value)

2.6 Contiguous Iterator(C++17)

C++17 新增 contiguous_iterator_tag,继承自 random_access_iterator_tag。元素在内存中严格连续排列,可安全转为 T* 参与 C 风格 API。

cpp 复制代码
#include <iterator>
#include <vector>
#include <string>
#include <array>
#include <deque>
using namespace std;

int main() {
    vector<int>::iterator vit;                  // 是
    string::iterator sit;                       // 是
    array<int, 5>::iterator ait;                // 是
    int* pit;                                   // 是(原生指针也是)

    deque<int>::iterator dit;                   // 否(分块存储)
    list<int>::iterator lit;                    // 否
    forward_list<int>::iterator fit;            // 否
    return 0;
}

工程意义 :编译期判断能否安全地 &*it 传给 C API:

cpp 复制代码
template<class It>
void send_to_device(It first, It last) {
    if constexpr (contiguous_iterator<It>) {
        auto* ptr = to_address(first);          // C++20
        size_t n = last - first;
        // cudaMemcpy(device_ptr, ptr, n * sizeof(*ptr), ...);
    } else {
        // fallback: 逐元素拷贝
    }
}

三、面试题

Q1:vector 插入后所有迭代器都失效,list 不会------为什么?

"vector 连续存储,插入时扩容需搬迁整个数组到新内存,旧迭代器全变野指针;不扩容时插入点之后的元素也要后移一位,迭代器指向的位置变了。list 每个节点独立堆分配,插入仅改相邻节点指针,不移动已有元素------只有指向被删除节点的迭代器失效。"

Q2:reverse_iterator 的 base() 为什么返回 it + 1?

"reverse_iterator 内部存的是正向迭代器中"当前元素的下一个位置"。rbegin() 指向末元素 5,内部存的是 end()。base() 返回 end()------如果你要删除 ri 指向的元素,erase(ri.base()) 将越界 UB。正确的正向写法是 prev(ri.base())(ri + 1).base()。这个+1偏移是为 erase 语义设计的。"

Q3:std::distance 对 forward_list 为什么是 O(n)?

"distance 通过迭代器标签分派:RandomAccessIterator 用 last - first(O(1)),其他类别用循环计数(O(n))。forward_list 只提供 Forward Iterator,不支持 operator-,只能从头走到尾。这就是标签分派在算法复杂度上的直接体现。"

Q4:输入迭代器为什么不能 multi-pass?

"输入迭代器(如 istream_iterator)是单向一次性读通道------++it 后之前位置的元素已被消费,无法回退。两遍遍历同一个范围时,第一遍读完整个流,迭代器已到 EOF 状态,第二遍直接结束。Forward Iterator 背后是持久化数据结构,多次遍历得到相同结果。"

Q5:back_inserter 怎么把赋值变成插入?

"back_inserter© 返回 back_insert_iterator,它的 operator= 内部调用 push_back(value)。关键是 operator* 返回 *thisoperator++ 是空操作------所以算法中 *it = value; ++it 实际只执行了赋值动作。这就是适配器模式在 STL 中的经典应用。"

Q6:C++17 的 contiguous_iterator 有什么用?

"contiguous_iterator_tag 是第六种标签,继承自 random_access_iterator_tag。vector、string、array 和原生指针满足它------元素严格连续排列。deque 是分块连续,不是 contiguous。它的价值在于编译期判断能否安全取连续内存传给 C API(memcpy、write、cudaMemcpy),C++20 的 to_addressis_contiguous_iterator_v 建立在此之上。"

Q7:inserter(c, it) 对 map 插入有什么性能意义?

"map::insert(hint, value) 的 hint 恰好正确时复杂度从 O(log n) 降为 O(1) amortized。有序序列上连续用 inserter,每次 hint 都是上次插入位置的迭代器,总复杂度从 O(n log n) 优化到 O(n)。批量构建 map 时有用。"

Q8:写自定义迭代器需要哪些步骤?

"定义 iterator_category、value_type、difference_type、pointer、reference 五种 trait,然后实现 operator++operator*operator==。Bidirectional 加 operator--,RandomAccess 加 operator+=operator-=operator[]operator< 等。现代 C++ 推荐用 ranges 的 view_interface 封装迭代逻辑,避免手写。"


一句话总结 :迭代器的本质是容器遍历接口的标准化------六大标签通过 tag dispatching 在编译期为 advance 等算法选择 O(1) 或 O(n) 路径;失效规则(尤其 deque 中间插引用全失效)决定迭代器安全边界;适配器模式让算法无需感知具体容器。