C++ list 模拟实现(二):const迭代器与反向迭代器,模板的黑魔法
摘要:上一篇我们手撕了 list 的基础骨架,但很多读者留言说被那个
<T, Ref, Ptr>的三参数模板绕晕了。为什么一个迭代器要传三个参数?const 迭代器是怎么保证数据不被修改的?反向迭代器又是如何“倒着走”的?本文将揭开 STL 泛型编程中最精妙的设计模式。
一、一个让你当场卡壳的面试题
面试官:“你的 list 迭代器,怎么实现 const 版本的?”
候选人 A:“我再写一个ConstListIterator类……”
面试官:“那如果还有反向迭代器呢?再写一个ReverseListIterator?”
候选人 A:“额……那得写好几个类。”
面试官:“STL 源码里只写了一个模板,你看看它是怎么实现的?”
痛点就在这里:如果普通迭代器、const迭代器、反向迭代器、const反向迭代器都各写一个类,会有大量的代码重复。SGI STL 用模板参数的精妙设计,只写了一份代码,就搞定了所有情况。
二、模板三参数:<T, Ref, Ptr> 的魔法
回顾一下我们上篇写的迭代器:
template<typenameT,typenameRef,typenamePtr>structListIterator{typedefListNode<T>Node;Node*_node;Refoperator*(){return_node->_data;}// Ref 控制读写的权限Ptroperator->(){return&(_node->_data);}// Ptr 控制指针类型// ... ++, --, ==, !=};魔法就在于模板参数Ref和Ptr。我们在 list 类中这样定义:
template<typenameT>classlist{typedefListNode<T>Node;public:// 普通迭代器:T&, T*typedefListIterator<T,T&,T*>iterator;// const迭代器:const T&, const T* —— 完美复用同一套代码!typedefListIterator<T,constT&,constT*>const_iterator;iteratorbegin(){returniterator(_head->_next);}const_iteratorbegin()const{returnconst_iterator(_head->_next);}};💡面试金句:“STL 通过提取
Ref和Ptr作为模板参数,实现了一份代码,多种实例化。这就是泛型编程的威力。”
三、反向迭代器:适配器模式的典范
rbegin()和rend()是怎么实现的?它并不是重新发明一个“倒着走的链表”,而是对正向迭代器做了一层封装。
template<typenameIterator>classReverseListIterator{Iterator _it;// 内部持有一个正向迭代器public:ReverseListIterator(Iterator it):_it(it){}// 解引用:实际调用的是正向迭代器的前一个位置autooperator*(){Iterator tmp=_it;return*(--tmp);}// ++ 实际上是 --ReverseListIterator&operator++(){--_it;return*this;}// -- 实际上是 ++ReverseListIterator&operator--(){++_it;return*this;}booloperator!=(constReverseListIterator&rit){return_it!=rit._it;}};这是经典的适配器模式(Adapter Pattern):在底层迭代器的基础上,通过反转++和--的语义,把正向遍历变成逆向遍历。
四、完整的测试验证
voidtest_iterator_magic(){list<int>lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);// 测试 const 迭代器constlist<int>&clt=lt;list<int>::const_iterator cit=clt.begin();// *cit = 10; // ❌ 编译报错,完美保护数据!// 测试反向迭代器cout<<"反向遍历: ";for(autorit=lt.rbegin();rit!=lt.rend();++rit){cout<<*rit<<" ";}cout<<endl;// 期望:3 2 1}五、面试高频 FAQ
Q1:为什么const_iterator不能直接继承iterator?
虽然在语法上可以继承,但iterator的operator*返回的是T&,允许修改数据。const_iterator必须返回const T&。通过三参数模板进行实例化,比继承更干净、更符合泛型编程思想。
Q2:反向迭代器的base()有什么用?base()返回内部持有的正向迭代器。注意一个反直觉的点:rbegin().base() == end()。因为rbegin()指向最后一个元素,它的底层正向迭代器其实是end(),解引用时做--操作。
Q3:为什么 list 的迭代器没有operator+?
因为 list 是双向链表,不支持随机访问。it + 3无法在 O(1) 时间内找到目标节点。
六、总结与挑战
| 核心设计 | 实现原理 |
|---|---|
const迭代器 | 三参数模板<T, Ref, Ptr> |
| 反向迭代器 | 适配器模式,反转++/-- |
| 代码复用 | 一份模板代码,实例化四种迭代器 |
核心结论:迭代器的封装是 STL 中最考验设计能力的部分。掌握了这一套“黑魔法”,你再看其他容器的源码,思路就会清晰很多。
👇 评论区互动:
你觉得三参数模板<T, Ref, Ptr>绕吗?
扣1觉得绕,但懂了;
扣2觉得这设计太牛了。
评论区见!
🔥 系列爆款推荐:
👉 C++ vector 迭代器失效:一张图+3句话,面试直接背
👉 C++ vector 为什么不用 new[]?从源码讲透 allocator 空间配置器