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* 返回 *this,operator++ 空操作------算法中的 *it = value; ++it 实际只触发 push_back。front_inserter 把 push_back 换成 push_front,inserter(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* 返回 *this,operator++ 是空操作------所以算法中 *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_address 和 is_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 中间插引用全失效)决定迭代器安全边界;适配器模式让算法无需感知具体容器。