list使用及底层模拟实现

目录

一.list的使用

排序sort

去重unique

remove按值删除

remove_if

splice

merge合并

二.模拟实现

1.成员变量及节点的实现

2.普通迭代器

成员变量

解引用operator*

operator->

前置++和前置--

后置++和后置--

等于与不等于重载

begin()迭代器

end()迭代器

3.const迭代器

4.插入删除

insert

erase

erase范围删除

push_back插入

push_back删除

push_front头插

pop_front头删

插入删除测试

5.构造及析构函数

默认构造

填充构造函数

区间构造函数

​编辑

initializer_list构造

析构函数和clear函数

6.拷贝构造及operator赋值

7.其它函数的实现

完整实现代码如下


一.list的使用

因为list的使用与vector有很多相似之处,所以只讲解不同的使用和在vector当中有疏漏的

排序sort

vector是没有自己的sort函数,一般是使用algorithm库里的sort函数来排序的,但是库里的排序只支持随机迭代器不支持双向迭代器,list是双向迭代器所以不能用库里的排序函数。双向迭代器与随机迭代器的区别在于双向迭代器只能一下一下移动,不支持迭代器+偏移量。而随机迭代器可以随意加减偏移量来移动。

虽然list不支持算法库里的sort,但是它的成员里面实现了一个它自己的sort函数,只不过它自带的sort排序函数不是传迭代器而是和库里面一样传仿函数函数或者什么都不传

防函数的使用greater<int>从大到下排序

默认什么参数不传是less<int>,也就是默认从小到大排序,也可以写出来传参

但是值得注意的是list的排序其实很少使用,因为vector的排序效率比它高很多。vector底层用的是快排,list底层用的是归并排序,虽然时间复杂度都是nlogn,但是这只是一个最差情况下的结果,只能说明他们是一个量级。但是2n和n的时间复杂度也都是On这个量级,但是n明显比2n的时间复杂度快

来举个例子证明一下

随机生成一百万个数,分别放到vector和list当中排序来看看前面消耗时间

#include<iostream>
using namespace std;
#include<list>
#include<vector>
#include<algorithm>
#define N 1000000

int main()
{
	vector<int> tamp2;
	list<int> tamp;
	srand(static_cast<unsigned int>(std::time(0)));
	for (int i=0;i<N;i++)
	{
		int a = rand()%N+ i;
		tamp.push_back(a);
		tamp2.push_back(a);
	}
	auto begin1=clock();
	tamp.sort();
	auto end1 = clock();
	cout << "list排序时间:"<<end1 - begin1 << endl;


	//vector排序
	auto begin2 = clock();
	sort(tamp2.begin(),tamp2.end());
	auto end2 = clock();
	cout << "vector排序时间:" << end2 - begin2 << endl;
}

从上图可以看出来一百万个数据vector比list快了将近3倍,用list的数据构造vector然后再排序来看看时间消耗

下图vector不只用list数据构造了自己排完序再用vector数据构造tamp3,然后把tamp3的数据复制到tamp当中,这样依旧比用list自带的排序快。

去重unique

算法库里面也有去重,但是那个去重不能完全叫做去重,它会将相邻的重复元素挪动到容器的末尾,并返回一个指向最后一个被去除元素后面位置的迭代器。它不会删除元素,只是移动一些元素。所以要配合erase来完成去重

举个例子

而list的unique是直接去重

remove按值删除

在vector里erase单个值删除是通过给erase传迭代器位置来完成删除,remove是直接按值删除,如果有多个重复的值是全删

remove_if

按条件移除,这个条件是用函数来实现

库里面也要remove_if但是库里面的和unique类似,会把符合条件的值挪到后面去,所以要和erase一起来使用

splice

虽然翻译是拼接,但是它实际上功能是剪切

复制代码
void splice (iterator position, list& x);把一个list对象东西全都剪切到另一个list对象当中,被剪切的对象变成空

void splice (iterator position, list& x, iterator i);把list指定迭代器指向的东西剪切到另一个对象当中

把tamp的1剪切到tamp2当中,原先的1变相地相当于删除

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| void splice (iterator position, list& x, iterator first, iterator last);这个就是把迭代器构成区间里的东西剪切过去 |

其实这个剪切也剪切自己的元素

这个剪切说实话很少用到,因为不支持随机迭代器,所以控制区间范围非常困难

merge合并

merge用于合并两个已排序的链表。此函数会将当前链表与另一个链表合并,结果仍然保持排序。需要注意的是,这两个链表都必须是已排序的。

如果有一个是无序的,那么就直接assert崩溃

同时它这个合并并排序也支持降序,同样是使用仿函数,但是前提是两个合并的链表也必须先是降序的然后才能使用greater仿函数合并完了后保持整体降序

二.模拟实现

1.成员变量及节点的实现

同样是模拟实现,vector的成员变量是用三个迭代器start,finish,end_of_storage来构成的,而string是通过char* _str;//指向存放字符串的空间,size_t _size;//实际有效字符结尾,size_t _capacity;//实际空间容量来实现的,vector和string都是顺序存储,所以通过一个开始的地址就可以找到后面的所有元素,所以可以通过start和char* _str来分配管理空间就可以了。但是list链表是链式存储结构,存储是不连续间断的,所以单靠一个指针管理开辟空间是不够的(因为开一段空间压根就不够,list是一个个间断空间构成的),因此直接把节点也封装成类方便一点(开辟节点很多都是重复操作,而且内容也差不多,封装成类直接传值构造就可以生成不同的节点对象)。

库里面的list容器是带头双向循环链表,所以Node节点类要有prev(前向指针),next(后向指针)。

		template<class T>
		struct Node
		{
			Node(const T& val = T())
				:prev(nullptr)
				, next(nullptr)
				, _data(val)
			{

			}
			Node<T>* prev;
			Node<T>* next;
			T _data;
		};

再回到list类,带头双向循环链表一般都设置一个_head来进行管理整个链表,list只需要构造出一个头结点就可以进行管理整个链表。因为双向循环链表,所以整个链表为空时_head节点的prev和next都指向自己,后序有节点插入只需要改变prev和next的指向就可以了。head实际上是不存储任何有效数据的,所以构造函数传空过去就可以了。在Node节点的构造函数中const T& val = T()写成这种缺省T()会去调用相对应类型的空值构造,如果是int类型那么默认就是0

2.普通迭代器

vector是顺序存储,所以可以直接用指针封装成迭代器,解引用和++都符合顺序存储的规则

比如下面的例子,这是一个int数组,ptr指向开头第一个元素位置,ret指向第二个元素。因为是存储int类型所以每个格子占四个字节,ptr++实际上是加了4个字节,正好和ret指向同一个位置,解引用就取到了第二个元素的值2

但是list存储是链式存储每个存储空间是不连续的,++不一定能找到第二个元素,在课表上可能会画成next链接第二个节点,但这只是方便理解的表达。因为第二个节点空间和第一个节点空间间隔有可能是4个字节也有可能是16个字节,这个没法控制

此时ptr++,依旧是加4个字节,但是因为不是顺序存储,所以很难保证加了4个字节后可以正好到第二个元素。此时解引用有可能得到的是一个随机值

链表怎么找到第二个元素呢,一般链表节点都会存储下一个节点的位置也就是(next指针),所以直接让ptr=ptr->next,然后解引用就可以找到第二个元素的值了。

但是此时就有一个问题了,一般迭代器都会封装成一样的名字iterator,iterator++是可以直接找到第二个元素的。但是如果直接把节点指针ptr封装成iterator的话是没法做到相同功能的,因为ptr直接++压根就不一定能找到第二个元素,要满足一定的规则。所以可以写成一个函数,但是iterator很难直接调用调函数,而且样式也绝对不是iterator++就可以直接解决问题。所以可以把iterator封装成类,因为运算符可以重载++,而且可以重新书写这个++重载函数的规则。因为也要用到节点的next指针和prev指针,所以iterator成员变量也要包含Node节点

cpp 复制代码
	template<class T>
	struct iteratorlist
	{
		 Node<T>* _node;

		iteratorlist(const Node<T> *node):_node(node)
		{

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

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

		iteratorlist operator++()
		{
			_node = _node->next;
			return *this;
		}
		
		iteratorlist operator--()
		{
			_node = _node->prev;
			return *this;
		}

		iteratorlist operator++(int)
		{
			Node<T>* tamp(*this);
			_node = _node->next;
			return tamp;
		}

		iteratorlist operator--(int)
		{
			Node<T>* tamp(*this);
			_node = _node->prev;
			return tamp;
		}

		bool operator==(const iteratorlist<T>& tamp)
		{
			return _node ==tamp._node;
		}

		bool operator!=(const iteratorlist<T>& tamp)
		{
			return _node !=tamp._node;
		}

	};

让我来挨个解释一下这段代码

成员变量

Node<T>* _node; 这个就是iterator的唯一成员变量,虽然已经确定了迭代器要封装成类了,但是还是要使用到节点的next和prev。为什么一直 Node<T>* _node这么写呢,而不是直接Node *_node,这是Node里面已经写成了模版类,如果不指定生成什么类型编译器就不知道Node构造函数里T到底是什么类型,所以要一直写明T,告诉编译器生成的是什么类型节点

解引用operator*

T& operator*()

{

return _node->_data;

}

重载迭代器的解引用,其实就是得到当前指向迭代器节点的数据,所以直接返回_node->data就可以

operator->

T* operator->()

{

return &(_node->_data);

}

在C语言结构体里面有两种访问结构体成员的方式,一种是实例化出对象,让对象通过点操作符访问结构体。另一种是定义一个结构体指针,让指针通过箭头操作符去访问结构体成员。其实就相当于让指针解引用,然后再去通过点操作符访问成员

比如下面的例子里直接实例化出来的对象tmp可以通过点操作符访问成员a,保存了它地址的ptr也可以通过箭头访问操作符去访问成员。其实就相当于(*ptr).a

放到list容器里operator箭头到底是有什么用的呢, 已知的是箭头操作符肯定是指针(地址)去用的,但是从下面的例子可以看出来如果是内置类型直接用箭头打印会出错

但是用自定义类型的list就可以用operator箭头了,结合上面的结构体的例子可以得出推论,list的operator箭头其实就相当于取一个结构体的指针出来,然后通过这个指针再用->箭头访问符去访问自定义类型的成员。下面例子中的i->b其实就相当于it.operator->()->b,也相当于(&节点)->b。所以operator要写成&(_node->_data),也就是把节点数据取地址然后就能通过箭头操作符去访问自定义类型里面的成员了。

为什么取的node节点里面_data的地址而不是直接node的地址呢,因为如果是箭头操作符本身就是为了取数据方便,如果直接取node的指针,还得在走一次箭头操作符访问到_data然后才去访问成员。这个_data的类型是T,如果是自定义类型那么_data是会存自定义类型的所有成员数据的,所以只取_data的地址就可以访问到任意成员了,可以简化操作

前置++和前置--

这个就很简单了,直接让节点指向next和prev就可以了

cpp 复制代码
iteratorlist operator++(int)
		{
			Node<T>* tamp(*this);
			_node = _node->next;
			return tamp;
		}

		iteratorlist operator--(int)
		{
			Node<T>* tamp(*this);
			_node = _node->prev;
			return tamp;
		}

后置++和后置--

后置操作符重载,因为规则是必须在operator之后加操作符,所以前置和后置运算符重载都是在operator后面的,这样就取分不开了,所以后置一律在这个运算符重载的函数参数那里填个int, iteratorlist operator--(int),以便于和前置运算符取分开来

cpp 复制代码
	iteratorlist operator++(int)
	{
		iteratorlist<T> tamp(*this);
		_node = _node->next;
		return tamp;
	}

	iteratorlist operator--(int)
	{
		iteratorlist<T> tamp(*this);
		_node = _node->prev;
		return tamp;
	}

与前置相比,后置的++和--区别在于,后置的是先使用这个数据,然后再把这个数据进行++

比如下面的例子中,a=b++,此时a得到的还是b加之前的数据2,而b在给完a之后再去加加,所以是3

回到list中,怎么实现迭代器的后置操作符呢,其实就是用当前迭代器实例化的对象拷贝构造一个新迭代器对象iteratorlist<T> tamp(*this);,然后把当前数据进行处理++,--,把新的节点tamp返回就可以了,这样对面收到的依旧是进行++之前的数据值了。这里注意的地方是此时必须使用传值返回而不是引用返回,因为引用返回是取别名,但是tamp生命周期只在这个函数里,处理函数就直接销毁了。如果依旧是引用tamp返回,就成了野引用了

等于与不等于重载

节点的相等不能只是值相等,因为不同的节点可以存不同的值,所以要直接比较节点

cpp 复制代码
	bool operator==(const iteratorlist<T>& tamp)
		{
			return _node ==tamp._node;
		}

		bool operator!=(const iteratorlist<T>& tamp)
		{
			return _node !=tamp._node;
		}

回到list类里面进行操作

首先 typedef iteratorlist<T> iterator;为什么这个迭代器类不直接设置类名位iterator呢,这个因为也有可能有别的迭代器代码放到同一个文件里,比如vector,如果此时直接将迭代器命名位iterator,那么vector也只能使用这个迭代器了,但是它的访问规则又和list不同,所以将迭代器类设置成iteratorlist进行区分,只在list类里面将它typedef位iterator,首先它满足了迭代器命名统一标准化,其次也和别的容器的迭代器代码区分开来了。这是因为只能通过list来使用对应的迭代器list<int>iterator,同样vector也只能使用它对应的迭代器vector<int>::iterator,虽然迭代器名字都是iterator,但是是在不同类域里面实现方法不同。

begin()迭代器

begin迭代器是返回第一个有效位置的数据,所以哨兵位是无效数据,所以直接返回它的next节点

cpp 复制代码
iterator begin()
{
	return iterator(_head->next);
}

end()迭代器

end迭代器是返回最后一个有效元素的下一个,这个位置是无效的,双向循环链表唯一无效的位置就是哨兵位了。因为是循环链表所以最后一个元素的next就是哨兵位

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

值得注意的是list的begin和end迭代器是不支持加偏移量的,可以通过++或--来移动迭代器这是因为 std::list 的迭代器是低效的,它不支持随机访问操作。如果希望移动到 list 中的某个位置,一个标准的做法是使用 std::advance 函数,或者手动在迭代器上使用 ++ 操作。

3.const迭代器

const迭代器的要求是迭代器本身可以修改,但是指向的内容不能修改,类似const int *a=5;这个整型指针指向的内容5不能修改,但是a可以++和--;

那么迭代器怎么处理呢,直接在迭代器前面加const吗,这样其实是不行,因为在迭代器前面加const限制的迭代器自己不能修改。

解决方法有两种,第一种直接另外写一个类,在解引用和operator->返回值那里直接加const,这样就直接限制了值不能修改,其它的都不用改

template<class T>
struct constiteratorlist
{
	Node<T>* _node;

	constiteratorlist(Node<T>* node) : _node(node)
	{
	}

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

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

	constiteratorlist operator++()
	{
		_node = _node->next;
		return *this;
	}

	constiteratorlist operator--()
	{
		_node = _node->prev;
		return *this;
	}

	constiteratorlist operator++(int)
	{
		constiteratorlist tamp(*this);
		_node = _node->next;
		return tamp;
	}

	constiteratorlist operator--(int)
	{
		constiteratorlist tamp(*this); 
		_node = _node->prev;
		return tamp;
	}

	bool operator==(const constiteratorlist& tamp) const // 确保具有 const 资格  
	{
		return _node == tamp._node;
	}

	bool operator!=(const constiteratorlist& tamp) const // 确保具有 const 资格  
	{
		return _node != tamp._node;
	}
};

回到list当中重命名为 typedef constiteratorlist<T> const_iterator;同时设置好begin()const 以及end()const迭代器

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

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

但是上面的做法为了改变两个函数就把两个类直接改变代价特别大,所以第二种做法利用模版来完成const。因为限制了返回节点数值的函数就可以完成了迭代器的const,所以专门传两个const模版参数给它们就可以了。所以把list当中iterator的模版参数全改成typedef iteratorlist<T,T*,T&> iterator;typedef iteratorlist<T,const T*,const T&> const_iterator,实际上还是生成了两个不同的类,只不过现在是编译器根据模版自动生成的; 而也要使用三个模版参数,所以在iterator类的模版参数声明也改成三个, template<class T,class ref,class ret>,解引用返回值和operator->也要改成相对的模版参数

cpp 复制代码
	template<class T,class ref,class ret>
	struct iteratorlist
	{
		 Node<T>* _node;

		iteratorlist(Node<T> *node):_node(node)
		{

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

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

		iteratorlist operator++()
		{
			_node = _node->next;
			return *this;
		}
		
		iteratorlist operator--()
		{
			_node = _node->prev;
			return *this;
		}

		iteratorlist operator++(int)
		{
			iteratorlist<T,ref,ret> tamp(*this);
			_node = _node->next;
			return tamp;
		}

		iteratorlist operator--(int)
		{
			iteratorlist<T,ref,ret> tamp(*this);
			_node = _node->prev;
			return tamp;
		}

		bool operator==(const iteratorlist<T,ref,ret>& tamp)
		{
			return _node ==tamp._node;
		}

		bool operator!=(const iteratorlist<T,ref,ret>& tamp)
		{
			return _node !=tamp._node;
		}

	};

也行有人会有疑问

既然传const模版参数就可以解决问题,为什么const迭代器不直接写成 typedef iteratorlist<const T> const_iterator;传一个const T过去不就行了,这样写其实是会报错的,这是因为迭代器的构造函数 iteratorlist(Node<T> *node):_node(node)这时候这个T类型实际上是const T,但是list当中begin和end传过来构造的节点可是T类型,举个例子list当中要求生成int类型的迭代器,可是迭代器当中是const int,这种没有什么权限缩小可以兼容使用,模版推演是严格遵守规则的(一点不同都生成不了),所以迭代器会生成失败

4.插入删除

insert

首先需要注意的是insert是在前面插入

cpp 复制代码
		iterator insert(iterator pos, const T& val)
		{
			Node<T>* cur = pos._node;
			Node<T>* newnode = new Node<T>(val);
			Node<T>* Prev= cur->prev;
			newnode->next =cur;
			newnode->prev =Prev;
			Prev->next = newnode;
			cur->prev = newnode;
			return iterator(newnode);
		}

让我挨个解释一下这段代码

Node<T>* cur = pos._node;

链表的插入是需要改变节点的prev和next,pos是迭代器,但是我们已经将迭代器设置成类了是无法直接访问到节点的next和prev的。所以要通过点操作符取到对应的node然后就可以使用node的next指针之类的来改变指向完成删除修改
Node<T>* newnode = new Node<T>(val);

这个就没啥好说了,构造一个新的节点,然后插入
newnode->next =cur;

newnode->prev =Prev;

Prev->next = newnode;

cur->prev = newnode;

这四步就是具体的插入操作,因为是在cur节点之前插入,所以newnode肯定是要放到cur节点之前的,所以newnode的next要指向cur,而cur的prev也要指向新节点。同样的cur的前面的节点Prev因为此时后面的节点改变了,所以next节点肯定也要改变。

inset迭代器不会失效,因为节点没有改变,但是库里还是让返回一个迭代器,所以和库里保持一致也返回一个迭代器

insert区间插入

template<class InputIterator>

void insert(iterator position, InputIterator first, InputIterator last)

{

assert(first<last);

InputIterator cur = first;

while (cur != last)

{

push_back(*cur);

cur++;

}

}

template<class InputIterator>把迭代器做成模版类型是为了方便别的类型的迭代器也能控制区间插入的数据范围,便于插入

insert填充插入

填充插入其实就是在一个迭代器位置上插入n个val

iterator insert(iterator pos, size_t n, const T& val)

{

while (n--)

{

pos=insert(pos, val);

}

return pos;

}

但是光这样会有一个问题,如果是tamp.insert(tamp.begin(), 5,6);,5默认是int类型,区间构造两个参数也做成模版了,tamp.insert(tamp.begin(), 5,6);这种情况会去匹配到区间构造了。虽然size_t是整型家族,但是和int还是有区别,所以它不会直接匹配到填充构造。解决办法就是直接再写一个int类型的填充插入。要插入long类型或者long long类型也是同样解决办法

iterator insert(iterator pos, int n, const T& val)

{

while (n--)

{

pos = insert(pos, val);

}

return pos;

}

erase

insert因为不用修改pos指向节点的空间,所以不会造成迭代器失效。但是erase因为要删除节点,所以会造成迭代器失效。因此要将下一个位置节点构造成迭代器返回

assert(pos!=end());这个是防止删除哨兵位,这个统一放到析构函数里面去处理

cpp 复制代码
	iterator erase(iterator pos)
	{
		assert(pos!=end());
		Node<T>* cur = pos._node;
		Node<T>* Next = cur->next;
		Node<T>* Prev = cur->prev;

		Prev->next = Next;
		Next->prev = Prev;
		delete cur;
		return iterator(Next);
	}

erase范围删除

iterator erase(iterator first,iterator last)

{

iterator cur = first;

while (cur != last)

{

cur=erase(cur);

}

return last;

}

范围删除就是从头开始挨个删除数据, cur=erase(cur);在单个数据删除里最后返回的是next下一个数据的迭代器,所以 cur=erase(cur);就相当于删完了cur上面的数据同时++cur;

push_back插入

尾插就是一直在尾部插入,也就是在end()的前面进行插入,因为已经实现了insert,所以直接调用insert在尾部插入就行

void push_back(const T& val)

{

insert(end(), val);

}

push_back删除

尾删是删除最后一个有效数据,最后一个有数据其实就是哨兵位的前一个数据,所以直接是--end()位置上进行删除就可以了。

void pop_back()

{

erase(--end());

}

push_front头插

头插其实就是每一次都在begin之前插入,因为begin就是第一个元素啊

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

pop_front头删

头删也很简单,就是把begin位置上的数据进行删除就行了。因为begin是第一个有效元素啊,头删本来就是每次删除第一个有效元素

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

插入删除测试

insert测试

insert区间插入测试

insert填充插入测试

push_back测试

push_front头插

erase测试

erase区间删除

为什么不是begin()++,而是前置++,因为后置++是先使用然后再++,如果使用后置++区间范围会变tamp.begin()到tamp.begin();

pop_back尾删

pop_front头删

5.构造及析构函数

默认构造

带头双向循环链表一般都设置一个_head来进行管理整个链表,list只需要构造出一个头结点就可以进行管理整个链表。因为双向循环链表,所以整个链表为空时_head节点的prev和next都指向自己,后序有节点插入只需要改变prev和next的指向就可以了。head实际上是不存储任何有效数据的,所以构造函数传空过去就可以了。在Node节点的构造函数中const T& val = T()写成这种缺省T()会去调用相对应类型的空值构造,如果是int类型那么默认就是0

cpp 复制代码
list()
{
	_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
	_head->next = _head;
	_head->prev = _head;
}

填充构造函数

填充构造和填充插入有点类似,区别在于填充构造必须先构造哨兵位出来,这是因为push_back底层实现用到了_head哨兵位节点

list(size_t n, const T& val = T())

{

_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点

_head->next = _head;

_head->prev = _head;

while (n--)

{

push_back(val);

}

}

同样填充构造函数会和区间构造产生冲突,所以要重新写一个int类型的填充构造函数

cpp 复制代码
	list(int n, const T& val = T())
	{
		_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
		_head->next = _head;
		_head->prev = _head;
		while (n--)
		{
			push_back(val);
		}
	}

区间构造函数

这个和区间插入有相似之处,都是用迭代器遍历挨个插入,但是这个依旧要先构造头节点出来,因为插入依赖头结点

template <class InputIterator>

list(InputIterator first, InputIterator last)

{

_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点

_head->next = _head;

_head->prev = _head;

InputIterator cur = first;

while (cur != last)

{

push_back(*cur);

cur++;

}

}

initializer_list构造

initializer_list这个我就不像vector一样实现了,因为initializer_list也有前后的迭代器,所以直接用insert实现了。

list(initializer_list<T> il)

{

insert(begin(), il.begin(), il.end());

}

析构函数和clear函数

析构要注意的是释放所以节点的空间,所以先定义一个clear函数用来释放所有有效节点,在确定了后面没有了有效节点后再把哨兵位_head进行delete。clear其实也是用迭代器进行变量,挨个调用erase进行删除

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

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

6.拷贝构造及operator赋值

拷贝构造其实与别的构造没什么差别,也是挨个用迭代器访问然后插入。但是要注意依旧要先构造头节点

cpp 复制代码
list(const list<T>& tamp)
{
	_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
	_head->next = _head;
	_head->prev = _head;
	const_iterator cur = tamp.begin();
	while (cur != tamp.end())
	{
		push_back(*cur);
		cur++;
	}
}

operator你可以挨个取值出来然后挨个插入,也可以直接利用拷贝构造和swap来完成赋值。

list<T>& operator=(list<T> tamp)

{

std::swap(_head, tamp._head);

return *this;

}

list<T> tamp这是传值传参实际上会走拷贝构造,tamp此时是个临时对象,但是我拿到tamp里面的数据,所以直接swap就可以了。我原先不要的数据因为和tamp交换了,所以会随着函数的结束而自动销毁。

7.其它函数的实现

cpp 复制代码
bool empty()const
{
	if (begin() == end())
		return true;
	else
		return false;
}

size_t size()const
{
	size_t num = 0;
	auto it = begin();
	while (it != end())
	{
		num++;
		it++;
	}
	return num;
}

// List Access
T& front()//返回头元素
{
	auto it = begin();
	return *it;
}
const T& front()const
{
	auto it = begin();
	return *it;
}

T& back()//返回尾元素
{
	auto it = --end();
	return *it;
}
const T& back()const
{
	auto it = --end();
	return *it;
}

void swap(list<T>& l)//交换函数
{
	std::swap(_head, l._head);
}

完整实现代码如下

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
using namespace std;
#include<assert.h>

namespace mylist
{
	template<class T>
	struct Node
	{
		Node(const T& val = T())
			:prev(nullptr)
			, next(nullptr)
			, _data(val)
		{

		}
		Node<T>* prev;
		Node<T>* next;
		T _data;
	};

	template<class T,class ref,class ret>
	struct iteratorlist
	{
		 Node<T>* _node;

		iteratorlist(Node<T> *node):_node(node)
		{

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

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

		iteratorlist operator++()
		{
			_node = _node->next;
			return *this;
		}
		
		iteratorlist operator--()
		{
			_node = _node->prev;
			return *this;
		}

		iteratorlist operator++(int)
		{
			iteratorlist<T,ref,ret> tamp(*this);
			_node = _node->next;
			return tamp;
		}

		iteratorlist operator--(int)
		{
			iteratorlist<T,ref,ret> tamp(*this);
			_node = _node->prev;
			return tamp;
		}

		bool operator==(const iteratorlist<T,ref,ret>& tamp)
		{
			return _node ==tamp._node;
		}

		bool operator!=(const iteratorlist<T,ref,ret>& tamp)
		{
			return _node !=tamp._node;
		}

	};


	//template<class T>
	//struct constiteratorlist
	//{
	//	Node<T>* _node;

	//	constiteratorlist(Node<T>* node) : _node(node)
	//	{
	//	}

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

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

	//	constiteratorlist operator++()
	//	{
	//		_node = _node->next;
	//		return *this;
	//	}

	//	constiteratorlist operator--()
	//	{
	//		_node = _node->prev;
	//		return *this;
	//	}

	//	constiteratorlist operator++(int)
	//	{
	//		constiteratorlist tamp(*this);
	//		_node = _node->next;
	//		return tamp;
	//	}

	//	constiteratorlist operator--(int)
	//	{
	//		constiteratorlist tamp(*this); // 使用副本而不是指针  
	//		_node = _node->prev;
	//		return tamp;
	//	}

	//	bool operator==(const constiteratorlist& tamp) const // 确保具有 const 资格  
	//	{
	//		return _node == tamp._node;
	//	}

	//	bool operator!=(const constiteratorlist& tamp) const // 确保具有 const 资格  
	//	{
	//		return _node != tamp._node;
	//	}
	//};


	template<class T>
	class list
	{
	public:
		typedef iteratorlist<T,T*,T&> iterator;
		typedef iteratorlist<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<T>();//指定调什么T类型的Node模版,构造头节点
			_head->next = _head;
			_head->prev = _head;
		}

		iterator insert(iterator pos, const T& val)
		{
			Node<T>* cur = pos._node;
			Node<T>* newnode = new Node<T>(val);
			Node<T>* Prev= cur->prev;
			newnode->next =cur;
			newnode->prev =Prev;
			Prev->next = newnode;
			cur->prev = newnode;
			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			assert(pos!=end());
			Node<T>* cur = pos._node;
			Node<T>* Next = cur->next;
			Node<T>* Prev = cur->prev;

			Prev->next = Next;
			Next->prev = Prev;
			delete cur;
			return iterator(Next);
		}

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

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

		void push_front(const T& val)
		{
			insert(begin(), val);
		}
		
		void pop_front()
		{
			erase(begin());
		}

		template<class InputIterator>
		void insert(iterator position, InputIterator first, InputIterator last)
		{
			assert(first<last);
			InputIterator cur = first;
			while (cur != last)
			{
				push_back(*cur);
				cur++;
			}
		}

		iterator insert(iterator pos, size_t n, const T& val)
		{
			while (n--)
			{
				pos=insert(pos, val);
			}
			return pos;
		}

		iterator insert(iterator pos, int n, const T& val)
		{
			while (n--)
			{
				pos = insert(pos, val);
			}
			return pos;
		}

		iterator erase(iterator first,iterator last)
		{
			iterator cur = first;
			while (cur != last)
			{
				cur=erase(cur);
			}
			return last;
		}

		list(size_t n, const T& val = T())
		{
			_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
			_head->next = _head;
			_head->prev = _head;
			while (n--)
			{
				push_back(val);
			}
		}

		list(int n, const T& val = T())
		{
			_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
			_head->next = _head;
			_head->prev = _head;
			while (n--)
			{
				push_back(val);
			}
		}

		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
			_head->next = _head;
			_head->prev = _head;
			InputIterator cur = first;
			while (cur != last)
			{
				push_back(*cur);
				cur++;
			}
		}


		list(initializer_list<T> il)
		{
			_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
			_head->next = _head;
			_head->prev = _head;
			insert(begin(), il.begin(), il.end());
		}

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


		list(const list<T>& tamp)
		{
			_head = new Node<T>();//指定调什么T类型的Node模版,构造头节点
			_head->next = _head;
			_head->prev = _head;
			const_iterator cur = tamp.begin();
			while (cur != tamp.end())
			{
				push_back(*cur);
				cur++;
			}
		}

		list<T>& operator=(list<T> tamp)
		{
			std::swap(_head, tamp._head);
			return *this;
		}
		bool empty()const
		{
			if (begin() == end())
				return true;
			else
				return false;
		}

		size_t size()const
		{
			size_t num = 0;
			auto it = begin();
			while (it != end())
			{
				num++;
				it++;
			}
			return num;
		}


		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
	private:
		Node<T>* _head;
	};
}
相关推荐
就爱学编程25 分钟前
重生之我在异世界学编程之C语言:数据在内存中的存储篇(上)
c语言·数据结构
狄加山6751 小时前
数据结构(顺序表)
数据结构
Protinx1 小时前
2009年408真题解析-数据结构篇(未完)
数据结构·经验分享·考研·408·计算机考研
析木不会编程1 小时前
【数据结构】【线性表】栈在算术表达式中的应用
数据结构
R_.L1 小时前
数据结构:单链表
数据结构
GISer_Jing1 小时前
前端Javascript数据结构与算法常见题目(二 **简单level**)
javascript·数据结构·算法
SchrodingerSDOG1 小时前
(补)算法刷题Day24: BM61 矩阵最长递增路径
数据结构·python·算法·矩阵
用户0099383143012 小时前
代码随想录算法训练营第十三天 | 二叉树part01
数据结构·算法
shinelord明3 小时前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
小手cool3 小时前
List反转的方法
list