C++ list 模拟实现(二):const迭代器与反向迭代器,模板的黑魔法

C++ list 模拟实现(二):const迭代器与反向迭代器,模板的黑魔法

摘要 :上一篇我们手撕了 list 的基础骨架,但很多读者留言说被那个 <T, Ref, Ptr> 的三参数模板绕晕了。为什么一个迭代器要传三个参数?const 迭代器是怎么保证数据不被修改的?反向迭代器又是如何"倒着走"的?本文将揭开 STL 泛型编程中最精妙的设计模式。

一、一个让你当场卡壳的面试题

面试官:"你的 list 迭代器,怎么实现 const 版本的?"

候选人 A:"我再写一个 ConstListIterator 类......"

面试官:"那如果还有反向迭代器呢?再写一个 ReverseListIterator?"

候选人 A:"额......那得写好几个类。"

面试官:"STL 源码里只写了一个模板,你看看它是怎么实现的?"

痛点就在这里 :如果普通迭代器、const迭代器、反向迭代器、const反向迭代器都各写一个类,会有大量的代码重复。SGI STL 用模板参数的精妙设计,只写了一份代码,就搞定了所有情况。

二、模板三参数:<T, Ref, Ptr> 的魔法

回顾一下我们上篇写的迭代器:

cpp 复制代码
template <typename T, typename Ref, typename Ptr>
struct ListIterator {
    typedef ListNode<T> Node;
    Node* _node;

    Ref operator*() { return _node->_data; } // Ref 控制读写的权限
    Ptr operator->() { return &(_node->_data); } // Ptr 控制指针类型
    // ... ++, --, ==, !=
};

魔法就在于模板参数 Ref 和 Ptr。我们在 list 类中这样定义:

cpp 复制代码
template <typename T>
class list {
    typedef ListNode<T> Node;
public:
    // 普通迭代器:T&, T*
    typedef ListIterator<T, T&, T*> iterator;
    // const迭代器:const T&, const T* ------ 完美复用同一套代码!
    typedef ListIterator<T, const T&, const T*> const_iterator;
    
    iterator begin() { return iterator(_head->_next); }
    const_iterator begin() const { return const_iterator(_head->_next); }
};

💡 面试金句 :"STL 通过提取 Ref 和 Ptr 作为模板参数,实现了一份代码,多种实例化。这就是泛型编程的威力。"

三、反向迭代器:适配器模式的典范

rbegin() 和 rend() 是怎么实现的?它并不是重新发明一个"倒着走的链表",而是对正向迭代器做了一层封装。

cpp 复制代码
template <typename Iterator>
class ReverseListIterator {
    Iterator _it; // 内部持有一个正向迭代器
public:
    ReverseListIterator(Iterator it) : _it(it) {}

    // 解引用:实际调用的是正向迭代器的前一个位置
    auto operator*() {
        Iterator tmp = _it;
        return *(--tmp); 
    }

    // ++ 实际上是 --
    ReverseListIterator& operator++() {
        --_it;
        return *this;
    }
    // -- 实际上是 ++
    ReverseListIterator& operator--() {
        ++_it;
        return *this;
    }

    bool operator!=(const ReverseListIterator& rit) { return _it != rit._it; }
};

这是经典的适配器模式(Adapter Pattern) :在底层迭代器的基础上,通过反转 ++ 和 -- 的语义,把正向遍历变成逆向遍历。

四、完整的测试验证

cpp 复制代码
void test_iterator_magic() {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);

    // 测试 const 迭代器
    const list<int>& clt = lt;
    list<int>::const_iterator cit = clt.begin();
    // *cit = 10; // ❌ 编译报错,完美保护数据!
    
    // 测试反向迭代器
    cout << "反向遍历: ";
    for (auto rit = 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 迭代器失效:一张图+3句话,面试直接背)

👉 [C++ vector 为什么不用 new\[\]?从源码讲透 allocator 空间配置器](#C++ vector 为什么不用 new[]?从源码讲透 allocator 空间配置器)

相关推荐
Data analyse4561 小时前
Session超时与浏览器休眠的计时校验
大数据·前端·数据库·数据分析
NineData1 小时前
Oracle 实时同步到 Doris 怎么做?全量迁移、CDC 增量复制与数据校验指南
数据库·oracle
xxwl5851 小时前
三、区域赛算法模版第三章---字符串相关算法
笔记·学习·算法
DLite2 小时前
NLang 0.9.0发布:成为可嵌入语言
开发语言·编程语言·编译技术·nlang
信奥卷王2 小时前
[GESP202509 六级] 划分字符串(含视频)
数据结构·算法
有同事要进步2 小时前
LeetCode 14. 最长公共前缀:纵向扫描一次讲透
开发语言·python
修船大队长2 小时前
CanNm 从休眠唤醒后重复消息报文的周期与数量分析
开发语言·php
事圆则缓2 小时前
Kotlin 变量与函数:把页面状态和行为写成清楚的契约
android·开发语言·kotlin
酒醉的胡铁2 小时前
相似度搜索的常见算法
算法·ai