• C++模拟实现——list


    一、成员变量及其基本结构

    1.基本结构模型

    本质是一个带头双向循环列表,将节点进行封装,并且为了方便使用,进行重定义

    2.节点的封装定义

    1. template<class T>
    2. //定义节点
    3. struct list_node
    4. {
    5. list_node* _prev;
    6. list_node* _next;
    7. T _data;
    8. list_node(const T& x = T()) :
    9. _prev(nullptr),
    10. _next(nullptr),
    11. _data(x)
    12. {}
    13. };

    在定义节点时,要注意将初始化一起进行封装完成,提供默认构造函数

    3.成员变量的定义

    成员变量是一个哨兵位的头结点

    1. typedef list_node node;//对节点重命名,方便使用
    2. private:
    3. list_node* _head;

    二、迭代器(重点)

    1.介绍

    list的迭代器用原生指针无法实现,需要对原生指针进行封装,然后对顺序表指针的行为操作进行模拟实现,是list模拟实现中最大的重点难点,此时从使用者的角度上看,依然能将iterator看作为指针去使用,但设计者的角度上看,其本质是一个指针的封装,是个自定义类型。

    2.对指针的基本封装

    1. template<class T>
    2. struct __list_iterator
    3. {
    4. typedef list_node<int> node;//将节点重定义方便使用
    5. typedef __list_iterator<int> self;//将类型重定义方便使用
    6. //成员变量
    7. node* _node;
    8. //初始化
    9. __list_iterator(node* n)
    10. :_node(n)
    11. {}
    12. //模拟实现指针操作
    13. ...
    14. }

    以上对节点指针进行了封装处理,之后逐一实现常用的功能,例如:++ 、--、* 、 -> 、== 、!= 等等

    3.++和--

    要提供迭代器++和--的操作,需要对运算符进行重载,链表迭代器的++本质上是获得下一个节点的地址,--则是前一个节点的地址,并且要区分前置和后置

    1. //++
    2. slef& operator++()
    3. {
    4. _node = _node->_next;
    5. return *this;
    6. }
    7. slef operator++(int)//后置
    8. {
    9. slef tmp(*this);
    10. _node = _node-> _next;
    11. return tmp;
    12. }
    13. //--
    14. self& operator--()
    15. {
    16. _node = _node->_prev;
    17. return *this;
    18. }
    19. self operator--(int)
    20. {
    21. self tmp(*this);
    22. _node = _node->_prev;
    23. return tmp;
    24. }

    4.== 和 !=

    迭代器的比较,本质是要比较其封装在内部的指针是否同一个

    1. bool operator!=(const self& n)
    2. {
    3. return _node != n._node;
    4. }
    5. bool operator==(const self& n)
    6. {
    7. return _node == n._node;
    8. }

    5. * 和 ->

    对解引用操作符的重载,则需要考虑到常量迭代器的调用,常量迭代器去本质是对迭代器所指向的内容进行常量化,因此在这里,const_iterator 和 iterator 的核心区别在于解引用后返回的值是否常量,其他功能相同,因此可以使用类模板去控制这两个运算符重载返回值的区别,在定义部分加上两个新的模板参数即可。

    1. template<class T,class Ref,class Ptr>
    2. strucr __list_iterator
    3. {
    4. ...//定义和重命名等等
    5. Ref operator*()// Ref == T&(迭代器) / const T&(常量迭代器)
    6. {
    7. return _node->_data;
    8. }
    9. //对于->的重载,存在特殊处理,只需要返回
    10. Ptr operator->()// Ptr == T*(迭代器)/ const T*(常量迭代器)
    11. {
    12. return& _node->_data;
    13. }
    14. }
    15. // 迭代器定义部分,在list类内定义
    16. // typedef __list_iterator iterator;
    17. // typedef __list_con_iterator;

    三、构造与析构

    1.默认构造函数

    默认构造需要初始化出一个哨兵位的头结点,并且让节点指针指向自己,为了方便其他构造函数初始化哨兵位的头结点,可以单独写一个函数进行复用

    1. void empty_init()
    2. {
    3. _head = new node;
    4. _head->_next = _head;
    5. _head->_prev = _head;
    6. }
    7. list()//直接的初始化
    8. {
    9. empty_init();
    10. }

    2.用迭代器区间去构造

    迭代器区间构造需要借助函数模板,任意类型的迭代器都可以将值拷贝到容器中

    1. template<class Iterator>
    2. list(Iterator first,Iterator last)
    3. {
    4. //先得初始化容器
    5. empty_init();
    6. while(first != last)
    7. {
    8. push_back(*first); // 底层是
    9. ++first;
    10. }
    11. }

    3.拷贝构造

    拷贝构造这里选择对上面的构造函数进行复用,深拷贝出一个tmp,在进行交换

    1. void swap(list& lt)
    2. {
    3. std::swap(_head, lt._head);
    4. }
    5. list(const list& lt)//拷贝构造
    6. {
    7. empty_init();
    8. list tmp(lt.begin(), lt.end());
    9. swap(tmp);
    10. }

    4.赋值重载

    赋值重载的底层实现,也是在传参的时候,调用了拷贝构造实现深拷贝后,在进行交换

    1. list& operator=(list lt)//赋值重载
    2. {
    3. swap(lt);
    4. return *this;
    5. }

    5.析构函数

    可以先实现clear,然后复用,底层就是将所有节点全部逐一释放,用迭代器遍历释放即可

    1. void clear()
    2. {
    3. iterator it = begin();
    4. while (it != end())
    5. {
    6. it = erase(it);
    7. }
    8. }
    9. ~list()//析构
    10. {
    11. clear();
    12. delete _head;
    13. _head = nullptr;
    14. }

    四、增删操作

    对应增删操作,只需要实现insert和erase,其余的头插头删等等都可以对其进行复用,这里是用迭代器去实现的。

    1. void insert(iterator pos, const T& x)
    2. {
    3. node* cur = pos._node;
    4. node* prev = cur->_prev;
    5. node* new_node = new node(x);
    6. //链接
    7. new_node->_prev = prev;
    8. prev->_next = new_node;
    9. new_node->_next = cur;
    10. cur->_prev = new_node;
    11. }
    12. iterator erase(iterator pos)
    13. {
    14. assert(pos != end());
    15. node* cur = pos._node;
    16. node* prev = cur->_prev;
    17. node* next = cur->_next;
    18. delete cur;
    19. //链接
    20. prev->_next = next;
    21. next->_prev = prev;
    22. return iterator(next);
    23. }

    需要注意的是,erase后迭代器会失效,因此为了部分场景下的方便,erase是有一个返回值的,返回的是下一个节点的迭代器;

    总结

    本章通过自行模拟实现了list,加深了类和对象以及list的相关知识,其中很重要的一个知识点就是对与list迭代器的封装和实现,本篇博客整理了整个实现过程的思路,方便今后复习和其他同学参考学习

  • 相关阅读:
    Python 中的模糊字符串匹配
    采购软件能否降低企业采购成本?如何实现的?
    使用PyTorch实现简单的AlphaZero的算法(1):背景和介绍
    [附源码]计算机毕业设计JAVAjsp校外教育机构类网站
    linux基本指令(二)
    有意识的神经网络之对比网络层和后意识层 两个em 自回归解码
    《最新出炉》系列入门篇-Python+Playwright自动化测试-45-鼠标操作-下篇
    Nuxt - 自定义页面布局,<Nuxt /> 个性化多套模板(一个项目内既要有用户正常浏览的普通页面,又要存在后台管理系统布局的页面)
    Linux搭建NFS服务器
    共享日志。 vCorfu: A Cloud-Scale Object Store on a Shared Log
  • 原文地址:https://blog.csdn.net/china_chk/article/details/133933619