【万字总结】C++——list的基本使用和模拟实现(建议收藏)

目录

一、list基本介绍

二、list的使用

1、list的初始化方式

2、list的增删查改

push_front和pop_front与push_back和pop_back

insert

erase

3、list迭代器的使用

正向迭代器

反向迭代器

4、list获取头尾元素

5、list容量操作

6、list的其他操作

sort

splice

remove

unique

merge

reverse

assign

swap

7、结点的构造函数

三、模拟迭代器类

迭代器类的模板参数

1、构造函数

2、运算符重载

++

==

!=

*

->

四、list的模拟实现

1、构造函数

2、拷贝构造函数

3、赋值运算符重载函数

4、析构函数

5、迭代器相关函数

begin和end

insert

erase

push_back和pop_back与push_front和pop_front

clear

五、反向迭代器的模拟实现

六、完整代码 

reverse_iterator.h

list.h

七、vector与list优劣


一、list基本介绍

1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。

3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高 效。

4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。

5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)。

二、list的使用

1、list的初始化方式

    list lt1;//构造空容器
	list lt2(20, 2);//构造有20个2的int型容器
	list lt3(lt2);//拷贝构造

	string str("hello world");
	//使用迭代器
	list lt4(str.begin(), str.end());//使用区间给容器赋值
    int myints[] = {16,2,77,29};
    std::list fifth (myints, myints + sizeof(myints) / sizeof(int) );//使用数组进行初始化

2、list的增删查改

push_front和pop_front与push_back和pop_back

void test_list2()
{
	list lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	//1  2  3  4

	//尾删
	lt.pop_back();
	//1  2  3  
	
	//头插
	lt.push_front(0);
	//0  1  2  3

	//头删
	lt.pop_front();
	//1  2  3 

	//打印
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	//1  2  3 
}

insert

【万字总结】C++——list的基本使用和模拟实现(建议收藏)_第1张图片

查阅文档我们可以发现list有三种插入方式:

1、在指定迭代器位置插入一个数。

2、在指定迭代器位置插入n个值为val的数。

3、在指定迭代器位置插入一段迭代器区间。 

	list lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	// 1  2  3  4
	


    //方式一
	//先确定2的位置
	list::iterator pos = find(lt.begin(), lt.end(), 2);
	//在2的前面插入一个0
	lt.insert(pos, 0);
	// 1  0  2  3  4 

	//方式二
	//在2的前面插入两个0
	lt.insert(pos, 2, 0);
	//1  0  0  0  2  3  4

	//方式三
	vector v(3, 0);
	lt.insert(pos, v.begin(), v.end());
	//在2的前面插入三个0
	//1  0  0  0  0  0  0  2  3  4

erase

 文档中记载有两种删除方式:

  1. 删除指定迭代器位置的元素。
  2. 删除指定迭代器区间(左闭右开)的所有元素。
    list lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	// 1  2  3  4
	// 
	
	//找到2的位置用迭代器指向它获得
	list::iterator pos1 = find(lt.begin(), lt.end(),2);
	lt.erase(pos1);
	// 1  3  4  

	list::iterator pos2 = find(lt.begin(), lt.end(), 3);
	lt.erase(pos2, lt.end());
	// 1

3、list迭代器的使用

正向迭代器


	list lt(3, 7);
	list::iterator it = lt.begin();

	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	//输出3个7

反向迭代器

    list lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	list::reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	//4  3  2  1

4、list获取头尾元素

    list lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	//输出最后一个元素的值
	cout << lt.back() << endl;

	//输出第一个元素的值
	cout << lt.front() << endl;

5、list容量操作


	list lt;
	//尾插
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	//输出数据个数
	cout << lt.size() << endl;

	//当所给值大于当前的size时,将size扩大到该值,扩大的数据为第二个所给值
	//若未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
	//当所给值小于当前的size时,将size缩小到该值。
	//容量增加到5
	lt.resize(5);
	
	//判断容器内是否为空
	if (lt.empty())
	{
		cout << "容器为空" << endl;
	}
	else
	{
		cout << "容器不为空" << endl;
	}

	//清空容器的数据,不改变容量大小
	lt.clear();
	cout << lt.size() << endl;

6、list的其他操作

sort

将容器中的数据默认按照升序排列。

list lt;
	//尾插
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(1);
	lt.push_back(2);

	lt.sort();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	//输出 1  2  3  4

splice

作用是拼接两个list容器。

【万字总结】C++——list的基本使用和模拟实现(建议收藏)_第2张图片

由文档可知有三种方式: 

1、将一个容器拼接到另一个容器迭代器指向的位置。

2、将容器中的某一个数据拼接到另一个容器迭代器指向的位置。

3、将要容器中迭代器指向的区间的数据拼接到另一个容器迭代器指向的位置。


	//方式一
	list lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);

	list lt2(3, 5);

	list::iterator pos1 = find(lt1.begin(), lt1.end(), 2);
	lt1.splice(pos1, lt2);

	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
	//1 5 5 5 2 3 4



	//方式二
	list lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);

	list lt2(3, 5);

	list::iterator pos1 = find(lt1.begin(), lt1.end(), 2);
	lt1.splice(pos1, lt2, lt2.begin());
	//1  5  2  3  4



	
	//方式三
	list lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);

	list lt2(3, 5);

	list::iterator pos1 = find(lt1.begin(), lt1.end(), 2);
	list::iterator it2 = lt2.begin();
	lt1.splice(pos1, lt2, lt2.begin(), lt2.end());
	//1 5 5 5 2 3 4

remove

删除容器中的某个值


	list lt1;
	//尾插
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(3);
	lt1.push_back(3);
	lt1.push_back(3);
	lt1.push_back(4);

	lt1.remove(3);
	
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//1  2  4

unique

去除容器容器中连续重复的元素。


	list lt;
	lt.push_back(1);
	lt.push_back(1);
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(4);
	lt.push_back(4);
	lt.push_back(4);
	lt.push_back(4);

	lt.unique();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	//1  2  3  4

merge

类似归并排序,将一个有序容器归并到另一个有序容器中,使其还是有序的。

list lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.merge(lt2);

	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//1  2  3  4  5  6  7  8

reverse

将容器中的元素进行逆置。

list lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.merge(lt2);

	//1  2  3  4  5  6  7  8

	lt1.reverse();

	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//8  7  6  5  4  3  2  1

assign

将新内容分配给容器,并且会覆盖原容器中的值。

【万字总结】C++——list的基本使用和模拟实现(建议收藏)_第3张图片

由文档可知有两种方式:

1、将n个val值分配给容器

2、将迭代器区间中的内容分配给容器


	//方式一
	list lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);

	lt1.assign(3, 3);

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


	//方式二
	list lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.assign(lt2.begin(), lt2.end());
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//2  4  6  8 

swap

交换两个容器的内容

list lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	list lt2;
	lt2.push_back(2);
	lt2.push_back(4);
	lt2.push_back(6);
	lt2.push_back(8);

	lt1.swap(lt2);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	//2  4  6  8 

7、结点的构造函数

构造一个结点,并且给结点的数据域he指针域初始化。

template//类模板
	struct ListNode
	{
		ListNode* _next;
		ListNode* _prev;
		T _data;
ListNode(const T& data = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{}

三、模拟迭代器类

为什么在vector和string中不用实现一个迭代器类呢?

因为vector和string中的数据都是存储在一块连续的地址空间,我们直接使迭代器像原生指针那样解引用、自增、自减。就可以得到我们所要得到的效果。

可是list却不是如此,我们知道list的本质是一个双向带头循环链表。地址空间并不连续,我们就不能单纯的像原生指针那样进行操作了。

而迭代器的意义就是,让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。

迭代器类的模板参数

//迭代器
	template//类模板

由以上代码我们可以看出来,迭代器类的模板参数列表有三个模板参数。

这也与list模拟实现中的两个迭代器相对应。

typedef _list_iterator iterator;
typedef _list_iterator const_iterator;

其具体作用其实就是为了让我们使用普通迭代器的时候,编译器实例化出一个普通迭代器对象。当我们使用const迭代器的时候就会实例化一个const迭代器对象。

1、构造函数

本质就是根据结点指针构造一个迭代器对象。

//构造函数
__list_iterator(Node* x)
	:_node(x)
{}

2、运算符重载

 注意:

self是当前迭代器对象的类型

		typedef __list_iterator self;

++

本质是使结点指针指向后一个结点,并且返回下一个结点指针。

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

        
		//it++
        //切记后置++不能传引用
        //因为tmp是个临时对象,除了作用域会销毁的因此只能拷贝不能传引用
		self operator++(int)
		{
			self tmp(*this);
			//保存++之前的值以便于返回
			_node = _node->_next;
			return tmp;
		}

注意: 切记后置不能传引用,因为tmp是个临时对象,出了作用域会销毁的因此只能拷贝不能传引用

--

本质是使结点值真指向前一个结点,并且返回上一个结点指针。


		//--it
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//it--
		self operator--(int)
		{
			self tmp(*this);
			//保存--之前的值以便于返回
			_node = _node->_prev;
			return tmp;
		}

注意: 切记后置不能传引用,因为tmp是个临时对象,出了作用域会销毁的因此只能拷贝不能传引用 

==

本质就是比较此时两个迭代器当中的结点指针是否指向同一个位置

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

!=

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

*

由于list是一个链表结构,*解引用实质就是返回此结点指针所指向的数据。

//需要T&传T& 需要const T& 就传 const T&
		Ref operator*()//由于用的引用,所以可读可写
		{
			return _node->_data;
		}

->

如果list容器中结点中存放的不是内置类型,而是自定义类型,那么我们就要用到->。

比如下面的日期类:


	struct Date
	{
		int _year;
		int _month;
		int _day;

		Date(int year = 1, int month = 1, int day = 1)
			:_year(year)
			, _month(month)
			, _day(day)
		{}
	};

	void test_list2()
	{
		list lt;
		lt.push_back(Date(2022, 3, 12));
		lt.push_back(Date(2022, 3, 13));
		lt.push_back(Date(2022, 3, 14));
		//普通指针用解引用访问,结构体指针用箭头
		list::iterator it = lt.begin();
		while (it != lt.end())
		{
			//(*it)代表结点的数据也就是日期类的对象
			/*cout << (*it)._year << " ";
			cout << (*it)._month << " ";
			cout << (*it)._day << " ";
			cout << endl;*/
			cout << it->_year << " ";
			cout << it->_month << " ";
			cout << it->_day << " ";
			cout << endl;
			it++;
		}
		cout << endl;
	}

那么该如何重载呢?

在这里我们先写出重载的函数,再加以解释。

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

如果你认真去推理你会发现这个重载函数是有问题的,it->找到的是自定义类型Date*,只有再->才是对应的Date的成员变量呀。所以按理说这里应该是it->->,才行呀。

因此,我们可以得出,如果两个->的话,程序的可读性太差了,所以这里编译器做了特殊的处理,所以省略了一个->。

四、list的模拟实现

1、构造函数

list()//构造函数
		{
			_head = new Node();//申请头结点
			_head->_next = _head;//头结点的后一个结点指向自己
			_head->_prev = _head;//头结点的前一个结点指向自己
		}

2、拷贝构造函数

传统写法:

首先申请一个头结点,使头结点前驱指针和后继指针都指向自己。然后将容器中的数据通过遍历的方式尾插到新容器后面。

现代写法:

首先申请一个头结点,使头结点前驱指针和后继指针都指向自己。然后创建一个名叫tmp的list容器并且使lt1容器通过迭代器区间的方式初始化tmp,随后交换tmp的头结点和lt2。

传统写法
		//lt2(lt1)
		list(const list& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			for (auto e : lt)
			{
				push_back(e);  
			}
		}
//现代写法
		//lt2(lt1)
		list(const list& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			list tmp(lt.begin(), lt.end());
			std::swap(_head, tmp._head);
		}

3、赋值运算符重载函数

传统写法:

直接将lt中的值一个个遍历尾插到lt1中。

现代写法:

直接交换两个容器。

首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换即可。

这样做相当于将应该用clear清理的数据,通过交换函数交给了容器lt,而当该赋值运算符重载函数调用结束时,容器lt会自动销毁,并调用其析构函数进行清理。

//传统写法
//lt2=lt1
		list& operator=(const list& lt)
		{
			if (this != <)
			{
				clear();
				for (auto e : lt)
				{
					push_back(e);
				}
			}
			return *this;
		}
//现代写法
list& operator=(list lt)
		{
			std::swap(_head, lt._head);

			return *this;
		}

4、析构函数

先用clear清理数据,然后释放头结点,再把头结点置空。

~list()
		{
			clear();

			delete _head;
			_head = nullptr;

		}

5、迭代器相关函数

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

insert

先根据所给迭代器得到该位置处的结点指针cur,然后通过cur指针找到前一个位置的结点指针prev,接着根据所给数据x构造一个待插入结点,之后再建立新结点与cur之间的双向关系,最后建立新结点与prev之间的双向关系即可。

iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);

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

erase

首先使用断言assert来使所删除位置不能是头结点。先根据所给迭代器得到该位置处的结点指针cur,然后通过cur指针找到前一个位置的结点指针prev,以及后一个位置的结点指针next,紧接着释放cur结点,最后建立prev和next之间的双向关系即可。


		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

			delete pos._node;
			prev->_next = next;
			next->_prev = prev;
			return iterator(next);
		}

push_back和pop_back与push_front和pop_front

可以复用insert和erase进行处理。

        void push_back(const T& x)
		{
			//Node* tail = _head->_prev;//记录最后的尾结点
			//Node* newnode = new Node(x);//创建新结点
			空节点也适用
			链接各个节点之间的关系
			//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_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}

clear

复用 erase,只保留头结点。


		void clear()
		{/*
			iterator it = begin();
			while (it != end())
			{
				iterator del = it++;
				delete del._node;
			}
			_head->_next = _head;
			_head->_prev = _head;*/
			iterator it = begin();
			while (it != end())
			{
				iterator del = it++;
				erase(del);
			}
		}

五、反向迭代器的模拟实现

反向迭代器有一点非常重要,就是rbegin和rend的位置。

我们所知道的begin就是指向数据的第一个元素,然而end是指向最后一个元素的后一个位置。

那么反向迭代器呢,rbegin是指向数据的最后一个数据,rend是指向数据的第一个数据的前一个。

也就是如图所示的情况:

【万字总结】C++——list的基本使用和模拟实现(建议收藏)_第4张图片

 可是,list的反向迭代器的源代码却不是这样实现的,而是如图所示:

【万字总结】C++——list的基本使用和模拟实现(建议收藏)_第5张图片

 可是为什么这么做呢?,难道只是为了强迫症吗?

其实不然,我们可以发现list这样做的话begin与rend和end和rbegin有很好的的对称性,也印证了为什么我们在vector和string的时候不对反向迭代器进行模拟实现,因为当我们模拟实现了list这个反向迭代器之后,我们的vector和string可以直接使用,就是因为这个很好的对称性!

那么现在有一个问题,我们既然把rend和rbegin这样都向后移动了一位,那我们解引用的时候,也就是通过rend和rbegin拿到他们对应的值不就乱套了,所以我们此时对 *  运算符进行重载的时候很好的避免了这一点,这也就是为什么下边代码中 * 运算符重载返回的是 return *--prev;


namespace mwb
{
	//Iterator是哪个容器的迭代器,reverse_iterator就可以
	//适配出哪个容器的反向迭代器。复用的体现
	template 
	class reverse_iterator
	{
		typedef reverse_iterator self;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}
		
		Ref operator*()
		{
			//return *_it;
			Iterator prev = _it;
			return *--prev;
		}

		Ptr operator->()
		{
			return &operator*();
		}
		self& operator++()
		{
			_it--;
			return *this;
		}
		self& operator--()
		{
			_it--;
			return *this;
		}
		bool operator!= (const self& rit) const
		{
			return _it != rit._it;
		}
		
	private:
		Iterator _it;
	};

}

六、完整代码 

reverse_iterator.h

#pragma once

namespace mwb
{
	//Iterator是哪个容器的迭代器,reverse_iterator就可以
	//适配出哪个容器的反向迭代器。复用的体现
	template 
	class reverse_iterator
	{
		typedef reverse_iterator self;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}
		
		Ref operator*()
		{
			//return *_it;
			Iterator prev = _it;
			return *--prev;
		}

		Ptr operator->()
		{
			return &operator*();
		}
		self& operator++()
		{
			_it--;
			return *this;
		}
		self& operator--()
		{
			_it--;
			return *this;
		}
		bool operator!= (const self& rit) const
		{
			return _it != rit._it;
		}
		
	private:
		Iterator _it;
	};

}

list.h

#pragma once
#include"reverse_iterator.h"
namespace mwb
{

	template//类模板
	struct ListNode
	{
		ListNode* _next;
		ListNode* _prev;
		T _data;

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





	//迭代器
	template//类模板

	struct __list_iterator//自定义类型
	{
		typedef ListNode Node;
		typedef __list_iterator self;
		//typedef __list_iterator
		Node* _node;

		//构造函数
		__list_iterator(Node* x)
			:_node(x)
		{}

		//it2=it1 浅拷贝
		//拷贝构造和赋值重载是否需要我们自己实现
		//析构呢?
		// 迭代器是借助节点的指针访问修改链表
		//节点属于链表,不属于迭代器,所以他不管释放
		//都不需要自己实现,默认生成的即可

		//需要T&传T& 需要const T& 就传 const T&
		Ref operator*()//由于用的引用,所以可读可写
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}
		//++it
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		//it++
		self operator++(int)
		{
			self tmp(*this);
			//保存++之前的值以便于返回
			_node = _node->_next;
			return tmp;
		}

		//--it
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//it--
		self operator--(int)
		{
			self tmp(*this);
			//保存--之前的值以便于返回
			_node = _node->_prev;
			return tmp;
		}
		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}
		bool operator==(const self& it) const
		{
			return _node == it._node;
		}
	};



	template//类模板
	class list
	{
		typedef ListNode Node;
	public:
		typedef __list_iterator iterator;
		typedef __list_iterator const_iterator;

		typedef reverse_iterator const_reverse_iterator;
		typedef reverse_iterator reverse_iterator;

		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}
		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->_next = _head;
			_head->_prev = _head;
		}

		list(size_t n, const T& val = T())
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		list(int n, const T& val = T())
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		template
		list(InputIterator first, InputIterator last)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

		//现代写法
		//lt2(lt1)
		list(const list& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			list tmp(lt.begin(), lt.end());
			std::swap(_head, tmp._head);
		}
		list& operator=(list lt)
		{
			std::swap(_head, lt._head);

			return *this;
		}
		//传统写法
		lt2(lt1)
		//list(const list& lt)
		//{
		//	_head = new Node();
		//	_head->_next = _head;
		//	_head->_prev = _head;
		//	for (auto e : lt)
		//	{
		//		push_back(e);  
		//	}
		//}

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

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;

		}
		void clear()
		{/*
			iterator it = begin();
			while (it != end())
			{
				iterator del = it++;
				delete del._node;
			}
			_head->_next = _head;
			_head->_prev = _head;*/
			iterator it = begin();
			while (it != end())
			{
				iterator del = it++;
				erase(del);
			}
		}
		void push_back(const T& x)
		{
			//Node* tail = _head->_prev;//记录最后的尾结点
			//Node* newnode = new Node(x);//创建新结点
			空节点也适用
			链接各个节点之间的关系
			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _head;
			//_head->_prev = newnode;

			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return iterator(newnode);
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

			delete pos._node;
			prev->_next = next;
			next->_prev = prev;
			return iterator(next);
		}
	private:
		Node* _head;
	};
	//void print_list(const list& lt)
	//{
	//	list::iterator it = lt.begin();
	//	while (it != lt.end())
	//	{
	//		cout << *it << " ";
	//		it++;
	//	}
	//	cout << endl;

	//}
	void test_list1()
	{
		list lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list::iterator it = lt.begin();
		while (it != lt.end())
		{
			*it *= 2;
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}
	struct Date
	{
		int _year;
		int _month;
		int _day;

		Date(int year = 1, int month = 1, int day = 1)
			:_year(year)
			, _month(month)
			, _day(day)
		{}
	};
	void test_list2()
	{
		list lt;
		lt.push_back(Date(2022, 3, 12));
		lt.push_back(Date(2022, 3, 13));
		lt.push_back(Date(2022, 3, 14));
		//普通指针用解引用访问,结构体指针用箭头
		list::iterator it = lt.begin();
		while (it != lt.end())
		{
			//(*it)代表结点的数据也就是日期类的对象
			/*cout << (*it)._year << " ";
			cout << (*it)._month << " ";
			cout << (*it)._day << " ";
			cout << endl;*/
			cout << it->_year << " ";
			cout << it->_month << " ";
			cout << it->_day << " ";
			cout << endl;
			it++;
		}
		cout << endl;
	}
	void test_list3()
	{
		list lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);

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


		list lt3;
		lt3.push_back(10);
		lt3.push_back(10);
		lt3.push_back(10);
		lt3.push_back(10);

		lt1 = lt3;
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_list4()
	{
		list lt1(5, Date(2022, 3, 15));
		for (auto e : lt1)
		{
			cout << e._year << "/" << e._month << "/" << e._day << endl;
		}
		cout << endl;

		list lt2(5, 1);
		for (auto e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_list5()
	{
		list lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);

		list::iterator it = lt1.begin();
		while (it != lt1.end())
		{
			//*it *= 2; // 修改
			cout << *it << " ";
			++it;
		}
		cout << endl;

		list::reverse_iterator rit = lt1.rbegin();
		while (rit != lt1.rend())
		{
			cout << *rit << " ";
			++rit;
		}
		cout << endl;
	}
}

七、vector与list优劣

vector缺陷:

连续的物理空间,是优势也是劣势。

劣势:

1、空间不够要增容,增容代价比较大。

2、可能存在一定空间浪费。按需申请,会导致频繁增容,所以一般都会2倍左右扩容。

3、头部或者中部插入删除需要挪动数据,效率低下。

list优势:

1、按需申请释放空间

2、list任意位置支持O(1)插入删除。

综上所述:

本质vector和list是互补的两个数据结构。

你可能感兴趣的:(C++,c++,链表,数据结构,list)