list

89 阅读5分钟

stl的list是带头双向循环链表

list的特殊成员函数

--splice

(1)void splice (iterator position, list& x);

将链表x上的所有结点,转移给当前链表的position前一个位置

		//lt1 :1 2 3 4
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);

		//lt2 :5 6 7 8
		list<int> lt2;
		lt2.push_back(5);
		lt2.push_back(6);
		lt2.push_back(7);
		lt2.push_back(8);


		list<int>::iterator it1 = lt1.begin();
		//1 void splice(iterator pos, list& x)
		//将x链表的数据全部转移给某个链表的pos之前
		lt1.splice(it1, lt2);
		//此时 lt1:5 6 7 8 1 2 3 4

		cout << "lt1的数据:";
		for (auto x : lt1)
			cout << x << " ";
		cout << endl;

		cout << "lt2的数据:";
		for (auto x : lt2)
			cout << x << " ";
		cout << endl;

image.png

(2) void splice (iterator position, list& x, iterator i);

将x链表位于迭代器位置i的结点,转移到当前链表的position前

	// lt1:5 6 7 8 1 2 3 4
	// lt2:10 11
	lt2.push_back(10);
	lt2.push_back(11);

	//将x链表的i位置数据,转移给pos之前
	//2 void splice(iterator pos, list& x, iterator i)

	//将lt1第一个迭代器的数据,转移给lt2首元素之前
	lt2.splice(lt2.begin(), lt1, lt1.begin());

	cout << "lt1的数据:";
	for (auto x : lt1)
		cout << x << " ";
	cout << endl;

	cout << "lt2的数据:";
	for (auto x : lt2)
		cout << x << " ";
	cout << endl;

image.png

(3) void splice (iterator position, list& x, iterator first, iterator last);

将x链表的某个迭代器区间[ 左闭右开 ), 转移到当前链表的pos之前

		//lt1:6 7 8 1 2 3 4
		//lt2:5 10 11
		//将x链表的某个迭代器区间[first, last),转移到pos之前
		//3 void splice(iterator pos, list& x, iterator first, iterator last)

		//将lt1的7~3所有结点(不包含3),转移给lt2头部之前
		list<int>::iterator first = find(lt1.begin(), lt1.end(), 7);
		list<int>::iterator last = find(lt1.begin(), lt1.end(), 3);
		if (first != lt1.end() && last != lt1.end())
			lt2.splice(lt2.begin(), lt1, first, last);

		cout << "lt1的数据:";
		for (auto x : lt1)
			cout << x << " ";
		cout << endl;

		cout << "lt2的数据:";
		for (auto x : lt2)
			cout << x << " ";
		cout << endl;

image.png

--remove

void remove (const T& val);

删除链表中所有值为val的结点

		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(1);
		lt.push_back(4);
		lt.push_back(1);
		//void remove(const T& val)
		lt.remove(1);
		for (auto x : lt)
			cout << x << " ";
		cout << endl;

--sort

(1) 算法库里的sort用的是快速排序,必须满足空间连续才能使用,例如vector和string

(2) 链表的sort用的是归并排序,归并对数组需要开空间,对链表不需要开额外的空间.

(3) 涉及高速缓存命中率,vector连续的物理空间会让访问数据的速度更快

一组数据,【list + sort 排序】 不如用 【vector + 算法库sort排序】

list<int> lt1;
vector<int> v1;
srand((unsigned)time(nullptr));
int num = 1000000;
//产生num个随机数,把它们分别插入到list和vector中,比较排序的时间
for (int i = 0; i < num; ++i)
{
	int insert = rand() % 100000;
	lt1.push_back(insert);
	v1.push_back(insert);
}

//链表的排序
clock_t beginList = clock();
lt1.sort();
clock_t endList = clock();

//算法库的排序
clock_t beginVector = clock();
sort(v1.begin(), v1.end());
clock_t endVector = clock();

cout << "链表sort时间:" << endList - beginList << endl;
cout << "算法库sort时间:" << endVector - beginVector << endl;

image.png

list模拟实现

--总览

(1) 在sgi版的stl中,它的成员变量只有一个头结点指针:

image.png

(2) 链表结点的类型声明:

image.png

(3) list的迭代器类型不是原生指针:

image.png

--迭代器的实现(重点)

容器使用迭代器进行遍历的代码:

	void testMyList1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);

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

如果list的迭代器使用原生指针实现,即用链表的结点指针作为迭代器,会出现以下问题:

(1) 链表的各个结点位置一般不是连续的,++it无法找到下一个结点的位置

(2) it是Node*类型,结点指针,*it不能直接取得结点存的数据.

因此,list的迭代器是一个单独的类型,然后在该类型内部运算符重载++、*、!=

普通迭代器类型

(1) 我们用链表的一个结点指针作为迭代器的成员变量

template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;      //简化结点类型写法
	typedef ListIterator<T> Iterator;//简化迭代器类型写法
	Node* _node;

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

(2) 在内部重载++、!=、*运算符

//要控制!= ++ 和 *,在迭代器类型里重载这些运算符
bool operator!=(const Iterator& it)
{
	//比较结点地址是否相同
	return this->_node != it._node;
}

//前置++
//让当前迭代器指向下一个结点
Iterator& operator++()
{
	_node = _node->_next;
	return *this;
}

//对迭代器类型进行*,返回的是结点保存的数据引用,可读可写
T& operator*()
{
	return  _node->_date;
}		

(3) 特殊:->运算符

使用迭代器遍历数据时,我们可以把迭代器直接当成数据的地址来使用.

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

如果list内部存的数据,是结构体类型呢?

	struct A
	{
		int _a;
		int _b;
		A(int a = 0, int b = 0)
			:_a(a)
			,_b(b)
		{}
	};

	//测试迭代器内重载的operator->()
	void testMyList3()
	{
		list<A> lt;
		lt.push_back(A(0, 0));
		lt.push_back(A(1, 1));
		lt.push_back(A(2, 2));

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

*it的结果是A类型数据,无法用cout直接打印

能否支持以下使用:

std::cout << it->_a << " " << it->_b<< std::endl;

用起来就像it是结点内数据的指针

          //在迭代器类型内部重载->运算符,用于链表存放结构体类型的清况
		T* operator->()
		{
			return &(this->operator*());
		}

代码解析:

(a) 它是迭代器类型的成员函数,只有迭代器类型对象才能调用它.

(b) this->operator*(),返回该迭代器指向的具体数据

(c) 外面加&,最终返回 【迭代器指向的数据】的地址

(d) it -> _a 本质是it -> -> _a

it->调用it.operator->(),返回A数据地址

然后再 结构体地址->结构体成员.

编译器为了可读性,这块地方特殊处理,不需要写2个箭头

const迭代器类型

使用const迭代器遍历的例子:

	//专门用来打印链表数据的函数
	void testMyList2(const list<int>& lt)
	{
		//const对象会调用const迭代器
		list<int>::const_iterator cit = lt.begin();
		while (cit != lt.end())
		{
			std::cout << *cit << " ";
			++cit;
		}
	}

const迭代器和普通迭代器区别是const迭代器不能用*或者->修改数据,但普通迭代器可以,

迭代器的operator*()只有返回值T&和const T&不同,

operator->()只有返回值T和const T不同,

把返回值作为模板参数传入,可以在list中通过传T&或者const T&等确定迭代器类型

		//const迭代器和普通迭代器复用同一个迭代器类型,将不同的返回值作为模板参数传入
		typedef ListIterator<T,T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;

迭代器整体代码

list迭代器类型:

	template<class T,class Reference, class Pointer>
	struct ListIterator
	{
		typedef ListNode<T> Node;                            //简化结点类型
		typedef ListIterator<T,Reference, Pointer> Iterator; //简化迭代器类型
		Node* _node;


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

		//要控制!= ++ 和 *,在迭代器类型里重载这些运算符
		bool operator!=(const Iterator& it)
		{
			//比较结点地址是否相同
			return this->_node != it._node;
		}

		//前置++
		//让当前迭代器指向下一个结点
		Iterator& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		//后置++
		//让当前迭代器指向下一个结点,返回++之前的迭代器
		Iterator operator++(int)
		{
			Iterator ret = *this;
			_node = _node->_next;
			return ret;
		}

		//前置--
		//让当前迭代器指向前一个结点
		Iterator& operator--()
		{
			_node = _node->_pre;
			return *this;
		}

		//后置--
		//让当前迭代器指向前一个结点,返回--之前的迭代器
		Iterator operator--(int)
		{
			Iterator tmp = *this;
			_node = _node->_pre;
			return tmp;
		}

		//对迭代器类型进行*,返回的是结点保存的数据引用
		Reference operator*()
		{
			return  _node->_date;
		}

		//it->调用it.operator->()
		//返回数据的指针,用于T是结构体类型的情况
		Pointer operator->()
		{
			return &( operator*() );
		}
	};

list内部:

		//const迭代器和普通迭代器复用同一个迭代器类型,将不同的返回值作为模板参数传入
		typedef ListIterator<T,T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
		iterator begin()
		{
			return iterator(_head->_next);
		}
		
		const_iterator begin()const
		{
			return const_iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

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

--在任意位置插入删除

		//删除pos位置的结点,返回删除结点的下一个结点位置
		iterator erase(iterator pos)
		{
			Node* cur = pos._node;//当前要删除的结点
			Node* pre = cur->_pre;
			Node* next = cur->_next;
			
			//链接pre和next
			pre->_next = next;
			next->_pre = pre;
			
			//删除结点
			delete cur;

			return iterator(next);
		}

		//在pos位置前插入val,返回新插入结点的迭代器位置
		iterator insert(iterator pos, const T& val)
		{
			Node* newNode = new Node(val);
			Node* next = pos._node;
			Node* pre = next->_pre;

			//链接newNode和pre
			newNode->_pre = pre;
			pre->_next = newNode;

			//链接newNode和next
			newNode->_next = next;
			next->_pre = newNode;

			return iterator(newNode);
		}

--构造函数

默认构造

		//构造函数,初始化头结点,并让next和pre指向自己
		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_pre = _head;
		}

迭代器区间构造

		//用一段迭代器区间来构造链表
		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			//由于是模板,可以用vector、list、string等区间来构造
			_head = new Node;
			_head->_next = _head;
			_head->_pre = _head;

			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

拷贝构造

list的拷贝构造是深拷贝

方法一: 通过遍历已有链表lt,将数据不停尾插给新链表.

方法二:复用迭代器区间构造一个tmp,交换tmp与新链表的成员变量,

必须保证tmp所得头结点有效

		//拷贝构造1
		list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_pre = _head;

			const_iterator it = lt.begin();
			while (it != lt.end())
			{
				push_back(*it);
				++it;
			}
		}

		//拷贝构造2
		list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_pre = _head;

			list<T> tmp(lt.begin(), lt.end());

			//直接交换哨兵位头结点
			std::swap(tmp._head, _head);
		}

--析构函数

clear()用于清空有效结点,clear()调用erase()释放结点空间.

		//析构函数
		~list()
		{
			clear();
			delete _head;
		}

		//清空结点,头结点除外
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

--赋值运算符重载

形参进行拷贝构造,把需要的数据交给形参lt1,

然后直接交换形参和当前对象lt2的成员变量.

形参lt1销毁时也会顺便把原空间带走.

		//=运算符重载 lt2 = lt1
		list<T>& operator=(list<T> lt1)
		{
			//交换哨兵位头结点
			std::swap(lt._head, _head);
			return *this;
		}
	//测试各种构造函数 以及 =运算符重载
	void testMyList5()
	{
		std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		
		//迭代器区间构造
		list<int> lv(v.begin(), v.end());
		for (auto x : lv)
			std::cout << x << " ";
		std::cout << std::endl;

		//拷贝构造
		list<int> lt2 = lv;
		for (auto x : lt2)
			std::cout << x << " ";
		std::cout << std::endl;

		//赋值运算符重载
		list<int> lt3;
		lt3.push_back(9);
		lt3.push_back(19);
		lt2 = lt3;
		for (auto x : lt2)
			std::cout << x << " ";
		std::cout << std::endl;
	}

image.png