• 【C++】List模拟实现


    List模拟实现

    1.1 创建结点

    1. template<class T>
    2. struct ListNode
    3. {
    4. ListNode<T>* _next;//指向后一个节点
    5. ListNode<T>* _prev;//指向前一个节点
    6. T _data; // 存数据
    7. // 这里用匿名对象当缺省值
    8. ListNode(const T& data = T())
    9. :_next(nullptr)
    10. , _prev(nullptr)
    11. , _data(data)
    12. {}
    13. };

    因为要频繁的调用ListNode里面的数据选择了struct ,struct具有公有性。 

    1. template<class T>
    2. class list
    3. {
    4. typedef ListNode<T> Node;
    5. private:
    6. Node* _head;
    7. };

    1.2 实现遍历功能

    在class list类中添加了构造以及插入函数 

    1. list()
    2. {
    3. _head = new Node();
    4. _head->_next = _head;
    5. _head->_prev = _head;
    6. }
    7. void push_back(const T& x)
    8. {
    9. Node* newnode = new Node(x);
    10. Node* tail = _head->_prev;
    11. tail->_next = newnode;
    12. newnode->_prev = tail;
    13. newnode->_next = _head;
    14. _head->_prev = newnode;
    15. }

    当创建一个list对象首先发生构造,创建头结点,之后就是插入数据 


    迭代器 

     在main函数中我们要这样使用迭代器,进行循环 

    1. list<int> lit;
    2. lit.push_back(1);
    3. lit.push_back(2);
    4. lit.push_back(3);
    5. lit.push_back(4);
    6. lit.push_back(5);
    7. list<int>::iterator it = lit.begin();
    8. // it.operator!=(lit.end());
    9. while (it != lit.end())
    10. {
    11. cout << *it << " ";
    12. ++it;
    13. }

    我们先创建一个对象lit,在这个对象中,我们在各个节点中push_back数据,在push_back内部我们使其各个分散的节点连接一起,最后定义了迭代器进行循环 

    在循环的过程中,我们使用下面的迭代器可以吗?

    typedef Node* iterator;

    不能,因为每个节点是独立的空间地址,每个节点是不连续的,我们无法保证++来移动指针,使其下个地址就是下个节点的位置。 

    那应该怎么办?

    因为,我们要求这个迭代器++就能指向下一个节点位置,我们只能创建个类 

    在这里我们创建一个类,来模拟自定义类型Node的指针,为其添加类似于内置类型的功能(++,--)最后封装成iterator

    1. template<class T>
    2. class ListIterator
    3. {
    4. typedef ListNode<T> Node;
    5. typedef ListIterator<T> Self;
    6. public:
    7. ListIterator(Node* node)
    8. :_node(node)
    9. {}
    10. // ++it
    11. Self& operator++()
    12. {
    13. _node = _node->_next;
    14. return *this;
    15. }
    16. bool operator!=(const Self& it)
    17. {
    18. return _node != it._node;
    19. }
    20. T& operator*()
    21. {
    22. return _node->_data;
    23. }
    24. T* operator->()
    25. {
    26. return &_node->_data;
    27. }
    28. private:
    29. Node* _node;
    30. };

     循环遍历首先要++,移动指针的位置,使其指向下一个节点

    在上面的main函数中

    list<int>::iterator it = lit.begin();

    it接受第一个节点的地址后

    之后进行++it<------>it.operator(第一个节点地址)

    1. Self& operator++()
    2. {
    3. _node = _node->_next;
    4. return *this;
    5. }

     把一个节点地址修改为下一个节点的地址进行返回

    循环遍历的循环条件是

    it != lit.end()

    迭代器的区间一般又是左闭右开

    所以iterator end返回的是头节点

    指向的位置

    什么意思呢?就是指向的位置是左闭右开

     之后就是访问里面的数据

     如果_data里面存储的是内置类型可以直接解引用使用operator*

    如果_data里面存储的是自定义类型这样就要使用operator->去访问里面的数据(若是自定义类型不是一定要用operator->去访问)

    1. struct Pos
    2. {
    3. int _row;
    4. int _col;
    5. Pos(int row = 0,int col = 0)
    6. :_row(row)
    7. ,_col(col)
    8. {}
    9. };

     例如我们自定义一个这样的类型

    我们push_back存储数据,该如何遍历里面的数据呢?

    1. list<Pos> lt1;
    2. lt1.push_back(Pos(100, 100));
    3. lt1.push_back(Pos(200, 200));
    4. lt1.push_back(Pos(300, 300));
    cout << it.operator->()->_col << ":" << it.operator->()->_row << endl;
    

    我们可以向上面一样访问

    operator->返回的是匿名对象存数据的地址,之后再访问Pos类里面的内容(只是调用了一次operator->)

    系统默认优化这样写也行

    cout << it->_col << ":" << it->_row << endl;

    上面的代码可以这么理解,迭代器就是一个指针,指针访问数据

    也可以这么写

    cout << (*it)._col << ":" << (*it)._row << endl;

    我们这里operator*采用的是引用返回,方便修改数据

    operator->采用的是T*返回,也能修改数据。

    如果不想修改数据,又该如何操作 

    const 迭代器 

    假如是一个const list我们又该如何遍历链表

    1. void Func(const list<int>& lt)
    2. {
    3. list<int>::iterator it = lt.begin();
    4. while (it != lt.end())
    5. {
    6. // *it += 10;
    7. cout << *it << " ";
    8. ++it;
    9. }
    10. cout << endl;
    11. }

     首先const的list需要一个const迭代器

     这个const迭代器的功能是 *it内容的数据不能修改,it可以++

    直接在iterator前面+const是不可以的,因为it不能进行++了

     如何操作?再设计一个const迭代器的类

    首先,要明白如何控制*it的内容不被修改------->就是迭代器中的返回的数据不能修改

    也就是控制T&与 T*的返回类型,在他们前面都加上const

    1. T& operator*()
    2. {
    3. return _node->_data;
    4. }
    5. T* operator->()
    6. {
    7. return &_node->_data;
    8. }

    之后再list类中补充一下const list有关迭代器返回函数

    1. typedef ListConstIterator<T> const_iterator;//const迭代器类
    2. const_iterator begin() const
    3. {
    4. return const_iterator(_head->_next);
    5. }
    6. const_iterator end() const
    7. {
    8. return const_iterator(_head);
    9. }

     我们发现,虽然我们实现了const list的遍历,但是代码过于繁琐

     我们通过模板让编译器控制返回的类型这样可以减少代码的繁琐,

    于是分别定义了两个模板在list类中,通过调用不同的名称来控制某个模板

    1. typedef ListIterator<T, T&, T*> iterator;
    2. typedef ListIterator<T, const T&, const T*> const_iterator;

    迭代器的模板,接受的类型一个是T一个是Ref一个是Ptr,若是使用的iterator,迭代器模板所接受的就是T,T&,T*,若是使用的是const_iterator,迭代器模板所接受的就是T,const T&,const T*

    1. template<class T,class Ref,class Ptr>
    2. struct ListIterator
    3. {
    4. typedef ListNode<T> Node;
    5. typedef ListIterator<T, Ref, Ptr> Self;
    6. Node* _node;
    7. ListIterator(Node* node)
    8. :_node(node)
    9. {}
    10. Ref operator*()
    11. {
    12. return _node->_data;
    13. }
    14. Ptr operator->()
    15. {
    16. return &_node->_data;
    17. }
    18. bool operator!=(const Self& it)
    19. {
    20. return _node != it._node;
    21. }
    22. Self& operator++()
    23. {
    24. _node = _node->_next;
    25. return *this;
    26. }
    27. };

    最后把迭代器补充完整

    后置++

    1. // it++
    2. Self& operator++(int)
    3. {
    4. Self tmp(*this);
    5. _node = _node->_next;
    6. return tmp;
    7. }

    注意:这里实现的是浅拷贝,为什么这里不实现深拷贝?

    我们这是需要指针,而不是所指向的内容,使用深拷贝太浪费空间

    前置--,与后置-- 

    1. // --it
    2. Self& operator--()
    3. {
    4. _node = _node->_prev;
    5. return *this;
    6. }
    7. // it--
    8. Self& operator--(int)
    9. {
    10. Self tmp(*this);
    11. _node = _node->_prev;
    12. return _node;
    13. }

    operator== 

    1. bool operator==(const Self& it)
    2. {
    3. return _node == it._node;
    4. }

    operator->

    1. T* operator->()
    2. {
    3. return &_node->_data;
    4. }

     insert

    1. iterator insert(iterator pos, const T& x)
    2. {
    3. Node* cur = pos._node;
    4. Node* newnode = new Node(x);
    5. Node* prev = cur->_prev;
    6. prev->_next = newnode;
    7. newnode->_prev = prev;
    8. newnode->_next = cur;
    9. cur->_prev = newnode;
    10. return iterator(newnode);
    11. }

     

    insert这里没有迭代器失效,因为pos指向的位置没有改变

    erase

    1. iterator erase(iterator pos)
    2. {
    3. assert(pos != end());
    4. Node* cur = pos._node;
    5. Node* prev = cur->_prev;
    6. Node* next = cur->_next;
    7. prev->_next = next;
    8. next->_prev = prev;
    9. delete cur;
    10. return iterator(next);
    11. }

    这里迭代器失效了,因为pos的位置已经delete了,pos是野指针

     尾删,头插,尾删

    1. void pop_back()
    2. {
    3. erase(--end());
    4. }
    5. void push_front(const T& x)
    6. {
    7. insert(begin(), x);
    8. }
    9. void pop_front()
    10. {
    11. erase(begin());
    12. }

    析构

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

    这里我们采用一个clear函数来清除节点的数据,因为清除每个节点后,it失效,erase指向下一个节点的位置,所以要重新赋值一下it,防止野指针

     拷贝构造

    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. }
    11. //lt2(lt1)
    12. list(const list<T>& lt)
    13. {
    14. empty_init();
    15. for (const auto& e : lt)
    16. {
    17. push_back(e);
    18. }
    19. }

    我们这里创建一个empty_init用来初始化头结点,因为拷贝与拷贝构造都需要使用所以创建了一个函数,使其方便调用。在拷贝构造过程中,我们使用了for (const auto& e : lt)循环,为啥使用&?如果这里的T是string等其他自定义类型,会先构造生成一个临时变量,使其效率低下,所以要添加&

    赋值拷贝

    1. list<T>& operator=(list<T> lt)
    2. {
    3. swap(_head, lt._head);
    4. return *this;
    5. }

     这里采用的是是现代写法。

     要是我想使用C++11中的这种初始化方式应该如何模拟?

    		list<int> lit = {1,2,3,4};

     initializer_list il

     

    这个底层其实就是把{ }中的内容变成常量数组 

    底层有2个指针,一个指向开始的位置,一个指向结束的地方 

     

    1. list(initializer_list il)
    2. {
    3. empty_init();
    4. for (const auto& e : il)
    5. {
    6. push_back(e);
    7. }
    8. }

    在mian函数中 初始化lit为1234之后传给形参il经过initializer_list类的实现,已经成为一个il为1234的常量数组了,之后把il中的数据插入到list链表中

  • 相关阅读:
    哲学家就餐问题与python解决方案
    【力扣白嫖日记】601.体育馆的人流量
    9. 回文数
    LCD婴儿电子秤pcba/芯片方案设计
    LFMCW雷达测速基础- 多普勒频移和2DFFT
    在 Visual Studio 中使用远程 MacOS 调试功能
    多旋翼无人机组合导航系统-多源信息融合算法(Matlab代码实现)
    mysql的函数
    uni-app 导航栏自定义图标及图标的点击事件
    【FreeRTOS】FreeRTOS删除任务vTaskDelete()
  • 原文地址:https://blog.csdn.net/m0_73911405/article/details/140956661