C++ STL中list迭代器的实现

简介: C++ STL中list迭代器的实现

list 的模拟实现中,重难点在于迭代器功能的实现,因此本文只围绕 iterator 及 const_iterator 的设计进行介绍,其余如增删查改则不再赘述——在C语言的基础上,这些都非常简单。


与 string / vector 不同,list 的节点原生指针不能通过简单的 ++ / * 等实现迭代器,因此我们需要对节点指针进行封装,利用自定义类型支持运算符重载的性质,完成迭代器的设计。


为了与 STL中list 进行区分,我们在创建的命名空间内实现。


一、iterator 的实现

(双向带头循环)链表及其节点的结构设计:

push_back():

void push_back(const T& x)
{
    Node* newnode = new Node(x);
    Node* tail = _head->prev;

    tail->_next = newnode;
    newnode->_prev = tail;
    _head->_prev = newnode;
    newnode->_next = _head;
}

我们很容易可以插入一组数据,但如果想遍历链表,同时对数据进行修改,则需要实现迭代器。

list 无法像 vector 一样简单地 ++/* 实现节点遍历,且原始指针也不支持对运算符重载,我们需要对 list节点的指针 进行封装

template<class T>
struct __list_iterator
{
    typedef ListNode<T> Node;
    Node* _node;// 被封装的节点指针
    
    // 迭代器的构造
    __list_iterator(Node* node)
      :_node(node)
  {}
};

struct __list_iterator
{
  typedef __list_iterator<T> self;  
    
    self operator++()
    {
        _node = _node->_next;
        return _node;
    }
    
    T& operator*()
    {
        return _node->_data;
    }
};

实际上,编译器优化后,会直接用 _node 对等式左边的变量进行构造。

以此类推,后置++ / 前后置-- / != / == 等都很容易了。

二、const_iterator 及第二个模板参数的引入

const_iterator 不能通过对 iterator 加 const 实现:


const_iterator 是为了防止 list 节点的数据被修改;iterator 加 const 会让迭代器本身无法++/–,导致 const list 无法实现遍历——迭代器不能++/–

const_iterator 本身的功能很简单——防止 list 节点的数据被修改,我们只需要针对性的修改 * 重载,其余与普通迭代器没有区别。

const T& operator*()
{
    return _node->_data;
}
// 普通迭代器 iterator:
// T& operator*() —— 能对节点的数据进行修改

即:

template<class T>
struct __list_const_iterator
{
    // ...
  const T& operator*()
    {
        return _node->_data;
    }
};

template<class T>
class list
{
    // ...
    typedef __list_const_iterator<T> const_iterator;
}

仅有一个函数不同,而其余部分都一样(与 iterator 相比),这种做法不是很好。

因此,我们引入第二个模板参数。

template<class T, class Ref>
struct __list_iterator
{
    // ...
  Ref operator*()
    {
        return _node->_data;
    }
}

template<class T, class Ref>
class list
{
  typedef __list_iterator<T, T&> iterator;
    typedef __list_iterator<T, const T&> const_iterator;
    // ...
}
  • 用一个例子测试 const迭代器:
namespace MyList
{
    void Print(const list<int>& lt)
    {
        for (auto& e : lt)
        {
            cout << e << " ";
        }
        cout << endl;
    }
    void test1()
    {
        list<int> lt;
        lt.push_back(10);
        lt.push_back(20);
        lt.push_back(30);
        lt.push_back(40);
        
        Print(lt);
    }
}

int main()
{
    test1();
    return 0;
}

三、箭头->重载

T* operator->()
{
    return &(_node->_data);
}


观察一下:it->_year ——> it.operator->()_year

此处 it.operator->() 的返回值是 Date* ,不应该写成 it->->_year 吗?

事实上,编译器在此处进行了优化,->-> 的写法反而错了

我们也可以引入第三个模板参数 Ptr ,这就就可以同时实现 T* 和 const T* 。

template<class T, class Ref, class Ptr>
struct __list_iterator
{
    typedef __list_iterator<T, Ref, Ptr> self;
    // ...
};

template<class T>
class list
{
    typedef __list_iterator<T, T&, T*> iterator;
    typedef __list_iterator<T, const T&, const T*> const_iterator;
    // ...
}
相关文章
|
6月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
343 2
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
696 73
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
564 3
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
871 1
|
10月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
250 0
|
10月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
390 0
|
12月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
462 12
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。