从C语言到C++_17(list的模拟实现)list不是原生指针的迭代器

目录

1. list 的基本框架

1.1 list 的结点

1.2 list 构造函数

1.3 push_back 

2. list 迭代器的实现

2.1 迭代器的构造

2.2 begin() 和 end()

2.3  重载 != 和 * 和 ++ 

2.4 遍历测试:

2.6 operator->

2.7 operator--

2.8 const 迭代器

3. list 的增删查改

3.1 insert和头插尾插

3.2 erase和头删尾删

4.  list 的深浅拷贝

4.1 clear 和析构 

4.2 迭代器区间构造和交换

4.3 拷贝构造和赋值重载

5. list相关选择题

答案:

6. 完整代码

list.h

Test.c

本篇完。


上一篇说到,list 其实就是带哨兵位循环双向链表而已,这种链表虽然结构复杂,

但是实现起来反而是最简单的,我们在数据结构与算法专栏中有过详细的讲解:

数据结构与算法⑦(第二章收尾)带头双向循环链表的实现_GR C的博客-CSDN博客

当时我们是用C语言实现,这里对 list 的实现其实也是大同小异的。

当然,我们重点还是倾向于去理解它的底层实现原理,

所以我们将对其实现方式进行进一步地简化,并且按照我们自己习惯的命名风格去走。

我们之前已经模拟实现过 string 和 vector 了,这是本专栏 STL 的第三个模拟实现,

因此在讲解的时,出现重复的知识点我们就一笔带过。我们将重点去讲解迭代器的实现!

本章我们要对迭代器有一个新的认知,迭代器不一定就是一个原生指针,

也有可能是一个自定义类型。

本章我们将通过自定义类型的运算符重载去控制我们的迭代器的 "行为"。


1. list 的基本框架

我们还是参考《STL源码剖析》,既然是要实现链表,我们首先要做的应该是建构结点。

此外,为了和真正的 list 进行区分,我们这里仍然在自己的命名空间内实现。

1.1 list 的结点

C语言写的:

 C++的代码:

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

C++中对象里的成员如果全是共有的还是比较习惯用 struct 的

我们知道,结构体 struct 在 C++ 中升级成了类,因此它也有调用构造函数的权利。

也就是说,在创建结构体对象的时会调用构造函数。

既然如此,结点的初始化工作,可以考虑写一个构造函数去初始化:

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

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

设计成全缺省,给一个匿名对象 T() 。如此一来,如果没有指定初识值,

它就会按模板类型去给对应的初始值了。

1.2 list 构造函数

设计好结点后,我们现在可以开始实现 list 类了。

考虑到我们刚才实现的 "结点" ListNode<T> 类型比较长,为了美观我们将其 typedef 成 Node

因为是带头(哨兵位)双向循环链表,我们先要带个头。

我们先要把头结点 _pHead 给设计出来,而 _prev 和 _next 是默认指向头结点的。

到这里 list.h 就是这样:

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

namespace rtx
{
	template<class T>
	struct list_node // 定义结点
	{
		T _data;
		list_node* _prev;
		list_node* _next;

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

	template<class T>
	class list // 定义list类
	{
		typedef list_node<T> Node;
	public:
		list()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}

	private:
		Node* _head; // 哨兵位头结点
	};
}

1.3 push_back 

我们先去实现一下最经典的 push_back 尾插,好让我们的 list 先跑起来。

尾插思路和以前写过的思路一样,后面很多接口也是,不懂的回去看啊,别逼我求你,

数据结构与算法⑦(第二章收尾)带头双向循环链表的实现_GR C的博客-CSDN博客

直接放代码了:

		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* NewNode = new Node(x);
			// 思路图:head        tail  NewNode
			tail->_next = NewNode;
			NewNode->_prev = tail;
			_head->_prev = NewNode;
			NewNode->_next = _head;
		}

我们想要打印的话就只能自己实现迭代器了:

2. list 迭代器的实现

list 的重点是迭代器,因为这里的迭代器的实现和我们之前讲的实现方式都不同。

我们之前讲的 string 和 vector 的迭代器都是一个原生指针,实现起来是非常简单的。

但是 list 是一个链表,你的迭代器还能这样去实现吗?在空间上不是连续的,如何往后走?

而这些所谓的 "链接" 其实都是我们想象出来的,实际上根本就不存在。

而这些链接的含义只是 "我存的就是你的地址" ,所以我可以找到你的位置。

而我要到下一个位置的重点是 —— 解引用能取到数据,++ 移动到下一位。

而自带的 解引用* 和 ++ 的功能,是没法在链表中操作的。

但是,得益于C++有运算符重载的功能,我们可以用一个类型去对结点的指针进行封装,

然后重载运算符 operator++ 和 operator* ,

是不是就可以控制其解引用并 ++ 到下一个位置了?

所以,我们首先要做的是对这两个运算符进行重载:

2.1 迭代器的构造

代码:只需要用一个结点的指针

	template<class T>
	struct __list_iterator // 定义迭代器
	{
		typedef list_node<T> Node;
        typedef __list_iterator<T> iterator; // STL规定的命名,且公有
		Node* _node;

		iterator(Node* node)
			:_node(node)
		{}
	};

这里命名是参考源码的,__list_iterator 前面是两个下划线。

我们想要打印的话应该是这样的:

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

所以我们来实现这几个操作

2.2 begin() 和 end()

代码:在 list 类中设计 begin 和 end

	template<class T>
	class list // 定义list类
	{
		typedef list_node<T> Node;
	public:
		typedef __list_iterator<T> iterator; // STL规定的命名,且公有

		iterator begin()// begin是实际数据的第一个,_head 是哨兵位头结点
		{
			return iterator(_head->_next);
		}
		iterator end()// end是实际数据的下一个
		{
			return iterator(_head);
		}

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

		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* NewNode = new Node(x);
			// 思路图:head        tail  NewNode
			tail->_next = NewNode;
			NewNode->_prev = tail;
			_head->_prev = NewNode;
			NewNode->_next = _head;
		}

	private:
		Node* _head; // 哨兵位头结点
	};

2.3  重载 != 和 * 和 ++ 

operator!=

如何判断是否相等呢?

如果两个迭代器结点的指针指向的是同一个结点,那就说明是相等的迭代器:

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

operator*

解引用就是取结点 _node 里的数据,并且 operator* 和指针一样,不仅仅能读数据,还能写数据。

为了使 operator* 能支持修改的操作,我们这里用引用返回 & (返回 _node 中 _data 的别名)

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

operator++

加加分为前置和后置,我们这里先实现前置++:

		iterator& operator++()
		{
			_node = _node->_next;
			return *this; // 返回加加后的值
		}

因为前置是直接改变本体,我们直接 return *this 即可。

因为出了作用域还在,所以可以用引用返回, __list_iterator<T>& 

对应的,后置++ 我们可以拷贝构造出一个 tmp 存储原来的值,这样虽然改变本体了,

但是返回的还是之前的值,这就实现了后置++。此外,因为前置++后置++都是 operator++,

区分方式是后置++用占位符 (int) 占位,这些知识点在之前讲解日期类的时候都说过。

后置++的实现:(注意后置++不能用引用返回)

		iterator operator++(int)
		{
			__list_iterator<T> tmp(*this); // 拷贝构造一个tmp存储原来的值
			_node = _node->next;
			return tmp; // 返回加加前的值
		}

2.4 遍历测试:

至此,可以遍历打印我们的代码了,而且范围for也能用了:

list.h

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

namespace rtx
{
	template<class T>
	struct list_node // 定义结点
	{
		T _data;
		list_node* _prev;
		list_node* _next;

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

	template<class T>
	struct __list_iterator // 定义迭代器
	{
		typedef list_node<T> Node;
        typedef __list_iterator<T> iterator; // STL规定的命名,且公有
		Node* _node;

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

		bool operator!=(const iterator& it) 
		{
			return _node != it._node;
		}
		T& operator*()
		{
			return _node->_data;  // 返回结点的数据
		}
		iterator& operator++()
		{
			_node = _node->_next;
			return *this; // 返回加加后的值
		}
		iterator& operator++(int)
		{
			iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
			_node = _node->next;
			return tmp; // 返回加加后的值
		}
	};

	template<class T>
	class list // 定义list类
	{
		typedef list_node<T> Node;
	public:
		typedef __list_iterator<T> iterator; // STL规定的命名,且公有

		iterator begin()// begin是实际数据的第一个,_head 是哨兵位头结点
		{
			return iterator(_head->_next);
		}
		iterator end()// end是实际数据的下一个
		{
			return iterator(_head);
		}

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

		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* NewNode = new Node(x);
			// 思路图:head        tail  NewNode
			tail->_next = NewNode;
			NewNode->_prev = tail;
			_head->_prev = NewNode;
			NewNode->_next = _head;
		}

	private:
		Node* _head; // 哨兵位头结点
	};
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "list.h"

namespace rtx
{
	void Test_push_back()
	{
		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())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

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

int main()
{
	rtx::Test_push_back();

	return 0;
}


2.6 operator->

迭代器是像指针一样的,所以要重载两个解引用。

为什么?指针如果指向的类型是原生的普通类型,要取对象是可以用解引用,

但是如果指向而是一个结构,并且我们又要取它的每一个成员变量,

比如我们想打印坐标:

	void Test_arrow()
	{
		struct Pos
		{
			int _a1;
			int _a2;

			Pos(int a1 = 0, int a2 = 0)
				:_a1(a1)
				, _a2(a2)
			{}
		};
		Pos aa;
		Pos* p2 = &aa;
		p2->_a1;
		p2->_a2;

		list<Pos> lt;
		lt.push_back(Pos(10, 20));
		lt.push_back(Pos(10, 21));

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

虽然能用解引用+点操作符,但用箭头还是方便的,而且你模拟实现总不能不给别人用吧,

所以我们这里可以去实现一下箭头操作符 operator->,如果不是很熟练应该是不会的。

我们直接看一下源代码是怎么实现的,抄下来用用然后思考下:

		T& operator*()
		{
			return _node->_data;  // 返回结点的数据
		}
		T* operator->()
		{
			return &(operator*());
			//即 return &(_node->_data);
		}
	void Test_arrow()
	{
		struct Pos
		{
			int _a1;
			int _a2;

			Pos(int a1 = 0, int a2 = 0)
				:_a1(a1)
				, _a2(a2)
			{}
		};
		Pos aa;
		Pos* p2 = &aa;
		p2->_a1;
		p2->_a2;

		list<Pos> lt;
		lt.push_back(Pos(10, 20));
		lt.push_back(Pos(10, 21));

		list<Pos>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1 << "," << (*it)._a2 << endl;
			cout << it->_a1 << "," << it->_a2 << endl;
		    //实际要写,it->->_a1,但是编译器优化了一个箭头
			++it;
		}
		cout << endl;
	}

第一个指针是operator->,第二个指针是原生指针的箭头,但是编译器为了可读性:

所有类型重载 operator-> 时都会省略一个箭头。


2.7 operator--

前面实现了operator++,现在实现下operator--,把++的_next换成_prev就行:

		iterator& operator--()
		{
			_node = _node->_prev;
			return *this; // 返回减减后的值
		}
		iterator operator--(int)
		{
			iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
			_node = _node->prev;
			return tmp; // 返回减减前的值
		}

2.8 const 迭代器

不用范围 for 的前提下去用迭代器遍历打印似乎挺麻烦的,我们可以把它放到一个函数里,

这里考虑到减少拷贝,使用引用返回,之前也说过这种情况能用 const 就用 const。

所以这里就成 const_iterator 了,而刚才实现的是普通迭代器,会导致没法遍历:

 普通迭代器访问普通对象,可读可写;const 迭代器访问 const 对象,可读但不可写。

所以这里自然是需要实现 const 迭代器,即实现一个 "可读但不可写" 的迭代器。

(可以 ++ 可以解引用,但解引用的时候不能修改)

所以直接在 __list_iterator 里面重载一个 const 类型的 operator* 解决不了问题,

我们得重新实现一个 __const_list_iterator 出来。(更好的方法后面讲)

传统的方法是把  list_iterator 这个类CV一下,然后把名称改成 __const_list_iterator

这种实现方式可以是可以,但是这么实现好像有点拉胯,代码是很冗余的,

这个 const 迭代器和普通迭代器也就是类型名称和返回值不一样而已……

有没有办法可以优化一下呢?

可以通过加一个额外的模板参数去控制 operator 的返回值,能想到吗?

我们来看看巨佬是怎么做的 :在定义 template 模板的时增加两个参数:

	template<class T, class Ref, class Ptr>
	struct __list_iterator // 定义迭代器
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;
		// 在list类里面:
		// typedef __list_iterator<T, T&, T*>             iterator;
        // typedef __list_iterator<T, const T&, const T*> const_iterator;

再加上const begin和const end我们的遍历打印函数就能跑出来了:

list.h

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

namespace rtx
{
	template<class T>
	struct list_node // 定义结点
	{
		T _data;
		list_node* _prev;
		list_node* _next;

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

	template<class T, class Ref, class Ptr>
	struct __list_iterator // 定义迭代器
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;
		// 在list类里面:
		// typedef __list_iterator<T, T&, T*>             iterator;
        // typedef __list_iterator<T, const T&, const T*> const_iterator;
		Node* _node;

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

		bool operator!=(const iterator& it)
		{
			return _node != it._node;
		}
		//T& operator*()
		Ref operator*()
		{
			return _node->_data;  // 返回结点的数据
		}
		//T* operator->()
		Ptr operator->()
		{
			return &(operator*());
			//即 return &(_node->_data);
		}
		iterator& operator++()
		{
			_node = _node->_next;
			return *this; // 返回加加后的值
		}
		iterator operator++(int)
		{
			iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
			_node = _node->_next;
			return tmp; // 返回加加前的值
		}
        iterator& operator--()
		{
			_node = _node->_prev;
			return *this; // 返回减减后的值
		}
		iterator operator--(int)
		{
			iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
			_node = _node->prev;
			return tmp; // 返回减减前的值
		}
	};

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

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

		iterator begin()// begin是实际数据的第一个,_head 是哨兵位头结点
		{
			return iterator(_head->_next);
		}
		iterator end()// end是实际数据的下一个
		{
			return iterator(_head);
		}

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

		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* NewNode = new Node(x);
			// 思路图:head        tail  NewNode
			tail->_next = NewNode;
			NewNode->_prev = tail;
			_head->_prev = NewNode;
			NewNode->_next = _head;
		}

	private:
		Node* _head; // 哨兵位头结点
	};
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "list.h"

namespace rtx
{
	void Test_arrow()
	{
		struct Pos
		{
			int _a1;
			int _a2;

			Pos(int a1 = 0, int a2 = 0)
				:_a1(a1)
				, _a2(a2)
			{}
		};
		Pos aa;
		Pos* p2 = &aa;
		p2->_a1;
		p2->_a2;

		list<Pos> lt;
		lt.push_back(Pos(10, 20));
		lt.push_back(Pos(10, 21));

		list<Pos>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1 << "," << (*it)._a2 << endl;
			cout << it->_a1 << "," << it->_a2 << endl;
		    //实际要写,it->->_a1,但是编译器优化了一个箭头
			++it;
		}
		cout << endl;
	}

	//cout << it->_a1 << ":" << it->_a2 << endl;

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

	void Test_push_back()
	{
		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();
		print_list(lt);

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

int main()
{
	rtx::Test_push_back();
	//rtx::Test_arrow();

	return 0;
}

前面实现的迭代器都是原生指针。,写到 list 这才是涉及到了迭代器的精华。


3. list 的增删查改

在以前数据结构实现的时候说过,双向带头循环链表,

这个结构的优势就是只要实现insert和erase其它大多函数都能复用了

3.1 insert和头插尾插

 pos 位置插入,我们通过 pos 去找到前驱 prev,之后创建新结点,再进行 "缝合" 操作,

这个我们也用C语言实现过了,这里不再细说。

		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 push_back(const T& x)
		{
			//Node* tail = _head->_prev;
			//Node* NewNode = new Node(x);
			 思路图:head        tail  NewNode
			//tail->_next = NewNode;
			//NewNode->_prev = tail;
			//_head->_prev = NewNode;
			//NewNode->_next = _head;
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}

测试:

	void Test_insert()
	{
		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 pos = find(lt.begin(), lt.end(), 2);// 涉及其它问题,先不这样写
		//if (pos != lt.end())
		//{
		//	lt.insert(pos, 20);
		//}
		lt.insert(++lt.begin(), 20);
		lt.push_front(0);
		lt.push_front(-1);
		print_list(lt);
	}


3.2 erase和头删尾删

只需注意别删掉哨兵位头结点:

		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node; // 删cur
			Node* prev = cur->_prev;

			prev->_next = cur->_next; // cur前一个指向cur后一个
			cur->_next->_prev = prev; // cur后一个指回cur前一个

			delete cur;
			return iterator(prev->_next); // 返回删除位置下一个
		}
		void pop_back()
		{
			erase(begin());
		}
		void pop_front()
		{
			erase(--end());
		}

测试:

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

		lt.erase(++++lt.begin());//发现个好玩的,(删除3)
		lt.pop_back();
		lt.pop_front();
		print_list(lt);
	}


4.  list 的深浅拷贝

list 的同样涉及深浅拷贝问题,下面的拷贝构造是深拷贝还是浅拷贝?

	void Test_copy() 
	{
		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> lt2(lt);
		print_list(lt);
		print_list(lt2);
	}

程序没有崩,是深拷贝吗?不是的,这里默认生成的拷贝构造,还是浅拷贝,

有同学就会问了:难道是没有写数据?也不是,这里没崩仅仅是因为我们还没设计析构。

这里依然要自己去实现一个拷贝构造,去完成 "深拷贝" 。下面先实现一下析构:


4.1 clear 和析构 

写析构之前为了方便清空,我们先实现一下 clear ,然后复用一下,clear又可以复用erase,

实现了 clear 后,我们再去实现 list 的析构函数就很简单了。

我们只需要把哨兵位头结点 _head 给干掉就行了,并且记得要置成空指针。

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it); // 返回删除位置的下一个结点
			}
		}

再运行一下程序就崩溃了,虽然没报错,但是调试一下就报错了:

自动生成的拷贝构造是浅拷贝,为了解决这个问题,我们需要手动实现一个深拷贝的拷贝构造:


4.2 迭代器区间构造和交换

我们直接写现代写法,因为list本来就是提供迭代器区间初始化和交换函数的,

现在我们实现一下,并且拷贝构造的话至少保证有个头结点把,

所以我们把构造函数拎出来复用一下,写成一个empty_init 函数 (源码里也是这样写的),

这几个函数我们很熟了,直接放代码:

		void empty_init()// 创建并初始化哨兵位头结点(即构造函数)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		list()
		{
			empty_init();
		}
		void swap(list<T>& x)//提一嘴:void swap(list& x)也行(类里面可以省略<T>,类外不行>
		{
			std::swap(_head, x._head); // 换哨兵位头结点就行
		}

4.3 拷贝构造和赋值重载

在上面的基础上我们直接用现代写法:

		list(const list<T>& lt)//lt2(lt1)
		{
			empty_init();
			list<T> tmp(lt.begin(), lt.end()); // 迭代器区间构造一个(lt1)
			swap(tmp); // 直接和lt2换哨兵位头结点
		}
		list<T>& operator=(list<T> lt)//lt3 = lt1 这里lt1直接深拷贝给lt,lt是局部对象,出来作用域直接调析构
		{
			swap(lt);// 把深拷贝出来的lt和lt3交换
			return *this; // 把lt3返回
		}

测试一下:

	void Test_copy() 
	{
		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> lt2(lt);
		for (auto& e : lt2)
		{
			e *= 10;
		}
		print_list(lt);
		print_list(lt2);
	}


5. list相关选择题

1. 以下程序输出结果为( )

int main()
{
	int ar[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int n = sizeof(ar) / sizeof(int);
	list<int> mylist(ar, ar + n);
	list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5);
	reverse(mylist.begin(), pos);
	reverse(pos, mylist.end());
	list<int>::const_reverse_iterator crit = mylist.crbegin();
	while (crit != mylist.crend())
	{
		cout << *crit << " ";
		++crit;
	}
	cout << endl;
}

A.4 3 2 1 0 5 6 7 8 9

B.0 1 2 3 4 9 8 7 6 5

C.5 6 7 8 9 0 1 2 3 4

D.5 6 7 8 9 4 3 2 1 0


2. 下面程序的输出结果正确的是( )

int main()
{
	int array[] = { 1, 2, 3, 4, 0, 5, 6, 7, 8, 9 };
	int n = sizeof(array) / sizeof(int);
	list<int> mylist(array, array + n);
	auto it = mylist.begin();
	while (it != mylist.end())
	{
		if (*it != 0)
			cout << *it << " ";
		else
			it = mylist.erase(it);
		++it;
	}
	return 0;
}

A.1 2 3 4 5 6 7 8 9

B. 1 2 3 4 6 7 8 9

C.程序运行崩溃

D.1 2 3 4 0 5 6 7 8 9


3. 对于list有迭代器it, 当erase(it)后,说法错误的是( )

A.当前迭代器it失效

B.it前面的迭代器仍然有效

C.it后面的迭代器失效

D.it后面的迭代器仍然有效


4. 下面有关vector和list的区别,描述错误的是( )

A.vector拥有一段连续的内存空间,因此支持随机读取,如果需要高效的随机读取,应该使用vector

B.list拥有一段不连续的内存空间,如果需要大量的插入和删除,应该使用list

C.vector<int>::iterator支持“+”、“+=”、“<”等操作符

D.list<int>::iterator则不支持“+”、“+=”、“<”等操作符运算,但是支持了[]运算符


5. 下面有关vector和list的区别,描述正确的是( )

A.两者在尾部插入的效率一样高

B.两者在头部插入的效率一样高

C.两者都提供了push_back和push_front方法

D.两者都提供了迭代器,且迭代器都支持随机访问


6. 以下代码实现了从 list 中删除重复项的功能,请选择其中空白行应填入的正确代码( )

template<typename T>
void removeDuplicates(list<T>& aList)
{
	T curValue;
	list<T>::iterator cur, p;
	cur = aList.begin();
	while (cur != aList.end())
	{
		curValue = *cur;
		//空白行 1
		while (p != aList.end())
		{
			if (*p == curValue)
			{
				//空白行 2
			}
			else
			{
				p++;
			}
		}
	}
}

A. p=cur+1;aList.erase(p++);

B.p=++cur; p == cur ? cur = p = aList.erase(p) : p = aList.erase(p);

C.p=cur+1;aList.erase(p);

D.p=++cur;aList.erase(p);


答案:

1. C

分析:list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5); //找到5的位置

reverse(mylist.begin(), pos);//逆置0 1 2 3 4 为 4 3 2 1 0

reverse(pos, mylist.end()); //逆置5 6 7 8 9 为 9 8 7 6 5

逆置完成之后其数据为:4 3 2 1 0 9 8 7 6 5

list<int>::const_reverse_iterator crit = mylist.crbegin(); //反向迭代器进行反向访问

while(crit != mylist.crend()){}

所以答案为:5 6 7 8 9 0 1 2 3 4

2. B

分析:程序在使用迭代器取值时,如果不等于0就进行打印,为0时不打印并删除当前节点,且返回删除结点的下一个结点。

3. C

分析:删除节点后,只有指向当前节点的迭代器失效了,其前后的迭代器仍然有效,因为底层为不连续空间,只有被删除的   节点才会失效。

4. D

A.如果想大量随机读取数据操作,vector是首选的容器

B.如果想大量的插入和删除数据,list效率较高,是首选

C.由于vector底层是连续空间,其迭代器就是相应类型的指针,所以支持对应的操作

D.list迭代器不支持[]运算符

5. A

A.vector在尾部插入数据不需要移动数据,list为双向循环链表也很容易找到尾部,因此两者在尾部插入数据效率相同

B.vector头部插入效率极其低,需要移动大量数据

C.vector由于在头部插入数据效率很低,所以没有提供push_front方法

D.list不支持随机访问

6. B

分析:迭代p需要迭代不重复节点的下一节点,重要的是cur迭代器需要往下迭代,因此cur需要往前移动,二答案A C的cur都不会改变,空白行2是当需要找到重复值时进行节点删除,当删除时当前迭代器会失效,因此需要将迭代器p往后迭代。

6. 完整代码

list.h

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

namespace rtx
{
	template<class T>
	struct list_node // 定义结点
	{
		T _data;
		list_node* _prev;
		list_node* _next;

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

	template<class T, class Ref, class Ptr>
	struct __list_iterator // 定义迭代器
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;
		// 在list类里面:
		// typedef __list_iterator<T, T&, T*>             iterator;
        // typedef __list_iterator<T, const T&, const T*> const_iterator;
		Node* _node;

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

		bool operator!=(const iterator& it)
		{
			return _node != it._node;
		}
		//T& operator*()
		Ref operator*()
		{
			return _node->_data;  // 返回结点的数据
		}
		//T* operator->()
		Ptr operator->()
		{
			return &(operator*());
			//即 return &(_node->_data);
		}
		iterator& operator++()
		{
			_node = _node->_next;
			return *this; // 返回加加后的值
		}
		iterator operator++(int)
		{
			iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
			_node = _node->_next;
			return tmp; // 返回加加前的值
		}
		iterator& operator--()
		{
			_node = _node->_prev;
			return *this; // 返回减减后的值
		}
		iterator operator--(int)
		{
			iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
			_node = _node->prev;
			return tmp; // 返回减减前的值
		}
	};

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

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

		iterator begin()// begin是实际数据的第一个,_head 是哨兵位头结点
		{
			return iterator(_head->_next);
		}
		iterator end()// end是实际数据的下一个
		{
			return iterator(_head);
		}

		void empty_init()// 创建并初始化哨兵位头结点(即构造函数)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		list()
		{
			empty_init();
		}
		void swap(list<T>& x)//提一嘴:void swap(list& x)也行(类里面可以省略<T>,类外不行>
		{
			std::swap(_head, x._head); // 换哨兵位头结点就行
		}

		list(const list<T>& lt)//lt2(lt1)
		{
			empty_init();
			list<T> tmp(lt.begin(), lt.end()); // 迭代器区间构造一个(lt1)
			swap(tmp); // 直接和lt2换哨兵位头结点
		}
		list<T>& operator=(list<T> lt)//lt3 = lt1 这里lt1直接深拷贝给lt,lt是局部对象,出来作用域直接调析构
		{
			swap(lt);// 把深拷贝出来的lt和lt3交换
			return *this; // 把lt3返回
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it); // 返回删除位置的下一个结点
			}
		}

		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 push_back(const T& x)
		{
			//Node* tail = _head->_prev;
			//Node* NewNode = new Node(x);
			 思路图:head        tail  NewNode
			//tail->_next = NewNode;
			//NewNode->_prev = tail;
			//_head->_prev = NewNode;
			//NewNode->_next = _head;
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node; // 删cur
			Node* prev = cur->_prev;

			prev->_next = cur->_next; // cur前一个指向cur后一个
			cur->_next->_prev = prev; // cur后一个指回cur前一个

			delete cur;
			return iterator(prev->_next); // 返回删除位置下一个
		}
		void pop_back()
		{
			erase(begin());
		}
		void pop_front()
		{
			erase(--end());
		}

	private:
		Node* _head; // 哨兵位头结点
	};
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "list.h"

namespace rtx
{
	void Test_arrow()
	{
		struct Pos
		{
			int _a1;
			int _a2;

			Pos(int a1 = 0, int a2 = 0)
				:_a1(a1)
				, _a2(a2)
			{}
		};
		Pos aa;
		Pos* p2 = &aa;
		p2->_a1;
		p2->_a2;

		list<Pos> lt;
		lt.push_back(Pos(10, 20));
		lt.push_back(Pos(10, 21));

		list<Pos>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1 << "," << (*it)._a2 << endl;
			cout << it->_a1 << "," << it->_a2 << endl;
		    //实际要写,it->->_a1,但是编译器优化了一个箭头
			++it;
		}
		cout << endl;
	}

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

	void Test_push_back()
	{
		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();
		print_list(lt);

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

	void Test_insert()
	{
		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 pos = find(lt.begin(), lt.end(), 2);// 涉及其它问题,先不这样写
		//if (pos != lt.end())
		//{
		//	lt.insert(pos, 20);
		//}
		lt.insert(++lt.begin(), 20);
		lt.push_front(0);
		lt.push_front(-1);
		print_list(lt);
	}

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

		lt.erase(++++lt.begin());//发现个好玩的,(删除3)
		lt.pop_back();
		lt.pop_front();
		print_list(lt);
	}

	void Test_copy() 
	{
		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> lt2(lt);
		for (auto& e : lt2)
		{
			e *= 10;
		}
		print_list(lt);
		print_list(lt2);
	}
}

int main()
{
	//rtx::Test_push_back();
	//rtx::Test_arrow();
	//rtx::Test_insert();
	//rtx::Test_erase();
	rtx::Test_copy();

	return 0;
}

本篇完。

list 的反向迭代器放在后面栈和队列期间讲,下一部分:栈和队列:使用,OJ,模拟实现。

穿越回来复习顺便贴个下一篇的链接:从C语言到C++_18(stack和queue的常用函数+相关练习)力扣_GR_C的博客-CSDN博客

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GR鲸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值