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 空间配置器)

相关推荐
2401_891409262 小时前
量化数据13:港股分钟K线、Level2逐笔Tick、十档盘口和港股通历史数据的批量提取
数据库
1024奇点8 小时前
【仓颉语言入门 · 第二篇 第 22 课】
开发语言·ide·开源
w-w0w-w9 小时前
SQL注入
网络·数据库·sql
Warren2Lynch9 小时前
C4 模型详解:利用 Visual Paradigm 可视化软件架构的综合指南
开发语言·uml
tianyuanwo9 小时前
NetBox 数据库详解:当“关系型数据库”被装进一个网络运维的领域模型里
运维·数据库·netbox
一条泥憨鱼9 小时前
【计算机网络】短连接、长连接与连接池:从 TCP 握手到池化复用的完整链路
网络·数据库·网络协议·tcp/ip·计算机网络
IvorySQL9 小时前
深入 PostgreSQL WAL:从崩溃恢复到逻辑复制
数据库·postgresql
FlightYe9 小时前
视界原理之3D视频(四):裸眼、眼镜和头显
android·linux·c++·3d·ffmpeg·音视频
Knight_AL9 小时前
Java 文件下载时文件名中的 `#` 被转换成 `%23`?一次 URL 编码与解码问题排查
java·开发语言
ZhengEnCi10 小时前
GPT-6 Astra 爆改 Qwen 权重真有那么神吗:权重空间编程 0.016% 参数的真相
算法·架构