07 STL中list的用法和模拟实现


一、list介绍

  1. list是带头的双向循环链表,支持在任意位置的插入和删除,可以前后双向迭代。forward_list为单向链表,只支持往前迭代,不支持往后迭代
  2. list的插入效率比vector效率要高,因为不需要数据的移动
  3. list的缺陷是不支持数据的随机访问,因此,插入删除频繁的选用list,而需要随机访问的选择vector
  4. list的插入删除都是在当前位置进行,插入元素返回插入的节点的指针,删除节点则是返回节点删除前的下一个位置的节点的指针forward_list则只支持在单前位置的后面插入和删除元素

二、常用接口

2.1. 构造函数

默认构造

explicit list (const allocator_type& alloc = allocator_type());

构造一个int类型的空list:

list<int> lt1; //构造int类型的空容器

半缺省构造

explicit list (size_type n, const value_type& val = value_type(),
                const allocator_type& alloc = allocator_type());
list<int> lt2(10, 1); //构造含有10个1的int类型容器

拷贝构造

拷贝构造容器x的复制品:

list (const list& x);
list<int> lt3(lt2); //拷贝构造int类型的lt2容器的复制品

迭代器构造

template <class InputIterator>
  list (InputIterator first, InputIterator last,
         const allocator_type& alloc = allocator_type());
string s("hello world");
list<char> lt4(s.begin(),s.end()); //构造string对象某段区间的复制品

当然数组也可以,因为数组的数组名是一个指针,string迭代器也是一个指针:

char arr[] = "hello world";
int sz = sizeof(arr) / sizeof(arr[0]);
list<char> lt5(arr, arr + sz); //构造数组某段区间

2.2. 增删查改的接口

push_front和pop_front

push_front函数用于头插一个数据,pop_front函数用于头删一个数据。

push_back和pop_back

push_back函数用于尾插一个数据,pop_back函数用于尾删一个数据。
在这里插入图片描述

find

template <class InputIterator, class T>
   InputIterator find (InputIterator first, InputIterator last, const T& val);

find函数是头文件<algorithm>当中的一个函数,该函数在指定迭代器区间(左闭右开)寻找指定值的位置,并返回该位置的迭代器。如果没有找到这样的元素,函数返回 last 。

insert

//在指定迭代器position位置插入一个数。
iterator insert (iterator position, const value_type& val);
//在指定迭代器position位置插入n个值为val的数。
void insert (iterator position, size_type n, const value_type& val);
//在指定迭代器position位置插入一段迭代器区间(左闭右开)。
template <class InputIterator>
    void insert (iterator position, InputIterator first, InputIterator last);

erase

//删除迭代器posion位置的元素。
iterator erase (iterator position);
//删除指定迭代器区间(左闭右开)的所有元素。
iterator erase (iterator first, iterator last);

在这里插入图片描述

2.3. 迭代器

begin和end

begin()返回容器中第一个元素的正向迭代器,end()返回容器中最后一个元素的后一个位置的正向迭代器。

rbegin和rend

rbegin()返回容器中最后一个元素的反向迭代器,rend()返回容器中第一个元素的前一个位置的反向迭代器。
在这里插入图片描述

2.4. 元素访问方式

front和back

front()用于获取list容器当中的第一个元素,back()用于获取list容器当中的最后一个元素。
在这里插入图片描述

迭代器和范围for访问

在这里插入图片描述

2.5. 大小控制函数

size

size()函数用于获取当前容器当中的元素个数。值得注意的是,list并没有capacity()获取容器大小和reserve()修改容器大小的函数,因为list不是一个数组,空间并不连续,建立多个空节点也没有意义。

resize

void resize (size_type n, value_type val = value_type());

resize()规则:
 1、当所给值n大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0。
 2、当所给值小于容器当前的size时,会将多出来的节点释放,将size缩小到该值。

在这里插入图片描述

empty

empty()函数用于判断当前容器是否为空。

clear

clear()函数用于清空容器,清空后容器的size为0,即为空容器。
在这里插入图片描述

2.6. list的其他操作函数

sort

void sort();

template <class Compare>
void sort (Compare comp);

sort函数默认将容器当中的所有数据排为升序。如果要排降序则需要传入仿函数greater<data-type>()
在这里插入图片描述

这个函数和头文件<algorithm>中的sort是有区别的:
在这里插入图片描述

<algorithm>中的sort无法对list类型排序,因为这个sort在底层使用的是快速排序,快速排序要求容器迭代器必须是随机迭代器,因为要三数取中,而list的迭代器不支持随机访问。

swap

swap函数用于交换两个容器的内容。
在这里插入图片描述

splice

//将x容器拼接到当前容器迭代器position的位置。
void splice (iterator position, list& x);
//将x容器迭代器i位置的数据拼接到当前容器迭代器position的位置
void splice (iterator position, list& x, iterator i);
//将x容器迭代器[first,last)的数据拼接到当前容器迭代器position的位置
void splice (iterator position, list& x, iterator first, iterator last);
#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> lt1(4, 2);
	list<int> lt2(4, 6);
	lt1.splice(lt1.begin(), lt2); //将容器lt2拼接到容器lt1的开头
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl; //6 6 6 6 2 2 2 2 
	for (auto e : lt2)
	{
		cout << e << " ";//lt2容器空了
	}
	cout << endl;


	list<int> lt3(4, 2);
	list<int> lt4(4, 6);
	lt3.splice(lt3.begin(), lt4, lt4.begin()); //将容器lt4的第一个数据拼接到容器lt3的开头
	for (auto e : lt3)
	{
		cout << e << " ";
	}
	cout << endl; //6 2 2 2 2 
	for (auto e : lt4)
	{
		cout << e << " ";//666
	}
	cout << endl; 

	list<int> lt5(4, 2);
	list<int> lt6(4, 6);
	lt5.splice(lt5.begin(), lt6, lt6.begin(), --lt6.end()); //将容器lt6的指定迭代器区间内的数据拼接到容器lt5的开头
	for (auto e : lt5)
	{
		cout << e << " ";
	}
	cout << endl; //6 6 6 6 2 2 2 2
	for (auto e : lt6)
	{
		cout << e << " ";//6
	}
	cout << endl; 
	return 0;
}

在这里插入图片描述

注意: 容器当中被拼接到另一个容器的数据在原容器当中就不存在了。(实际上就是将链表当中的指定结点拼接到了另一个容器当中)

remove

void remove (const value_type& val);

remove函数用于删除容器当中特定值的元素。
在这里插入图片描述

remove_if

template <class Predicate>
  void remove_if (Predicate pred);

remove_if函数用于删除容器当中满足条件的元素。
在这里插入图片描述

unique

unique函数用于删除容器当中连续的重复元素。
如果想使用unique函数做到真正的去重,需要在去重前对容器内元素进行排序。 因为这个函数会将多个连续的重复元素保留一个。
在这里插入图片描述

如果不排序则是这样的结果:
在这里插入图片描述

merge

//将有序链表x的元素插入到当前链表中,插入之后x为空
 void merge (list& x);
 
//按照comp函数,为真则将有序链表x的元素插入到当前链表中
template <class Compare>
  void merge (list& x, Compare comp);

merge函数用于将一个有序list容器合并到另一个有序list容器当中,使得合并后的list容器仍然有序。(类似于归并排序)

#include <iostream>
#include <list>
bool mycomparison(double first, double second)
{
    return first < second;
}
int main()
{
    std::list<double> lt1, lt2;

    lt1.push_back(3.1);
    lt1.push_back(2.2);
    lt1.push_back(2.9);

    lt2.push_back(3.7);
    lt2.push_back(7.1);
    lt2.push_back(1.4);

    //将lt1和lt2排升序
    lt1.sort();
    lt2.sort();
    //将lt2插入到lt1中,插入后lt2为空
    lt1.merge(lt2);


    lt2.push_back(2.1);
    lt2.push_back(2.5);

    lt1.merge(lt2, mycomparison);

    std::cout << "插入之后的lt1:";
    for (std::list<double>::iterator it = lt1.begin(); it != lt1.end(); ++it)
        std::cout << *it << "  ";
    std::cout <<std::endl;

    return 0;
}

在这里插入图片描述

reverse

reverse()函数用于将容器当中元素的位置进行逆置。

在这里插入图片描述

assign

assign函数用于将新内容分配给容器,替换其当前内容,新内容的赋予方式有两种:

//将所给迭代器区间当中的内容分配给容器。
template <class InputIterator>
  void assign (InputIterator first, InputIterator last);
// 将n个值为val的数据分配给容器。
void assign (size_type n, const value_type& val);

在这里插入图片描述


三、list迭代器失效问题

list进行insert插入时,返回新插入数据的迭代器,原来的迭代器不会失效。
而进行删除时会发生失效,因为pos迭代器位置被删除了,对应的迭代器自然失效了。
在这里插入图片描述


四、list模拟实现

4.1. 接口总览

namespace hjl
{
	//模拟实现list当中的结点类
	template<class T>
	struct _list_node
	{
		_list_node(const T& val=T())
			:_val(val)
			,_prev(nullptr)
			,_next(nullptr)
		{}
		//成员变量
		T _val;                 //数据域
		_list_node<T>* _next;   //后继指针
		_list_node<T>* _prev;   //前驱指针
	};
	//模拟实现list迭代器
	template<class T,class Ref,class Ptr>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T,Ref,Ptr> self;

		node* _pnode;//一个指向结点的指针

		_list_iterator(node*pnode)
			:_pnode(pnode)
		{}
		Ref operator*();
		Ptr operator->();
		bool operator!=(const self& s)const;
		bool operator==(const self& s)const;
		//前置++和--  it++ -> it.operator++(&it)
		self& operator++();
		self& operator--();
		//后置++和-- it++ -> it.operator++(&it,0)
		self operator++(int);
		self operator--(int);

	};


	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;
		//默认构造函数
		list();
		//拷贝构造函数
		list(const list<T>& lt);
		//迭代器区间构造函数
		template<class InputIterator> //模板函数
		list(InputIterator first, InputIterator last);
		//重载=运算符
		list<T>& operator=(list<T> lt);
		//析构函数
		~list();
		
		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//访问容器相关函数
		T& front();
		T& back();
		const T& front() const;
		const T& back() const;
	
		//插入、删除函数
		void push_back(const T& x);
		void push_front(const T& x);
		void pop_front();
		void pop_back();
		void insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void swap(list<T>& lt);

		//修改大小的函数
		size_t size() const;
		void resize(size_t n, const T& val = T());
		void clear();
		bool empty() const;

		//交换容器的函数
		void swap(list<T>& lt);

}

由于list比vector和string复杂,因此需要实现三个类,分别为list节点类、迭代器类以及list类
接口中一共有三个类:

  1. 节点类
    list实际上是一个带头双向循环链表。因此首先需要构造一个节点类_list_node
  2. 迭代器类
    链表的存储不是连续线性的物理空间,而是逻辑上的线性表。因此,迭代器不能是原生指针,而应该构造一个迭代器类,又因为迭代器和原生指针的用法是一致的,所以迭代器类中的方法实现需要包含以下几点:(1).指针是可以解引用的,因此需要重载operator *(),(2).指针可以通过->访问其所指向的空间成员,因此需要重载operator ->()(3).迭代器可以 ++ --,因此需要重载这两个操作符(4)迭代器需要进行是否相等的比较,因此需要重载operator ==()operator !=()
  3. list类
    list是一个带头双向循环链表,在构造一个list对象时,直接通过new一个节点类来实现申请一个头结点,并让其前驱指针和后继指针都指向自己即可。在后续插入时,也通过new节点类的方式申请节点。

4.2. 节点类_list_node模拟实现

结点类的构造函数直接根据所给数据构造一个结点即可,构造出来的结点的数据域存储的就是所给数据,而前驱指针和后继指针均初始化为空指针即可。

template<class T>
struct _list_node
 {
	_list_node(const T& val=T())
		:_val(val)
		,_prev(nullptr)
		,_next(nullptr)
	{}
	//成员变量
	T _val;                 //数据域
	_list_node<T>* _next;   //后继指针
	_list_node<T>* _prev;   //前驱指针
};

4.3. 迭代器类的模拟实现

迭代器的意义是让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。对于list来说,其各个结点在内存当中的位置是随机的,并不是连续的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作。既然list的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符操作进行重载,使得我们可以用和string和vector当中的迭代器一样的方式使用list当中的迭代器。例如,当你使用list当中的迭代器进行自增操作时,实际上执行了p = p->next语句,只是使用者不知道罢了。

为什么需要三个模板参数

在list的模拟实现当中,我们重命名了两个迭代器类型,普通迭代器和const迭代器。

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

这里我们就可以看出,迭代器类的模板参数列表当中的Ref和Ptr分别代表的是引用类型和指针类型。

当我们使用普通迭代器时,编译器就会实例化出一个普通迭代器对象;当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象。

若该迭代器类不设计三个模板参数,那么就不能很好的区分普通迭代器和const迭代器。

template<class T, class Ref, class Ptr>

在这里插入图片描述

self是当前迭代器对象的类型,根据传入的模板不同,可以实例化出普通迭代器和const迭代器:

typedef _list_iterator<T, Ref, Ptr> self;

构造函数

迭代器类的成员变量就只有一个,那就是结点指针,其构造函数直接根据所给结点指针构造一个迭代器对象即可。

//构造函数
_list_iterator(node* pnode)
	:_pnode(pnode)
{}

前置++和–运算符的重载

前置++和前置–原本的作用是将数据自增和自减,然后返回自增自减后的数据。我们的目的是让结点指针的行为看起来更像普通指针,那么对于结点指针的前置++和前置–,我们就应该先让结点指针指向后一个节点或前一个节点,然后再返回“自增”或“自减”后的节点指针即可。

//前置++和--  it++ -> it.operator++(&it)
self& operator++()
{
	_pnode = _pnode->_next;
	return *this;
}
self& operator--()
{
	_pnode = _pnode->_prev;
	return *this;
}

后置++和–运算符的重载

//后置++和-- it++ -> it.operator++(&it,0)
self operator++(int)
{
	self tmp(*this);
	_pnode = _pnode->_next;
	return tmp;
}
self operator--(int)
{
	self tmp(*this);
	_pnode = _pnode->_prev;
	return tmp;
}

对于后置++和–来说,我们应该保存最初的指针,然后修改修改其指向,最后返回最初指针的指向。

==运算符的重载

当使用==运算符比较两个迭代器时,我们实际上想知道的是这两个迭代器是否是同一个位置的迭代器,也就是说,我们判断这两个迭代器当中的结点指针的指向是否相同即可。

bool operator==(const self& s) const
{
	return _pnode == s._pnode; //判断两个结点指针指向是否相同
}

!=运算符的重载

bool operator!=(const self& s) const
{
	return _pnode != s._pnode; //判断两个结点指针指向是否不同
}

*运算符的重载

当使用解引用操作符时,是想得到该位置的数据内容。因此,我们直接返回当前结点指针所指结点的数据即可,但是这里需要使用引用返回,因为解引用后可能需要对数据进行修改。

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

->运算符的重载

Ptr operator->()
{
	return &_pnode->_val;
}

当list容器当中的每个结点存储的不是内置类型,而是自定义类型,例如日期类,那么当我们拿到一个位置的迭代器时,我们可能会使用->运算符访问Date的成员:
在这里插入图片描述

4.4. list的模拟实现

构造和析构函数

默认构造函数
list()
{
	_head = new node;
	_head->_prev = _head;
	_head->_next = _head;
}

默认构造函数的作用主要是在创建list类的时候默认创建一个哨兵位节点。

拷贝构造函数
//拷贝构造函数
list(const list<T>& lt)
{
	_head = new node; //申请一个头结点
	//如果不改变头结点的next和prev,push_back时就会出错
	_head->_next = _head; //头结点的后继指针指向自己
	_head->_prev = _head; //头结点的前驱指针指向自己
	for (const auto& e : lt)
	{
		push_back(e); //将容器lt当中的数据一个个尾插到新构造的容器后面
	}
}

对于拷贝构造函数,我们先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面即可。

迭代器构造
template<class InputIterator> //模板函数
list(InputIterator first, InputIterator last)
{
	_head = new node; //申请一个头结点
	//如果不改变头结点的next和prev,push_back时就会出错
	_head->_next = _head; //头结点的后继指针指向自己
	_head->_prev = _head; //头结点的前驱指针指向自己
	//将迭代器区间在[first,last)的数据一个个尾插到容器当中
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}
析构函数
//析构函数
~list()
{
	clear(); //清理容器
	delete _head; //释放头结点
	_head = nullptr; //头指针置空
}

析构时,首先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空即可。

重载=运算符
list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
{
	swap(lt); //交换这两个对象
	return *this; //支持连续赋值
}

迭代器相关函数

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

begin()函数返回的是第一个有效数据的迭代器,end()函数返回的是最后一个有效数据的下一个位置的迭代器。
对于list这个带头双向循环链表来说,其第一个有效数据的迭代器就是使用头结点后一个结点的地址构造出来的迭代器,而其最后一个有效数据的下一个位置的迭代器就是使用头结点的地址构造出来的迭代器。

元素访问相关函数

T& front()
{
	return *begin(); //返回第一个有效数据的引用
}
T& back()
{
	return *(--end()); //返回最后一个有效数据的引用
}
const T& front() const
{
	return *begin(); //返回第一个有效数据的const引用
}
const T& back() const
{
	return *(--end()); //返回最后一个有效数据的const引用
}

front()back()函数分别用于获取第一个有效数据和最后一个有效数据,直接返回第一个有效数据和最后一个有效数据的引用。

增删查改的函数

insert
//插入函数
void insert(iterator pos, const T& x)
{
	assert(pos._pnode); //检测pos的合法性
	
	node* cur = pos._pnode; //迭代器pos处的节点指针
	node* prev = cur->_prev; //迭代器pos前一个位置的节点指针
	node* newnode = new node(x); //根据所给数据x构造一个待插入节点

	//建立newnode与cur之间的双向关系
	newnode->_next = cur;
	cur->_prev = newnode;
	//建立newnode与prev之间的双向关系
	newnode->_prev = prev;
	prev->_next = newnode;
}
erase
//删除函数
iterator erase(iterator pos)
{
	assert(pos._pnode); //检测pos的合法性
	assert(pos != end()); //删除的结点不能是哨兵位节点

	node* cur = pos._pnode; //迭代器pos处的节点指针
	node* prev = cur->_prev; //迭代器pos前一个位置的节点指针
	node* next = cur->_next; //迭代器pos后一个位置的节点指针

	delete cur; //释放cur节点

	//建立prev与next之间的双向关系
	prev->_next = next;
	next->_prev = prev;
	
	return iterator(next); //返回所给迭代器pos的下一个迭代器
}
push_back和pop_back
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;
	insert(end(), x);
}
void pop_back()
{
	erase(--end());
}
push_front和pop_front
void push_front(const T& x)
{
	insert(begin(), x);
}
void pop_front()
{
	erase(begin()); //删除第一个有效结点
}

其他操作函数

size

size函数用于获取当前容器当中的有效数据个数,因为list是链表,所以只能通过遍历的方式逐个统计有效数据的个数。

size_t size() const
{
	size_t sz = 0; //统计有效数据个数
	const_iterator it = begin(); //获取第一个有效数据的迭代器
	while (it != end()) //通过遍历统计有效数据个数
	{
		sz++;
		it++;
	}
	return sz; //返回有效数据个数
}
resize
void resize(size_t n, const T& val = T())
{
	iterator i = begin(); //获取第一个有效数据的迭代器
	size_t len = 0; //记录当前所遍历的数据个数
	while (len < n&&i != end())
	{
		len++;
		i++;
	}
	if (len == n) //说明容器当中的有效数据个数大于或是等于n
	{
		while (i != end()) //只保留前n个有效数据
		{
			i = erase(i); //每次删除后接收下一个数据的迭代器
		}
	}
	else //说明容器当中的有效数据个数小于n
	{
		while (len < n) //尾插数据为val的结点,直到容器当中的有效数据个数为n
		{
			push_back(val);
			len++;
		}
	}
}

这里实现resize的方法是,设置一个变量len,用于记录当前所遍历的数据个数,然后开始变量容器,在遍历过程中:

  • 当len大于或是等于n时遍历结束,此时说明该结点后的结点都应该被释放,将之后的结点释放即可。
  • 当容器遍历完毕时遍历结束,此时说明容器当中的有效数据个数小于n,则需要尾插结点,直到容器当中的有效数据个数为n时停止尾插即可。
clear
void clear()
{
	iterator it = begin();
	while (it != end()) //逐个删除结点,只保留头结点
	{
		it = erase(it);
	}
}

clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点即可。

empty
bool empty() const
{
	return begin() == end(); //判断是否只有头结点
}
swap
void swap(list<T>& lt)
{
	std::swap(_head, lt._head); //交换两个容器当中的头指针即可
}

附录

namespace hjl
{
	template<class T>
	struct _list_node
	{
		_list_node(const T& val=T())
			:_val(val)
			,_prev(nullptr)
			,_next(nullptr)
		{}
		//成员变量
		T _val;                 //数据域
		_list_node<T>* _next;   //后继指针
		_list_node<T>* _prev;   //前驱指针
	};

	template<class T,class Ref,class Ptr>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T,Ref,Ptr> self;

		node* _pnode;

		_list_iterator(node*pnode)
			:_pnode(pnode)
		{}

		Ref operator*()
		{
			return _pnode->_val;
		}
		Ptr operator->()
		{
			return &_pnode->_val;
		}
		bool operator!=(const self& s)const
		{
			return _pnode != s._pnode;
		}
		bool operator==(const self& s)const
		{
			return _pnode == s._pnode;
		}
		//前置++和--  it++ -> it.operator++(&it)
		self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}
		self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}
		//后置++和-- it++ -> it.operator++(&it,0)
		self operator++(int)
		{
			self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}
		self operator--(int)
		{
			self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}

	};


	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 begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}

		list()
		{
			_head = new node;
			_head->_prev = _head;
			_head->_next = _head;
		}
		//拷贝构造函数
		list(const list<T>& lt)
		{
			_head = new node; //申请一个头结点

			//如果不改变头结点的next和prev,push_back时就会出错
			_head->_next = _head; //头结点的后继指针指向自己
			_head->_prev = _head; //头结点的前驱指针指向自己
			for (const auto& e : lt)
			{
				push_back(e); //将容器lt当中的数据一个个尾插到新构造的容器后面
			}
		}

		template<class InputIterator> //模板函数
		list(InputIterator first, InputIterator last)
		{
			_head = new node; //申请一个头结点

			//如果不改变头结点的next和prev,push_back时就会出错
			_head->_next = _head; //头结点的后继指针指向自己
			_head->_prev = _head; //头结点的前驱指针指向自己
			//将迭代器区间在[first,last)的数据一个个尾插到容器当中
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}


		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		
		传统写法
		//list<T>& operator=(const list<T>& lt)
		//{
		//	if (this != &lt) //避免自己给自己赋值
		//	{
		//		clear(); //清空当前的容器
		//		for (const auto& e : lt)
		//		{
		//			push_back(e); //将容器lt当中的数据一个个尾插到链表后面
		//		}
		//	}
		//	return *this; //支持连续赋值
		//}
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head); //交换两个容器当中的头指针即可
		}

		//现代写法
		list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
		{
			swap(lt); //交换这两个对象
			return *this; //支持连续赋值
		}


		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;
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		void pop_front()
		{
			erase(begin()); //删除第一个有效结点
		}
		void pop_back()
		{
			erase(--end());
		}

		void insert(iterator pos, const T& x)
		{
			assert(pos._pnode); //检测pos的合法性
			node* cur = pos._pnode;
			node* prev = cur->_prev;
			node* newnode = new node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}
		iterator erase(iterator pos)
		{
			assert(pos._pnode); //检测pos的合法性
			assert(pos != end()); //删除的结点不能是头结点
			node* prev = pos._pnode->_prev;
			node* next = pos._pnode->_next;
			delete pos._pnode;
			prev->_next = next;
			next->_prev = prev;

			return iterator(next);
		}

		bool empty()
		{
			return begin() == end();
		}
		size_t size()
		{
			size_t sz = 0;
			iterator it = begin();
			while (it != end())
			{
				++sz;
				++it;
			}
			return sz;
		}

		void resize(size_t n, const T& val = T())
		{
			iterator i = begin(); //获取第一个有效数据的迭代器
			size_t len = 0; //记录当前所遍历的数据个数
			while (len < n && i != end())
			{
				len++;
				i++;
			}
			if (len == n) //说明容器当中的有效数据个数大于或是等于n
			{
				while (i != end()) //只保留前n个有效数据
				{
					i = erase(i); //每次删除后接收下一个数据的迭代器
				}
			}
			else //说明容器当中的有效数据个数小于n
			{
				while (len < n) //尾插数据为val的结点,直到容器当中的有效数据个数为n
				{
					push_back(val);
					len++;
				}
			}
		}


		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//it不能++,因为erase返回下一个位置
			}
		}

		T& front()
		{
			return *begin(); //返回第一个有效数据的引用
		}
		T& back()
		{
			return *(--end()); //返回最后一个有效数据的引用
		}
		const T& front() const
		{
			return *begin(); //返回第一个有效数据的const引用
		}
		const T& back() const
		{
			return *(--end()); //返回最后一个有效数据的const引用
		}
	private:
		node* _head;
	};
	
	template<class T>
	void Print(const list<T>& lt)
	{
		typename list<T>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			
			//*it += 1;
			std::cout << *it << " ";
			it++;
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值