STL-list类

  • 【本节目标】
  • 1.list的介绍和使用
  • 2.list的深度剖析和模拟实现
  • 3.list和vector的对比

目录

 1.list的介绍及使用

1.1list介绍

1.2list使用 

 1.2.1list的构造

1.2.1list_iterator的使用

 1.2.3list_capacity

 1.2.4list element access

 1.2.5 list modifiers

1.2.6 list Operations 

1.2.7 list迭代器失效问题

2.list模拟实现 

 2.1模拟实现list

1.构造结点

 2.构造框架

 3.迭代器的实现

 4.赋值运算符重载函数

5.构造const迭代器

6.反向迭代器

 7.insert函数

8.erase函数

 9.访问操作函数

10.容量相关的函数

11.list的拷贝构造,赋值重载,析构函数

3.完整代码

 test.cpp


 

 1.list的介绍及使用

1.1list介绍

list文档介绍

  • 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  • 2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
  • 3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  • 4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  • 5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

1.2list使用 

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展 的能力。以下为list中一些常见的重要接口。

 1.2.1list的构造

构造函数接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list
void TestList1()
{
    list<int> l1;                         // 构造空的l1
    list<int> l2(4, 100);                 // l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                    // 用l3拷贝构造l4

    // 以数组为迭代器区间构造l5
    int array[] = { 16,2,77,29 };
    list<int> l5(array, array + sizeof(array) / sizeof(int));

    // 列表格式初始化C++11
    list<int> l6{ 1,2,3,4,5 };

    // 用迭代器方式打印l5中的元素
    list<int>::iterator it = l5.begin();
    while (it != l5.end())
    {
        cout << *it << " ";
        ++it;
    }       
    cout << endl;

    // C++11范围for的方式遍历
    for (auto& e : l5)
        cout << e << " ";

    cout << endl;
}

1.2.1list_iterator的使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

list 的迭代器是将原生指针进行封装后的自定义类型,因为与 vector 和 string 等在物理结上是连续的,所以可以直接将原生指针作为迭代器,而 list 不行,因为 list 的存储方式是链式结构,是分散的,所以不能直接对原生指针进行++等操作,所以得对 list 的指针进行封装,下面模拟实现的时候会具体讲。

函数声明接口说明
begin+end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin+rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置

【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动 2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动 。
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动。
void TestList2()
{
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    // 使用正向迭代器正向list中的元素
    // list<int>::iterator it = l.begin();   // C++98中语法
    auto it = l.begin();                     // C++11之后推荐写法
    while (it != l.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 使用反向迭代器逆向打印list中的元素
    // list<int>::reverse_iterator rit = l.rbegin();
    auto rit = l.rbegin();
    while (rit != l.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
}

 1.2.3list_capacity

函数说明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

 1.2.4list element access

函数说明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

 1.2.5 list modifiers

函数说明接口说明
push_front在list首元素前插入值为val的元素
pop_fornt删除list中第一个元素
push_back在list尾部插入值为val的元素

pop_back

删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素
void TestList3()
{
    int array[] = { 1, 2, 3 };
    list<int> L(array, array + sizeof(array) / sizeof(array[0]));

    // 在list的尾部插入4,头部插入0
    L.push_back(4);
    L.push_front(0);
    PrintList(L);

    // 删除list尾部节点和头部节点
    L.pop_back();
    L.pop_front();
    PrintList(L);

    int array1[] = { 1, 2, 3 };
    list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));

    // 获取链表中第二个节点
    auto pos = ++L.begin();
    cout << *pos << endl;

    // 在pos前插入值为4的元素
    L.insert(pos, 4);
    PrintList(L);

    // 在pos前插入5个值为5的元素
    L.insert(pos, 5, 5);
    PrintList(L);

    // 在pos前插入[v.begin(), v.end)区间中的元素
    vector<int> v{ 7, 8, 9 };
    L.insert(pos, v.begin(), v.end());
    PrintList(L);

    // 删除pos位置上的元素
    L.erase(pos);
    PrintList(L);

    // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
    L.erase(L.begin(), L.end());
    PrintList(L);
}

1.2.6 list Operations 

函数说明接口说明
splice将 list1 中的元素转移到 list2中 (不是拷贝,而是搬移)
remove将 list 中所有 val 节点删掉
unique去掉 list 中的重复的元素 (需要先排序,配合 list 类自己的sort函数一起使用)
merge在相同类型且分别有序的情况下,将 list2 中的元素都搬移到 list1 中,且按大小进行排序 (list2变成空的)
sort排序 list 中的元素(效率低,不推荐)
reverse逆置 list 中的元素

 为什么 list 类中要重新实现sort函数,而不是使用算法库里面的?

理论上来说,算法库中实现的函数模板是通用的,但是因为算法库里面的一些函数是需要接受不同类型的迭代器的,而 list 类的迭代器属于双向迭代器,算法库里面sort函数底层使用的是快速排序,而快速排序要求容器迭代器接收的是随机迭代器,迭代器类型不符合,所以使用不了库里面的sort函数以及其他一些函数。

1.2.7 list迭代器失效问题

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为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.list模拟实现 

 2.1模拟实现list

1.构造结点

为了做到通用,显然在 C++ 中就得借助 模板 来实现通用!

namespace liren
{
    template <class T>
    struct list_node      //在这里我们将其设置为结构体struct,也可以设置为类,但是要将成员变量设为public
    {
    	list_node<T>* _next;
    	list_node<T>* _prev;
    	T _val;
    };
}

接下来我们对节点附上构造函数,因为 C++ 中,每次开辟一个对象就会调用其构造函数进行初始化,这就很方便!

  • 将数据给到val
  • 将_next和_prev置为空 
  • 这些任务我们可以写到 struct ListNode 的构造函数中,我们还可以设计成全缺省,给一个匿名对象 T() 。如此一来,如果没有指定初识值,它就会按模板类型去给对应的初始值了。
namespace liren
{
    template <class T>
    struct list_node      
    {
        list_node(const T& val = T())
            :_val(val),
        	_next(nullptr),
        	_prev(nullptr)
        {}
        
    	list_node<T>* _next;
    	list_node<T>* _prev;
    	T _val;
    };
}

 2.构造框架

  • 考虑到我们写的节点 list_node<T> 名字比较长,所以我们用 typedef 将其别名设为 Node。
  • 又考虑到可能后面的拷贝构造以及赋值重载也需要构造头节点,所以我们将构造头节点独立为一个函数 CreateHead()
  • 构建的是哨兵位头结点,我们需要先设计出_head,再将_prev,_next指向默认的位置

初始化头结点

//初始化头结点
void CreateHead()
{
    _head = new Node;
    _head->_next = _head;     //让前后指针都指向头节点本身
    _head->_prev = _head;
}
namespace liren
{
    template <class T>
    class list
    {
    public:
        typedef list_node<T> Node;    //重命名为Node
    public:
        /* 构造函数:初始化头结点 */
        list()
        {
            CreateHead();       //调用函数去构造头节点
        }
    private:
    	Node* _head;   // 头结点指针
    };
}

 3.迭代器的实现

list 和之前的 vector/string 不同,list 是链式结构,在物理空间上是不连续的,所以直接对 list 的原生指针进行操作,比如++操作,是没办法移到下一个 list 元素的位置的(除非运气非常好,刚刚好下个节点位置就在上一个节点位置的下个地址处)。

所以我们要对list的迭代器进行封装,并对运算符进行重载,这样就实现了迭代器。

首先,因为 operator++ 以及 operator-- 等函数操作都是返回 list_iterator 类型的,所以可以先将迭代器重命名为 Self,减少长度,以及后面我们需要传多个参数的模板时候,这个重命名就可以方便了很多事情,不需要我们去修改很多地方!

构造迭代器

template <class T>
struct list_iterator
{
    typedef list_node<T> Node;  //将节点重命名为Node
    typedef list_iterator<T> Self;  //将迭代器重命名为Self
	
    /* 迭代器的构造 */
    list_iterator(Node* node = nullptr)  //设置一个nullptr的缺省值
        :_node(node)
    {}
    
	Node* _node;
};

迭代器的拷贝构造、赋值重载和析构函数

  1. 我们使用迭代器去访问和修改 list 或者其他容器的元素,本质就是要得到该处元素的指针,那么我们就没必要去做深拷贝,直接做浅拷贝,将该处的地址浅拷贝给迭代器构造即可。即当前迭代器赋值给另一个迭代器是不需要深拷贝的,浅拷贝就可以。
  2. 迭代器里面虽然有一个节点的指针,但是该节点并不是由迭代器进行管理的,而是由 list 进行管理的,list 类的生命周期结束后会调用析构函数对节点进行释放的,而迭代器只是负责对该节点进行一些操作,并不需要对节点进行释放,也不允许对节点进行释放。

 4.赋值运算符重载函数

1.operator++和++operator

  • 前置++返回的是本体。
  • 后置++返回的不是本体,是一个临时的迭代器对象,不能用传引用返回。

前置++

/* ++it */
Self& operator++()
{
    _node = _node->_next;  // 让 _node指向下一个结点
    return *this;   // 返回++后的值
}

后置++

/* it++ */
Self operator++(int)
{
    Self tmp(*this);  //拷贝构造一个tmp存储原来的值
    _node = _node->_next;
    return tmp;  //返回一个临时对象
}

2.operator*和operator->

  1. operator* 的功能是 解引用,也就是获得该处的数据。
  2. operator-> 的功能是 返回该处数据的地址。 

operator*

T& operator*()
{
    return _node->_val; //返回结点的数据
}

operator->

T* operator->()
{
	return &(_node->val);   //返回_node->val的地址
}

3.operator+和operator-

因为list是链式结构,所以operator+是指向下一个结点的位置,operator是指向上一个结点的位置 这与++和--是一样的只不过区分前置后置

operator--

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

4.operator!=和operator== 

bool operator!=(const Self& s)  //使用传引用提高效率
{	
    return _node != s._node;
}
    
bool operator==(const Self& s)
{
    return _node == s._node;
}

5.构造const迭代器

 与普通对象的迭代器不同,const 对象迭代器只能读不能写,而通常迭代器进行修改和读取都是通过 operator*和operator-> 来实现,所以我们只需要实现这两个函数的 const 版本即可。


   template<class T>
class list {
	typedef list_node<T> Node;      // 重命名为Node
public:
	/* 迭代器 */
	typedef list_iterator<T> iterator;
	typedef const_list_iterator<T> const_iterator;  // 重命名const迭代器


	// 👇 const 迭代器的 begin 和 end
	const_iterator begin() const {
		return const_iterator(_pHead->_next);  // 调用 const_list_iterator<T>
	}
	const_iterator end() const {
		return const_iterator(_pHead);
	}

	iterator begin() {   
		return iterator(_pHead->_next);  // 调用 list_iterator<T>
	}
	iterator end() {
		return iterator(_pHead);
	}

...
}

	const_list_iterator(Node* x)
		: _node(x)
	{}

	const T& operator*() const
	{
		return _node->_val;       // 返回结点的数据
	}

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


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

	Self operator++(int) 
 {
		_const_list_iterator<T> tmp(*this);   
		_node = _node->_next;
		return tmp;
	}

//....只展示部分函数,其他都是一样的
}

 这样看起来非常的冗余,让我们来看看原码

 这个用法的意思就是在定义 template 模板时候增加两个参数:一个是 Ref(reference引用的意思),一个是 Ptr(pointer)

这样的话,我们 operator 的返回值* 我们不要用 T&了,我们改成 Ref;而 operator-> 的返回值我们改成 Ptr

 

template <class T, class Ref, class Ptr>
struct list_iterator
{
	typedef _list_node<T> Node;  //将节点重命名为Node
	typedef list_iterator<T, Ref, Ptr> Self;  //将迭代器重命名为Self

	/* 迭代器的构造 */
	list_iterator(Node* node = nullptr)  //设置一个nullptr的缺省值
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_val; //返回结点的数据
	}

	Ptr operator->()
	{
		return &(_node->val);   //返回_node->val的地址
	}
 
    //......其他函数还是一样不变,前提是将迭代器重命名为了Self
}

6.反向迭代器

通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭 代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。

namespace liren
{
    //反向迭代器的自主实现版本
	template <class iterator, class Ref, class Ptr>
	struct reverse_iterator
	{
		// Iterator是哪个容器的迭代器,reverse_iterator<Iterator>就可以适配出哪个容器的反向迭代器。复用的体现

		typedef reverse_iterator<iterator, Ref, Ptr> Self; //将反向迭代器重命名为Self

		reverse_iterator(iterator it)
			:_it(it)
		{}

		Ref operator*()
		{
			iterator prev(_it);
			return *(--prev);  // 注意结构上的错位
		}

		Ptr operator->()
		{
			iterator prev = _it;
			return &(operator*());  //复用上面的operator*()
		}

		Self& operator++()  //与正向迭代器相反,反向迭代器的++其实是正向迭代器的--,反之也成立
		{
			--_it;
			return *this;
		}
		Self operator++(int)
		{
			Self tmp = _it;
			--_it;
			return tmp;
		}

		Self& operator--()
		{
			++_it;
			return *this;
		}
		Self operator--(int)
		{
			Self tmp = _it;
			++_it;
			return tmp;
		}

		bool operator!=(const Self& s) const
		{
			return _it != s._it;
		}

		iterator _it;
	};
    
    template <class T>
	class list
	{
	public:
		typedef list_node<T> Node;
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

		//记得这里第一个参数是要传iterator,而不是T,因为我们是通过iterator来包装反向迭代器的
		typedef reverse_iterator<iterator, T&, T*> re_iterator; 
		typedef reverse_iterator<iterator, const T&, const T*> const_re_iterator;
	public:
        
		re_iterator rbegin()
		{
			return re_iterator(end());
		}
		const_re_iterator rbegin() const
		{
			return const_re_iterator(end());
		}

		re_iterator rend()
		{
			return re_iterator(begin());
		}
		const_re_iterator rend() const
		{
			return const_re_iterator(begin());
		}
		
       …………
           
	private:
		Node* _head;
	};
}

 7.insert函数

在 pos 位置前插入,我们通过 pos 去找到前驱 prev,之后创建新结点,再进行 “缝合” 操作,比较简单。

// 在pos位置前插入值为val的节点
void insert(iterator pos, const T& x)
{
    Node* newnode = new Node(x); //开辟新节点
    Node* prev = pos._node;

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

有了insert函数,我们就可以赋用insert函数来实现push_back函数 

//尾插
void push_back(const T& x)
{
    //第一种自己实现
    /*Node* newnode = new Node(x);

			_head->_prev->_next = newnode;
			newnode->_prev = _head->_prev;
			newnode->_next = _head;
			_head->_prev = newnode;*/

    //第二种复用insert
    insert(end(), x); //在end(pHead)前插入,即尾插
}

//头插
void push_front(const T& x)
{
    insert(begin(), x);  // 在begin(头结点的下一个结点)前插入,即头插
}

8.erase函数

删除 pos 位置结点,步骤如下:

① 找到 pos 的前驱和后继

② 释放 pos 位置结点

③ 将已删除的 pos 结点的前驱和后继 “缝合”

// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos) 
{
    assert(pos != end());   // 防止头结点被删除

    Node* cur = pos._node;   // 找到pos位置的结点
    Node* cur_prev = cur->_prev;   // 找到pos的前驱
    Node* cur_next = cur->_next;   // 找到pos的后继

    // 删除cur
    delete cur;

    // 缝合:  cur_prev <-> cur(删) <-> cur_next
    cur_prev->_next = cur_next;
    cur_next->_prev = cur_prev;

    return iterator(cur_next);
}

 由于 erase 函数删掉了一个节点后,这个迭代器肯定失效了!为了让迭代器不失去作用,我们 erase 函数返回下一个节点的迭代器。

实现了erase函数,我们就能实现头删和尾删了

/* 尾删 */
void pop_back() 
{
	erase(--end());  // 删除最后一个元素,即尾结点
}

/* 头删 */
void pop_front() 
{
	erase(begin());  // 删除头结点的下一个结点(即begin位置的结点)
}

 9.访问操作函数

// List的元素访问操作
// 注意:List不支持operator[]
T& front()
{
    return _head->_next->_val;
}

const T& front()const
{
    return _head->_next->_val;
}

T& back()
{
    return _head->_prev->_val;
}

const T& back()const
{
    return _head->_prev->_val;
}

10.容量相关的函数

//size函数
size_t size() const
{
    Node* cur = _head->_next;
    size_t count = 0;
    while (cur != _head)
    {
        count++;
        cur = cur->_next;
    }
    return count;
}

//empty函数
bool empty() const
{
    return _head == _head->_next;
}

//resize函数
void resize(size_t n, const T& val = T())
{
    size_t sz = size();
    if (n <= sz)  //小于list长度
    {
        while (n <= sz)
        {
            pop_back();
            sz--;
        }
    }
    else  //大于list长度
    {
        while (sz < n)
        {
            push_back(x);
            sz++;
        }
    }
}

11.list的拷贝构造,赋值重载,析构函数

1.析构函数

我们需要把节点释放掉,防止内存泄漏

这里我们先讲一下clear函数

我们后面在实现析构和拷贝构造的时候,可能需要用到清理 list 的函数,所以我们就做了 clear 函数“负责清理有效元素个数"。

我们可以自己实现,但是没必要,因为可以用 erase 进行复用,但是注意,要传一个 iterator 给erase函数而不是传节点!

void clear()
{
    iterator cur = begin(); //注意要传迭代器,因为等会erase只接收迭代器
    while (cur != end())
    {
        erase(cur++); //因为erase完迭代器会失效,所以要重新赋值
    }
}

 析构函数代码如下

~list()
{
    clear(); //先将有效元素释放掉

    //然后将头节点也释放掉,并置空
    delete _head;
    _head = nullptr;
}

这样子我们就把析构函数写完啦!

但是这里就出现了问题,那就是拷贝构造或者赋值的浅拷贝问题引发的析构问题浅拷贝只是将指针拷贝过去,两个 list 指向的是同一块空间,最后释放的时候,同一块空间被析构了两次,这样子程序就报错了!

所以我们必须自己实现拷贝构造以及赋值重载!

2.拷贝构造

与 vector、string 类似,list 的拷贝构造也有传统写法现代写法之分!

同样的,深拷贝就是重新开辟一个空间,然后将原空间的数据复制过来

传统写法

//传统写法的拷贝构造
list(const list<T>& l)
{
    //创建新的头节点(该函数在上面实现了)
    CreateHead();

    for (auto e : l)
    {
        push_back(e);
    }
}

  现代写法

//拷贝构造的现代写法
template <class Iterator>
list(Iterator first, Iterator last)
{
    CreateHead();
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}

list(const list<T>& l)
{
    CreateHead();

    // 用l中的元素构造临时的temp,然后与当前对象交换
    list<T> temp(l.begin(), l.end());
    this->swap(temp);
}

3.赋值重载

传统写法

list<T>& operator=(list<T> L)
{
	if (this != &L) //判断一下是否为自己给自己赋值,若是的话则没必要赋值
    {
		clear();
		for (auto e : L) 
        {
			push_back(e);
		}
	}
	return *this;
}

现代写法 

list<T>& operator=(list<T> l) //接收的不是引用,而只是一个值
{
    this->swap(l);
    return *this;
}

3.完整代码

list.h

namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		list_node(const T& x = T())
			:_data(x)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};

	// T T& T*
	// T cosnt T& const T*
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{}

		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;
		}

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

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

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		bool operator==(const self& s)
		{
			return _node == s._node;
		}
	};


	//template<class T>
	//struct __list_const_iterator
	//{
	//	typedef list_node<T> Node;
	//	typedef __list_const_iterator<T> self;
	//	Node* _node;

	//	__list_const_iterator(Node* node)
	//		:_node(node)
	//	{}

	//	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;
	//	}

	//	// *it = 10
	//	const T& operator*()
	//	{
	//		return _node->_data;
	//	}

	//	// it->a1 = 10
	//	const T* operator->()
	//	{
	//		return &_node->_data;
	//	}

	//	bool operator!=(const self& s)
	//	{
	//		return _node != s._node;
	//	}

	//	bool operator==(const self& s)
	//	{
	//		return _node == s._node;
	//	}
	//};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		//typedef __list_const_iterator<T> const_iterator;

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

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

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

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

		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			_size = 0;
		}

		list()
		{
			empty_init();
		}

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

		// lt3 = lt1
		/*list<int>& operator=(const list<int>& lt)
		{
			if (this != &lt)
			{
				clear();
				for (auto e : lt)
				{
					push_back(e);
				}
			}

			return *this;
		}*/

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		// lt3 = lt1
		list<int>& operator=(list<int> lt)
		{
			swap(lt);

			return *this;
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_front()
		{
			erase(begin());
		}

		void pop_back()
		{
			erase(--end());
		}

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(x);

			Node* prev = cur->_prev;

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

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

			++_size;

			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

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

			--_size;

			return iterator(next);
		}

		size_t size()
		{
			return _size;
		}

	private:
		Node* _head;
		size_t _size;
	};

	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);

		// 封装屏蔽底层差异和实现细节
		// 提供统一的访问修改遍历方式
		list<int>::iterator it = lt.begin();
		while (it != lt.end())
		{
			*it += 20;

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

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

		/*set<int> s;
		s.insert(1);
		s.insert(3);
		s.insert(2);
		s.insert(4);

		set<int>::iterator sit = s.begin();
		while (sit != s.end())
		{
			cout << *sit << " ";
			++sit;
		}
		cout << endl;*/
	}

	void test_list2()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);

		list<int> lt1(lt);

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

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

		list<int> lt2;
		lt2.push_back(10);
		lt2.push_back(200);
		lt2.push_back(30);
		lt2.push_back(40);
		lt2.push_back(50);

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

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

	struct AA
	{
		AA(int a1 = 0, int a2 = 0)
			:_a1(a1)
			,_a2(a2)
		{}

		int _a1;
		int _a2;
	};

	void test_list3()
	{
		list<AA> lt;
		lt.push_back(AA(1, 1));
		lt.push_back(AA(2, 2));
		lt.push_back(AA(3, 3));

		list<AA>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1<<" "<<(*it)._a2 << endl;
			cout << it->_a1 << " " << it->_a2 << endl;
			cout << it.operator->()->_a1 << " " << it.operator->()->_a1 << endl;


			++it;
		}
		cout << endl;

		int* p = new int;
		*p = 1;

		AA* ptr = new AA;
		ptr->_a1 = 1;

	}

	//void print_list(const list<int>& lt)
	//{
	//	list<int>::const_iterator it = lt.begin();
	//	while (it != lt.end())
	//	{
	//		//*it = 10;
	//		cout << *it << " ";
	//		++it;
	//	}
	//	cout << endl;

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


	//void print_list(const list<int>& lt)
	//{
	//	list<int>::const_iterator it = lt.begin();
	//	while (it != lt.end())
	//	{
	//		//*it = 10;
	//		cout << *it << " ";
	//		++it;
	//	}
	//	cout << endl;

	///*	for (auto e : lt)
	//	{
	//		cout << e << " ";
	//	}
	//	cout << endl;*/
	//}

	// 实例化
	//template<typename T>
	template<class T>
	//void print_list(const list<T>& lt)
	//{
	//	// list<T>未实例化的类模板,编译器不能去他里面去找
	//	// 编译器就无法list<T>::const_iterator是内嵌类型,还是静态成员变量
	//	// 前面加一个typename就是告诉编译器,这里是一个类型,等list<T>实例化
	//	// 再去类里面去取
	//	typename list<T>::const_iterator it = lt.begin();
	//	while (it != lt.end())
	//	{
	//		cout << *it << " ";
	//		++it;
	//	}
	//	cout << endl;
	//}
	
	// 10:20继续
	// 模板(泛型编程)本质,本来应该由我们干的活交给编译器
	template<typename Container>
	void print_container(const Container& con)
	{
		typename Container::const_iterator it = con.begin();
		while (it != con.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_list4()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);
		//print_list(lt);
		print_container(lt);

		list<string> lt1;
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		lt1.push_back("1111111111111");
		//print_list(lt1);
		print_container(lt1);

		vector<string> v;
		v.push_back("222222222222222222222");
		v.push_back("222222222222222222222");
		v.push_back("222222222222222222222");
		v.push_back("222222222222222222222");
		//print_list(v);
		print_container(v);
	}
}

 

test.cpp

int main()
{
	//test_list5();

	//test_op();


	bit::test_list4();

	return 0;
}

 

 

 

 

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以给您讲解一下如何模拟STLlistList是一个双向链表,我们可以通过自己实现节点和链表来模拟这个数据结构。 首先,我们需要定义一个节点,包含前驱节点指针、后继节点指针、以及存储数据的成员变量。代码如下: ``` template <typename T> class ListNode { public: T data; ListNode* prev; ListNode* next; ListNode(T data) : data(data), prev(nullptr), next(nullptr) {} }; ``` 接着,我们定义链表,包含头节点指针和尾节点指针,以及一些常用的操作方法,如插入、删除、查找等。代码如下: ``` template <typename T> class List { public: List() : head(nullptr), tail(nullptr), size(0) {} ~List() { clear(); } void insert(T data) { ListNode<T>* node = new ListNode<T>(data); if (head == nullptr) { head = node; tail = node; } else { tail->next = node; node->prev = tail; tail = node; } size++; } void remove(T data) { ListNode<T>* node = head; while (node != nullptr) { if (node->data == data) { if (node == head) { head = node->next; if (head != nullptr) { head->prev = nullptr; } } else if (node == tail) { tail = node->prev; if (tail != nullptr) { tail->next = nullptr; } } else { node->prev->next = node->next; node->next->prev = node->prev; } delete node; size--; return; } node = node->next; } } ListNode<T>* find(T data) { ListNode<T>* node = head; while (node != nullptr) { if (node->data == data) { return node; } node = node->next; } return nullptr; } void clear() { ListNode<T>* node = head; while (node != nullptr) { ListNode<T>* next = node->next; delete node; node = next; } head = nullptr; tail = nullptr; size = 0; } int getSize() const { return size; } private: ListNode<T>* head; ListNode<T>* tail; int size; }; ``` 这样,我们就实现了一个简单的模拟STLlist。您可以通过调用insert、remove、find等方法来操作链表中的元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值