8.list的模拟实现

一.源码分析

1.成员变量

链表里面存储的就是节点的指针

2.构造函数

我们就可以知道,list是双向链表

3.push_back()函数分析

在position位置之前插入节点

二.定义成员函数

cpp 复制代码
namespace ltw
{
    template<class T>
    struct ListNode
    {
        ListNode<T>* _next;
        ListNode<T>* _prev;

        T _data;
    };

    template<class T>
    class List
    {
        typedef ListNode<T> Node;
    public:
        List()
        {
            head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }
    private:
        Node* _head;
    };
}

三.实现push_back()函数

cpp 复制代码
void push_back(const T& x)
        {
            Node* newnode = new Node(x);
            Node* tail = _head->_prev;

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

我们在使用模板的时候,在编译的时候是不会细节检查的,只有实例化(不实例化也不会报错),才会去检查

按需实例化(不调用就不实例化这个成员函数)

所以我们要给push_back()提供一个ListNode(const T& data)的构造函数

cpp 复制代码
template<class T>
    struct ListNode
    {
        ListNode<T>* _next;
        ListNode<T>* _prev;

        T _data;
        ListNode(const T& data)
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(data)
        {

        }
    };

我们这里使用_next和_prev很频繁,所以我们对于整个类使用struct(全部用公有)

cpp 复制代码
List()
        {
            head = new Node(T());
            _head->_next = _head;
            _head->_prev = _head;
        }

所以我们还不如直接将上面的写成全缺省的类型,代码如下:

cpp 复制代码
namespace ltw
{
    template<class T>
    struct ListNode
    {
        ListNode<T>* _next;
        ListNode<T>* _prev;

        T _data;
        ListNode(const T& data = T())
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(data)
        {

        }
    };

    template<class T>
    class List
    {
        typedef ListNode<T> Node;
    public:
        List()
        {
            head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }

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

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

    private:
        Node* _head;
    };
}

四.遍历list(重点)

1.迭代器的实现

我们不能使用前两个数据结构的方法 typedef Node* iterator (因为++没办法取到我们的下一个节点的)

我们重新设计一个类,来实现迭代器的功能

cpp 复制代码
template<class T>
    class ListIterator
    {
        typedef ListNode<T> Node;
        Node* _node;
    };
cpp 复制代码
template<class T>
    class ListIterator
    {
        typedef ListNode<T> Node;
        typedef ListIterator<T> Self;

        Node* _node;

        ListIterator(Node* node)
            :_node(node)
        {

        }

        // ++it
        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }

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

        bool operator!=(const Self& it)
        {
            return _node != it._node;
        }
    };

迭代器类的主要实现就是, ++ , * , != 这三个运算符,我们就先写这三个

2.在List里面引入迭代器

1.begin()的实现

cpp 复制代码
typedef ListIterator<T> iterator;

        iterator begin()
        {
            iterator it(_head->_next);
            return it;
        }

2.end()的实现

cpp 复制代码
iterator end()
        {
            return iterator(_head);
        }

end()是最后一个元素的下一个位置,所以是我们的哨兵位

3.List类的总代码

cpp 复制代码
template<class T>
    class List
    {
        typedef ListNode<T> Node;
    public:
        typedef ListIterator<T> iterator;

        iterator begin()
        {
            // iterator it(_head->_next);
            // return it;
            return iterator(_head->_next);
        }

        iterator end()
        {
            return iterator(_head);
        }


        List()
        {
            head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }

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

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

    private:
        Node* _head;
    };
cpp 复制代码
测试代码

void test_list1()
	{
		list<int> lt1;

		// 按需实例化(不调用就不实例化这个成员函数)
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		lt1.push_back(5);

		// Func(lt1);

		//ListIterator<int> it = lt1.begin();
		list<int>::iterator it = lt1.begin();
		while (it != lt1.end())
		{
			*it += 10;

			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

内置类型不能改变运算符的规则,但是我要是把内置类型进行封装,然后对封装的类进行重载,这样就间接的将我们的内置类型进行重载了

以后,对于红黑树,哈希表,我们都可以是要上述的方式进行操作,达到我们想要的效果

4.完善我们迭代器的运算符

1.重载++,--(前置和后置)

cpp 复制代码
// ++it
        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }
        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }
        Self& operator++(int)
        {
            Self tmp(*this);
            _node = _node->_next;
            return tmp;
        }
        Self& operator--(int)
        {
            Self tmp(*this);
            _node = _node->_prev;
            return tmp;
        }

2.operator==()

cpp 复制代码
bool operator==(const Self& it)
        {
            return _node == it._node;
        }

operator+效率太低了,我们就不重载了(库里面也没重载)

迭代器的节点是不用进行析构的,因为我们的节点不是单独开的,而是在链表里面的,(析构交给链表)

不要越级管理

3.拷贝构造(这个地方就是浅拷贝)

我们要拷贝的话,就是希望把我们的指针拷贝给你,默认的拷贝构造就够用

4.重载operator->

cpp 复制代码
T* operator->()
        {
            return &_node->_data;
        }

理解为啥要 重载operator->运算符?

我们要是想用,就能够重载 operator <<,但是我们也可以进行重载operator->

下面这种方式也能进行访问,但是不够方便

(*it) 表示的就是pos,pos用来访问struct内的x,y使用 .

(it) 表示pos的地址,地址访问里面的数据使用 ->

cpp 复制代码
struct Pos
	{
		int _row;
		int _col;

		Pos(int row = 0, int col = 0)
			:_row(row)
			,_col(col)
		{}
	};
	
	void test_list2()
	{
		list<Pos> lt1;
		lt1.push_back(Pos(100, 100));
		lt1.push_back(Pos(200, 200));
		lt1.push_back(Pos(300, 300));

		list<Pos>::iterator it = lt1.begin();
		while (it != lt1.end())
		{
			//cout << (*it)._row << ":" << (*it)._col << endl;
			// 为了可读性,省略了一个->
			cout << it->_row << ":" << it->_col << endl;
			//cout << it->->_row << ":" << it->->_col << endl;
			cout << it.operator->()->_row << ":" << it.operator->()->_col << endl;

			++it;
		}
		cout << endl;
	}

5.编译器的优化的解释:

具体的解释如下:

6.const变量的常性和临时变量的常性的区别

相当于是位于const和非const的一个中间态

7.const迭代器的生成

const迭代器,本身是可以修改的,但是指向的内容不能进行修改

因为const迭代器是值不能修改而指向能进行修改

cpp 复制代码
template<class T>
	class ListConstIterator
	{
		typedef ListNode<T> Node;
		typedef ListConstIterator<T> Self;

		Node* _node;
	public:
		ListConstIterator(Node* node)
			:_node(node)
		{}

		// ++it;
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;

			return tmp;
		}

		Self& operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;

			return tmp;
		}

		
		const T& operator*()
		{
			return _node->_data;
		}
        //const T* it  -->  修饰的是T,如int不能被修改,但是指向能修改(我们的迭代器还是能修改的)
		const T* operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}

		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
	};

然后把他们const的类型的加入到List类里面

cpp 复制代码
typedef ListConstIterator<T> const_iterator;

        const_iterator begin() const
        {
            // iterator it(_head->_next);
            // return it;
            return const_iterator(_head->_next);
        }

        const_iterator end() const
        {
            return const_iterator(_head);
        }

然后我们发现const_iterator类和iterator类只有返回类型不同

我们可以通过控制模板参数来进行实现代码的复用

代码如下:

cpp 复制代码
template<class T>
    struct ListNode
    {
        ListNode<T>* _next;
        ListNode<T>* _prev;

        T _data;
        ListNode(const T& data = T())
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(data)
        {

        }
    };

    template<class T,class Ref,class Ptr>
    struct ListIterator
    {
        typedef ListNode<T> Node;
        typedef ListIterator<T,Ref,Ptr> Self;

        Node* _node;

        ListIterator(Node* node)
            :_node(node)
        {

        }

        // ++it
        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }
        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }
        Self& operator++(int)
        {
            Self tmp(*this);
            _node = _node->_next;
            return tmp;
        }
        Self& operator--(int)
        {
            Self tmp(*this);
            _node = _node->_prev;
            return tmp;
        }


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

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

        bool operator!=(const Self& it)
        {
            return _node != it._node;
        }

        bool operator==(const Self& it)
        {
            return _node == it._node;
        }
    };

    template<class T>
    class List
    {
        typedef ListNode<T> Node;
    public:
        // typedef ListIterator<T> iterator;
        // typedef ListConstIterator<T> const_iterator;

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

        const_iterator begin() const
        {
            // iterator it(_head->_next);
            // return it;
            return const_iterator(_head->_next);
        }

        const_iterator end() const
        {
            return const_iterator(_head);
        }

        iterator begin()
        {
            // iterator it(_head->_next);
            // return it;
            return iterator(_head->_next);
        }

        iterator end()
        {
            return iterator(_head);
        }


        List()
        {
            head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }

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

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

    private:
        Node* _head;
    };

五.insert(),erase(),pop_back()和pop_front()的实现

1.insert()实现:

cpp 复制代码
void insert(iterator pos,const T& x)
        {
            Node* cur = pos._node;
            Node* newnode = new Node(x);
            Node* prev = cur->_prev;

            prev->_next = newnode;
            newnode->_prev = prev;
            newnode->_next = cur;
            cur->_prev = newnode;
        }

链表的迭代器,不会由迭代器失效,因为没有扩容的概念,但是库里面有返回值我们还是把返回值带上

cpp 复制代码
iterator insert(iterator pos,const T& x)
        {
            Node* cur = pos._node;
            Node* newnode = new Node(x);
            Node* prev = cur->_prev;

            prev->_next = newnode;
            newnode->_prev = prev;
            newnode->_next = cur;
            cur->_prev = newnode;
            return iterator(newnode);
        }

2.erase()实现:

cpp 复制代码
//erase后 pos失效,因为当前节点被删除了
        void erase(iterator pos)
        {
            Node* cur = pos._node;
            Node* newnode = new Node(x);
            Node* prev = cur->_prev;

            prev->_next = next;
            next->_prev = prev;
            delete cur;
        }

erase有迭代器失效,我们要返回删除元素的后面一个元素

cpp 复制代码
//erase后 pos失效,因为当前节点被删除了
        iterator erase(iterator pos)
        {
            assert(pos != end());
            Node* cur = pos._node;
            Node* newnode = new Node(x);
            Node* prev = cur->_prev;

            prev->_next = next;
            next->_prev = prev;
            delete cur;
            return iterator(next);
        }

3.pop_back()实现:

cpp 复制代码
void pop_back()
        {
            erase(--end());
        }

我们可以直接复用

4.pop_front()实现:

cpp 复制代码
void pop_front()
        {
            erase(begin());
        }

5.push_front()的实现

cpp 复制代码
void push_front(const T& x)
        {
            insert(begin(),x);
        }

测试代码:

cpp 复制代码
void test_list4()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		lt1.push_back(5);

		Func(lt1);

		lt1.push_front(10);
		lt1.push_front(20);
		lt1.push_front(30);

		Func(lt1);

		lt1.pop_front();
		lt1.pop_front();
		Func(lt1);

		lt1.pop_back();
		lt1.pop_back();
		Func(lt1);

		lt1.pop_back();
		lt1.pop_back();
		lt1.pop_back();
		lt1.pop_back();
		//lt1.pop_back();
		Func(lt1);
	}

六.list的拷贝

我们的迭代器想要的是浅拷贝,但是我们的list不能浅拷贝啊

1.析构函数的补充(浅拷贝会析构两次)

一般我们链表都是会实现clear()的,所以我们先实现clear(),然后我们在写析构函数的时候,进行复用

1.clear()

cpp 复制代码
void clear()
        {
            auto it = begin();
            while(it != end())
            {
                it = erase(it);
            }
        }

2.~list()

cpp 复制代码
~List()
        {
            clear();
            delete _head;
            _head = nullptr;
        }

3.重新默认构造

cpp 复制代码
void empty_init()
        {
            head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }

        List()
        {
            empty_init();
        }

将哨兵位的头节点单拎出来,变成一个head

4.拷贝构造

cpp 复制代码
//lt2(lt1)
        list(const list<T>& lt)
        {
            empty_init();
            for(auto e : lt)
            {
                push_back(e);
            }
        }

我们这里最好加上 const 和 & ,避免拷贝(这里的引用,是引用的lt里面的值)

cpp 复制代码
//lt2(lt1)
        list(const list<T>& lt)
        {
            empty_init();
            for(const auto& e : lt)
            {
                push_back(e);
            }
        }

2.operator=的重载

cpp 复制代码
list<T>& operator=(list<T> lt)
        {
            swap(_head,lt._head);
            return *this;
        }

3.initializer_list的构造

cpp 复制代码
//这里是两个指针,所以我们可以不传&
        list(initializer_list<T> il)
        {
            empty_init();
            for(const auto& e:il)
            {
                push_back(e);
            }
        }

vs的这种写法会更好,但是我们还是要代码写得规范

相关推荐
biubiubiu07064 小时前
VPS SSH密钥登录配置指南:告别密码,拥抱安全
linux
保持低旋律节奏4 小时前
C++ stack、queue栈和队列的使用——附加算法题
c++
初圣魔门首席弟子4 小时前
【C++ 学习】单词统计器:从 “代码乱炖” 到 “清晰可品” 的复习笔记
开发语言·c++
十五年专注C++开发5 小时前
CFF Explorer: 一款Windows PE 文件分析的好工具
c++·windows·microsoft
地平线开发者5 小时前
征程 6 | 征程 6 工具链如何支持 Matmul/Conv 双 int16 输入量化?
算法·自动驾驶
郝学胜-神的一滴5 小时前
计算机图形学中的光照模型:从基础到现代技术
开发语言·c++·程序人生·图形渲染
人生苦短,菜的抠脚5 小时前
Linux 内核IIO sensor驱动
linux·驱动开发
jz_ddk5 小时前
[LVGL] 从0开始,学LVGL:进阶应用与项目实战(上)
linux·信息可视化·嵌入式·gui·lvgl·界面设计
望获linux6 小时前
【实时Linux实战系列】Linux 内核的实时组调度(Real-Time Group Scheduling)
java·linux·服务器·前端·数据库·人工智能·深度学习