[Fun with c++] List explanation and simulation of the underlying implementation

Topic of this issue: List explanation and simulation implementation
Blog homepage: Xiaofeng students
share the knowledge I learned in Linux and the problems I encountered
.

1. Introduction and use of list

1.1. The introduction of list

1.list is a sequential container that can be inserted and deleted at any position within a constant range, and the container can be iterated in both directions. 2. The bottom layer of the list is a doubly linked list structure. Each element in the doubly linked list is stored in an independent node that is not related to each other. In the node, pointers point to the previous element and the next element. 3. list is very similar to forward_list: the main difference is that forward_list is a singly linked list, which can only be iterated forward, which makes it simpler and more efficient. 4. Compared with other serial containers (array, vector, deque), list usually has better execution efficiency for inserting and removing elements at any position. 5. Compared with other sequential containers, the biggest defect of list and forward_list is that it does not support random access at any position, for example: to access the sixth element of the list, it must be iterated from a known position (such as the head or tail) to this position, iterating over this position requires linear time overhead; the list also requires some additional space to hold the associated information for each node (this may be an important issue for large lists that store elements of smaller types the elements of)

1.2. The use of list

1.2.1. Member functions

This is almost the same as vector

1.2.2. Iterators

Similar to vector,
we need to know the importance of iterators from this point on. Subscript access is not supported, so iterators are very important at this time.
The real, unified traversal method for all containers is the iterator. 3

1.2.3. Capacity related

1.2.4. Access related

1.2.5. Modification related

1.2.6. Operation related

2. The simulation implementation of List

2.1. Source code

source code
#pragma once
#include <utility>
#include<iostream>
#include<assert.h>
using namespace std;

namespace zxf2
{

    template<class T>
    struct list_node
    {
        //共有成员变量
        list_node* _next;
        list_node* _prev;
        T _data;

        //每个节点的构造函数。
        list_node(const T& val = T())
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(val){}
    };


    //这里实现的迭代器是普通版本的,那如果想要一个const版本的迭代器怎么办?
    //const list_iterator<T> it = lt.begin();//这样  显然不是我们想要的结果,
    //上述的写法只能是it 不能改,但是 const迭代器是 *it不能改动。
    //为什么vector模拟实现的时候 直接 定义了 : typedef const T* const_iterator;自然就是实现了 it可以改,而 *it不能改的目的,(利用了指针的特性)
    //
    //template<class T>
    //struct list_iterator
    //{
    //    //迭代器本质上是在控制节点的指针 _pnode;
    //    //是对节点(node)指针的封装。
    //    typedef list_node<T> node;
    //    typedef list_iterator<T> self;//迭代器类型重命名。
    //    //成员变量
    //    node* _pnode;
    //    //迭代器在创建的时候必须初始化
    //    //使用节点的指针来构造
    //    list_iterator(node* pnode){
    //        _pnode = pnode;
    //    }


    //    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。
    //    self& operator++()
    //    {
    //        _pnode = _pnode->_next;
    //        return *this;
    //    }
    //    self& operator--()
    //    {
    //        _pnode = _pnode->_prev;
    //        return *this;
    //    }

    //    //后置++ , -- ;不能返回引用。
    //    self operator++(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_next;
    //        return tmp;
    //    }
    //    self operator--(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_prev;
    //        return tmp;
    //    }
    //    //所以一般使用前置++,减少拷贝照成的空间和时间浪费。

    //    //判断两个迭代器是否相同就是迭代器里面的成员变量的地址是否相同。
    //    bool operator!=(const self& lt)const
    //    {
    //        return _pnode != lt._pnode;
    //    }
    //    bool operator==(const self& lt)const
    //    {
    //        return _pnode == lt._pnode;
    //    }

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

    //template<class T>
    //struct list_const_iterator
    //{
    //    //迭代器本质上是在控制节点的指针 _pnode;
    //    //是对节点(node)指针的封装。
    //    typedef list_node<T> node;
    //    typedef list_const_iterator<T> self;//迭代器类型重命名。
    //    //成员变量
    //    node* _pnode;
    //    //迭代器在创建的时候必须初始化
    //    //使用节点的指针来构造
    //    list_const_iterator(node* pnode) {
    //        _pnode = pnode;
    //    }


    //    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。
    //    self& operator++()
    //    {
    //        _pnode = _pnode->_next;
    //        return *this;
    //    }
    //    self& operator--()
    //    {
    //        _pnode = _pnode->_prev;
    //        return *this;
    //    }

    //    //后置++ , -- ;不能返回引用。
    //    self operator++(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_next;
    //        return tmp;
    //    }
    //    self operator--(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_prev;
    //        return tmp;
    //    }
    //    //所以一般使用前置++,减少拷贝照成的空间和时间浪费。

    //    //判断两个迭代器是否相同就是迭代器里面的成员变量的地址是否相同。
    //    bool operator!=(const self& lt)const
    //    {
    //        return _pnode != lt._pnode;
    //    }
    //    bool operator==(const self& lt)const
    //    {
    //        return _pnode == lt._pnode;
    //    }

    //    const T& operator*()//这里是const迭代器和 普通迭代器真的区别,区别就这一点点。就是在解引用的时候,返回的是const类型还是普通类型。
    //        //返回const类型就是不能修改(const迭代器),返回普通类型就是可以修改(普通迭代器)。
    //    {
    //        return _pnode->_data;
    //    }
    //};


    //上面两个迭代器多少有点代码冗余。可以把他们合并在一起。

template<class T , class ref, class ptr>
struct list_iterator
{
    //迭代器本质上是在控制节点的指针 _pnode;
    //是对节点(node)指针的封装。
    typedef list_node<T> node;
    typedef list_iterator<T, ref ,ptr> self;//迭代器类型重命名。
    //成员变量
    node* _pnode;
    //迭代器在创建的时候必须初始化
    //使用节点的指针来构造
    list_iterator(node* pnode) {
        _pnode = pnode;
    }


    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。
    self& operator++()
    {
        _pnode = _pnode->_next;
        return *this;
    }
    self& operator--()
    {
        _pnode = _pnode->_prev;
        return *this;
    }

    //后置++ , -- ;不能返回引用。
    self operator++(int)
    {
        self tmp(*this);//出了函数tmp会被销毁的。
        _pnode = _pnode->_next;
        return tmp;
    }
    self operator--(int)
    {
        self tmp(*this);//出了函数tmp会被销毁的。
        _pnode = _pnode->_prev;
        return tmp;
    }

    bool operator!=(const self& lt)const//这里的const有什么用?
    {
        return _pnode != lt._pnode;
    }
    bool operator==(const self& lt)const
    {
        return _pnode == lt._pnode;
    }

    ref operator*()
    {
        return _pnode->_data;
    }
    ptr operator->()
    {
        return &_pnode->_data;
    }
    self operator+=(size_t n)
    {
        while (n--)
        {
            _pnode = _pnode->_next;
        }
        return *this;
    }

    self operator-=(size_t n)
    {
        while (n--)
        {
            _pnode = _pnode->_prev;
        }
        return *this;
    }

};
    template <class T>
    class list
    {
        //类名 list
        //类型 list<T>
        typedef list_node<T> node;
    public:
        //typedef list_iterator<T> iterator;
        //typedef list_const_iterator<T> const_iterator;
        typedef list_iterator<T, T& ,T*> iterator;
        typedef list_iterator<T, const T&, const T*> const_iterator;
        //创建出头节点
        void empty_list()
        {
            _phead = new node(T());
            _phead->_next = _phead;
            _phead->_prev = _phead;
        }

        //无参构造
        list()
        {
            empty_list();
        }

        void push_back(const T& val)
        {
            //node* tmp = new node(val);
            //tmp->_next = _phead;
            //tmp->_prev = _phead->_prev;
            //_phead->_prev->_next = tmp;
            //_phead->_prev = tmp;
            insert(iterator(_phead), val);
        }
        void push_front(const T& val)
        {
            //node* tmp = new node(val);
            //tmp->_next = _phead;
            //tmp->_prev = _phead->_prev;
            //_phead->_prev->_next = tmp;
            //_phead->_prev = tmp;
            insert(iterator(_phead->_next), val);
        }
        //单参数构造
        list(const T& val)
        {
            empty_list();
            push_back(val);
        }

        //迭代器构造的模板

        template<class input_iterator>
        list(input_iterator first, input_iterator last)
        {
            //创建头节点
            empty_list();
            while (first != last)
            {
                push_back(*first);//(*first)是 T/const T 类型的数据
                ++first;
            }
        }


//        //拷贝构造(古法) 里 lt1 = lt2;
//        list(const list<T>& lt)
//            //注意拷贝构造的参数也是引用,防止无线循环,
//        {
//            empty_list();
需要迭代器的支持
//            for (auto& e : lt){//注意这样的引用,const 可加可不加。
//                //引用1,防止e的空间浪费;2,list<list <T>>这种情况出现时,死循环。
//                push_back(e);
//            }
//        }

        void swap(list& lt)
        {
            std::swap(lt._phead, _phead);
        }


        ~list()
        {
            clear();
            delete _phead;
            _phead = nullptr;
        }

        void clear()
        {
            //注意这里只能是 iterator ,不能是 const_iterator 
            //const list 才会返回const_iterator,但是 const list 不能修改
            iterator first = begin();
            //while (first != end())
            //{
            //    erase(first);
            //    ++first;
            //}
            //上述写法是错误的
            while (first != end())
            {
                first = erase(first);
            }
        }
        size_t size()
        {
            return _size;
        }
        bool empty()
        {
            return _size == 0;
        }
        

        //lt1 =lt2;
        list<T> operator=(list<T> lt)
        {
            swap(lt);
            return *this;

        }
        //注意删除的时候要返回欲删除元素下一个元素的迭代器,
        iterator erase(iterator it)
        {
            assert(it != end());
            node* next = it._pnode->_next;
            node* prev = it._pnode->_prev;
            next->_prev = prev;
            prev->_next = next;
            delete it._pnode;
            return iterator(next);// 匿名对象, 这里的生命周期只有一行,不能引用返回。
            --_size;
        }


        iterator insert(iterator pos, const T& val)
        {
            node* newnode = new node(val);
            node* next = pos._pnode;
            node* prev = pos._pnode->_prev;
            newnode->_next = next;
            newnode->_prev = prev;
            prev->_next = newnode;
            next->_prev = newnode;
            return iterator(newnode);
            ++_size;
        }




        void pop_back()
        {
            erase(iterator(_phead->_prev));
        }

        void pop_front()
        {
            erase(iterator(_phead->_next));
        }

        //拷贝构造(现代) 里 lt1 = lt2;
        list(const list<T>& lt)
            //注意拷贝构造的参数也是引用,防止无线循环,
        {
            //无论什么构造,都要先创建头节点
            empty_list();
            list tmp(lt.begin(), lt.end());//先创建一个list类型的临时局部变量,出了作用域会被销毁。
            swap(tmp);
        }

        //这里也不能返回引用
        iterator begin()
        {
            return iterator(_phead->_next);//匿名对象,生命周期只有一行
        }
        const_iterator begin()const
        {
            return const_iterator(_phead->_next);//匿名对象,生命周期只有一行
        }

        iterator end()
        {
            return iterator(_phead);//匿名对象,生命周期只有一行
        }
        const_iterator end()const
        {
            return const_iterator(_phead);//匿名对象,生命周期只有一行
        }






    private:
        //list私有的成员
        node* _phead;
        size_t _size;
    };






}

2.2. Frequently Asked Questions

2.2.1. List iterator invalidation

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节 点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
void TestListIterator1()
{
 int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 list<int> l(array, array+sizeof(array)/sizeof(array[0]));
 auto it = l.begin();
 while (it != l.end())
 {
 // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
其赋值
 l.erase(it); 
 ++it;
 }
}

// 改正
void TestListIterator()
{
 int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 list<int> l(array, array+sizeof(array)/sizeof(array[0]));
 auto it = l.begin();
 while (it != l.end())
 {
 l.erase(it++); // it = l.erase(it);
 }
}

2.2.2.类模板中类名和类型的问题

类一般只有两种:
普通类 和 类模板
对于普通类: 类名就是类型,是相同的。
对于类模板:类名是类名,给T赋上对应的类型后才是类的类型
class BBB
{
    int _data;
};
//在这里类名就是类型
//类名:BBB
//类型:BBB

template<class T>
class AAA
{
    T _data;
};
//在这里
//类名是:AAA
//类型是:AAA<T>
但是在类模板里面可以使用类名去代表类型,但是在类外面类名就是类名,类型就是类型。
但是我个人习惯不喜欢,这个特例。

2.2.3.迭代器it的 -> 的访问问题

看一下示例代码
struct Pos
{
    int _row;
    int _col;

    Pos(int row = 0, int col = 0)
        :_row(row)
        , _col(col)
    {}
};

void print_list(const zxf2::list<Pos>& lt)
{
    zxf2::list<Pos>::const_iterator it = lt.begin();
    while (it != lt.end())
    {
        //it->_row++;

        //这里还能++ ,明明是 const类型的list<Pos>为啥里面的数据可以++,
        //因为这里又会出现一个问题 T*  和 const T* 的  问题,
        //const_iterator 对应的应该是 const T&(解引用的返回值不可修改)  和 const T* (it-> 返回值不可修改) 的问题。
        //iterator 对应的应该是 T& (解引用的返回值可修改)  和  T* (it-> 返回值不可修改) 的问题。


        //迭代器是什么?
        //功能类似于指针,可以 * 和 -> 访问指针元素的类模板
        //他是通过 对 T* 进行封装的得到的一个类。

        //typedef list_iterator<T, T&, T*> iterator;
        //普通迭代器:
        // 重载operator* 的时候 用T&返回。 
        // 重载operator->  的时候用 T* 返回
        //typedef list_iterator<T, const T&, const T*> const_iterator;
        //const迭代器:
        // operator* 的时候 用const T&返回。
        // operator-> 的时候用const T* 返回


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

        ++it;
    }
    cout << endl;
}

void test_list5()
{
    zxf2::list<Pos> lt;
    Pos p1(1, 1);
    lt.push_back(p1);
    lt.push_back(p1);
    lt.push_back(p1);
    lt.push_back(Pos(2, 2));
    lt.push_back(Pos(3, 3));

    // int* p  -> *p
    // Pos* p  -> p->
    zxf2::list<Pos>::iterator it = lt.begin();
    //list<Pos>::iterator it2 = it;
    while (it != lt.end())
    {
        it->_row++;
        //cout << it->;//错误写法。
        //cout << it.operator->();
        //it.operator->()->_col++;
        //cout << (&(*it))->_row << ":" << (*it)._col << endl;
        cout << it->_row << ":" << it->_col << endl;
        //cout << it.operator->()->_row << ":" << it->_col << endl;

        ++it;
    }
    cout << endl;

    print_list(lt);
}
首先第一个问题:operator->()如何重载的问题:
        ptr operator->()
            //ptr是 T* 类型或者 const T* 类型的节点里面存放数据的地址。
        {
            return &(_pnode->_data);
        }
我们说迭代器是一个类似于指针一样的东西,所以如果list节点数据是一个自定义类型不用->,但是如果list节点数据是一个结构体那么 -> 成员变量应该就可以访问到成员,所以应该 it -> 成员变量 就可以访问到。
但是根据我们迭代器->的重载来看的话它返回的是结构的指针,
所以按理来说访问成员应该是 it -> -> 成员变量
但是我们看到在实例中我们访问的时候 直接是使用 it -> 成员变量 ,即可
所以这里编译器做了优化。我们可以看到下面代码都可以实现,对结构体里面对象的访问
        cout << (&(*it))->_row << ":" << (*it)._col << endl;
        cout << it->_row << ":" << it->_col << endl;
        cout << it.operator->()->_row << ":" << it->_col << endl;
其实本质就是,编译器把 it -> -> _row 优化为 it ->_row
可以从:it.operator->()->_row 证明得出。
这里是编译器的一个特殊处理。在以后的智能指针中很常用。

Guess you like

Origin blog.csdn.net/zxf123567/article/details/129337967