天天看点

STL--> list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

使用STL vector 接口:

#include <iostream>
#include <vector>

using namespace std;

void TestVector( )
{
	
	vector<int> v1;

	v1.push_back( 1 ); 
	v1.push_back( 2 ); 
	v1.push_back( 3 ); 
	v1.push_back( 4 );	
	
	PrintVector( v1 );
	
	vector<int> v2( 3, 10 );
	PrintVector( v2 );
}

int main( )
{
	TestVector( );

	return 0;
}
           
//封装是为了复用,调用这个函数即可,不用每次都写打印代码
void PrintVector( vector<int>& v1 )//引用           //加 const 编译不通过   如果要编译通过 1.普通迭代器(可读,可写)   2.const 迭代器(可读)  3.反向迭代器(反着遍历)
{	
	//迭代器 是 类似指针的一个对象  ->   和智能指针相似     
	//[begin, end) 迭代器是 左闭右开 区间 

	vector<int>::iterator it/*迭代器对象*/;			//如果用const_iterator   则   ++(*it)  会报错, 因为不能给常量赋值
	it = v1.begin( );

	while ( v1.end( ) != it )
	{
		//1.打印
		cout << *it << " ";
		++it;

		//2.打印奇数
		//if ( 0 != *it % 2 )
		//{
		//	cout << *it << " ";
		//}
		/*++it;*/

		//3.每个数加 1
		//++(*it);
		//++it;
	}

	cout << endl;

	//反向迭代器 是 倒着走
	vector<int>::const_reverse_iterator rIt = v.rbegin( );

	while ( v.rend() != rIt )
	{
		cout << *rIt << " ";

		++rIt;
	}

	cout << endl;
}
           

list:

#include <iostream>
#include <list>

using namespace std;

void TestList( );
void PrintList( list<int>& l );

int main( )
{
	TestList( );

	return 0;
}

void TestList( )
{
	list<int> l1;

	l1.push_back( 1 );
	l1.push_back( 2 );
	l1.push_back( 3 );
	l1.push_back( 4 );

	list<int> l2( 5, 1 );						//带值的初始化
}

void PrintList( /*const*/list<int>& l )
{
	list<int>::/*const_*/iterator it = l.begin( );

	while ( l.end( ) != it )
	{
		cout << *it << " ";
		/* *it = 10; *///如果是	const迭代器 则不能赋值
		++it;
	}

	cout << endl;
}
           

模拟实现:STL 中 名字为 list, 我们模拟实现名字为 List

:

//链表的四次实现
//1.C ->	 基本实现和面试题
//2.C++ -> 面向对象  C 和 C++区别  深拷贝
//3.C++ -> 泛型编程  +  T  -- string
//4.数据结构 -- STL
//搜索 ctrl + f  -> 快捷键查找F3 -> 倒着搜索 shift + F3
//名称 小写加下划线

#pragma once//防止重定义


#include "Iterator.h"//为了实现 List 反向迭代器


template<class T>
struct __ListNode
{
	T _data;
	__ListNode<T>* _next;
	__ListNode<T>* _prev;
	
	__ListNode( const T& x )
		:_data( x )
		,_next( NULL )
		,_prev( NULL )
	{}
};

//迭代器   是一个 类  通过 运算符重载  使它像指针一样使用
//T T& T*   有了 T 为什么 还有传 T&, T*  ,为了实现 const迭代器
//T const T&  const T*
template<class T, class Ref, class Ptr>
struct __ListIterator   // sizeof(__ListIterator)  == 4  因为它里面还是一个指向节点的指针,只是通过封装1让它变为新的类型, 大小不变,为4
{
	typedef __ListNode<T> Node;
	typedef __ListIterator<T, Ref, Ptr> Self;
	typedef Ref Reference;//反向迭代器
	typedef Ptr Pointer;//反向迭代器

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

	Ref operator*( )//1.出了作用域存在,所以传&。而且为了效率,  如果不传引用,  string对象, 深拷贝。 2.  引用是为了可以更改数据
	{
		return _node->_data;
	}

	const Ref operator*( )//const 迭代器 中 用 const 修饰 Ref即可。
	{
		return _node->_data;
	}

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

	Self& operator++( )//前置++			//注意后置 前置的区别
	{
		_node = _node->_next;

		return *this;
	}	
	
	Self operator++( int )//后置++  不能给& 因为出了作用域对象不存在,不能给引用
	{
		Self tmp( _node );//构造                          1
		//Self tmp( *this );//拷贝构造                    2
		_node = _node->_next;

		return tmp;
	}//   1 or 2 时 构造 or 拷贝构造, 然后 return 时, 再拷贝构造个临时对象返回。

	Self& operator--( )
	{
		_node = _node->_prev;

		return *this;
	}
		
	Self operator--( int )
	{
		Node* cur = _node;
		_node = _node->_prev;

		return Self( cur );//先构造出一个对象,再拷贝构造出临时对象返回,但是他们是在返回步骤的一个表达式里,编译器会进行优化 -> 不会先构造再拷贝构造而是  直接构造临时对象   作为返回值,  所以 这里的代码 比 后置++实现的好.
	}

	bool operator!=( const Self& s ) const
	{
		return /*this->*/_node != s._node;//指针全是通过->调用,调用函数也是->。  对象全是 通过.调用
	}

	Node* _node;
};

template<class T>
class List
{
	typedef __ListNode<T> Node;

public:
	typedef __ListIterator<T, T&, T*> Iterator;
	typedef __ListIterator<T, const T&, const T*> ConstIterator;//实现 const 迭代器
	typedef ReverseIterator<ConstIterator> ConstReverseIterator;//注意这一句和后一句顺序不能颠倒, 否则, 最后一句 的ReverseIterator 已经被 typedef 这里就用不了了
	typedef ReverseIterator<Iterator> ReverseIterator;
	//此时只能实现 List 的反向迭代器, Vector的反响迭代器不能这样实现, 需要类型萃取才可以!


	Node* GetNode( const T& x )
	{
		return Node* node = new Node( x );
	}

	List( )
	{
		_head = GetNode( T( )/*匿名对象*/ );
		_head->_next = _head;
		_head->_prev = _head;
	}

	~List( )
	{
		Clear( );//清掉数据 。 but 析构还没完, 除了清掉数据还要把整个链表全销毁了

		delete _head;
		_head = NULL;
	}

	Iterator Begin( )//const对象不能调用 普通成员函数
	{
		return Iterator( _head->_next );
	}	
		
	//不能这样写
	//Iterator Begin( ) const
	//{
	//	return Iterator( _head->_next );
	//}	
	//const 成员函数 , 被 const 对象调用  -->  不可修改
	//应该返回 const 迭代器
	ConstIterator Begin( ) const
	{
		return ConstIterator( _head->_next );
	}	
	
	Iterator End( )
	{
		return Iterator( _head );
	}	

	ConstIterator End( ) const
	{
		return ConstIterator( _head );
	}

	 ReverseIterator RBegin( )//如图, 正向迭代器和反向迭代器的 begin 和 end 完全对应起来!  也由此图理解解引用  -->  为什么取前一个位置数据。   Node*  -->  正向迭代器  -->  反向迭代器
	 {
		return ReverseIterator( End( ) );
	 }

	 ReverseIterator REnd( )
	 {
		return ReverseIterator( Begin( ) );
	 }

	 ConstReverseIterator RBegin( ) const
	 {
		return ConstReverseIterator( End( ) );
	 }

	 ConstReverseIterator REnd( ) const
	 {
		return ConstReverseIterator( Begin( ) );
	 }

	void PushBack( const T& x )
	{
		//我们以前实现方法
		//Node* tail = _head->_prev;
		//Node* tmp = GetNode( x );


		//tail->_next = tmp;
		//tmp->_prev = tail;

		//tmp->_next = _head;
		//_head->_prev = tmp;

		//STL 实现方法
		Insert( End( ), x );
	}

	void PushFront( const T& x )
	{
		Insert( Begin( ), x );
	}

	void PopBack( )
	{
		//assert( !Empty( ) );//不为空才行,   否则不让你删除

		//Node* tail = _head->_prev;
		//Node* prev = tail->_prev;
		//
		//delete tail;

		//prev->_next = _head;
		//_head->_prev = prev;
		
		Erase( _head->_prev );
		Erase( --End( ) );
	}

	void PopFront( )
	{
		Erase( Begin( ) );
	}

	void Clear( )
	{
		Iterator it = Begin( );//注意不用写为: List<int>::Iterator  因为这是类里面, Iterator就是属于这个类域的

		while ( End( ) != it )
		{
			Node* del = it._node; //注意  为.  而不是  ->
			++it;

			delete del;
		}

		_head->_next = _head;		//别忘了 ,还需要重置
		_head->_prev = _head;
	}

	Iterator Find( const T& x )
	{
		//Node* cur = _head->_next;

		//while ( _head != cur )
		//{
		//	if ( x == cur->_data )
		//	{
		//		return cur;//隐式类型转换, Iterator为单参数构造函数。 用 cur 构造临时对象发货
		//		//return Iterator( cur );
		//	}
		//	else
		//	{
		//		cur = cur->_next;
		//	}
		//	//如string s = "1111", 一个是c风格字符串, 一个是字符串, 即用了 隐式类型转换

		//	return _head;
		//}

		Iterator it = Begin( );  //成员函数可以调用其他成员函数 是因为有隐含this指针

		while ( End( ) != it )
		{
			if ( x == *it )
			{
				return it;
			}
			else
			{
				++it;
			}
		}

		return it;							//这里返回的 是 End这个位置
	}

	//在 pos 的前面进行插入
	void Insert( Iterator pos, const T& x )
	{
		assert( pos._node );//pos这个迭代器中的结点不能为空

		Node* next = pos._node;
		Node* prev = next->_prev;
		
		Node* cur = new Node( x );

		prev->_next = cur;
		cur->_prev = prev;

		cur->_next = next;
		next->_prev = cur;
	}

	//测试考虑到:1。正常情况, 2.边界情况(头,尾,空)
	//但这种情况会引起我们下面提出的问题,迭代器失效, 所以这种方案不好, 采用下面的方案
	//void Erase( Iterator pos )
	//{
	//	assert( (End( ) != pos) && (NULL != pos._node) );//等于 End( )  相当于删除 头节点  -->   1.没有结点情况  2.给了非法的位置 ,如有多个结点,你却给了我头节点。

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

	//	delete del;

	//	prev->_next = next;
	//	next->_prev = prev;
	//}
		
	Iterator Erase( Iterator pos )
	{
		assert( (End( ) != pos) && (NULL != pos._node) );

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

		delete del;

		prev->_next = next;
		next->_prev = prev;

		//解决迭代器失效方法, 返回 删除位置下一个位置的迭代器
		return next;							//单参数构造函数 进行 隐式转换
	}

	bool Empty( )
	{
		return _head == _head->_next;
	}

	//[)
	//模板类中成员函数也可以给成 模板函数
	template <class InputIterator>
	void Assign( InputIterator first, InputIterator last )
	{
		Clear( );

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

protected:
	Node* _head;
};


struct AA
{
	int _a1;
	int _a2;
};


template<template T>
void PrintList( const List<T>& l1 )//因为只是打印, 所以你不应该修改我  传const对象
{//const 对象 应该调用 const迭代器
	List<int>::Iterator it = l1.Begin( );//const对象 不能调用 非const 的成员函数 Begin( )  -> 加一个const的成员函数 Begin( )返回const 迭代器
	//这里应该是 const 迭代器.
	while ( l1.End( ) != it )
	{
		cout << *it << " ";

		++it;
	}

	cout << endl;
}


template<template T>
void PrintList( const List<T>& l1 )
{
	List<int>::Iterator it = l1.Begin( );//假设const对象调用const 成员函数,但返回普通迭代器

	while ( l1.End( ) != it )
	{
		*it = 10;//const 对象, 理论上里面数据不能修改, 但是这里可以修改, 这不合理。  所以 把普通迭代器 ->  const 迭代器  :
		//此时(const迭代器)编译报错,  不能给常量赋值 
		cout << *it << " ";

		++it;
	}

	cout << endl;
}
           
void TestList( )
{
	List<int> l1;

	l1.PushBack( 1 );
	l1.PushBack( 2 );
	l1.PushBack( 3 );
	l1.PushBack( 4 );

	//迭代器
	List<int>::Iterator it = l1.Begin( );拷贝构造  ->  这里是浅拷贝(没毛病!)

	while ( l1.End( ) != it )//it != l1.End( )  ->   it.operator!=( &it/*传 this 指针*/, l.End( ) )  但是像我那样写可以吗?
	{//判断条件若为小于 or 不等于 则是 左闭右开 区间
		cout << *it << " ";

		++it;
	}

	cout << endl;


	List<AA> l2;
	l2.PushBack( AA( ) );
	l2.PushBack( AA( ) );

	List<AA>::Iterator it2 = l2.Begin( );

	while ( l2.End( ) != it2 )
	{
		//cout << *it2 << " ";//此时 *it2 -> 是AA  (它是自定义类型,不是内置类型 ,库里面不支持它的输出)

		cout << it2->_a1 << " ";//迭代器中包含指向节点的指针 迭代器通过 重载 operator->( ), 它成为指向节点中数据的指针
		//(it2.operator->( ))(指向节点中数据(AA)的指针) ->    _a1;
		//编译器做的就是 把 it2->_a1  解释为  it2.operator->( )  ->_a1
		//(it2.operator->( )) == it2->   然后 ,如果要访问a1  应该是:  it2->->_a1   但这样可读性不好,所以编译器做了优化,只需要一个 ->.
		//operator->(这是函数名)  隐含了一个参数 this指针,但是不需要我们传

		++it2; 
	}

	cout << endl;

	List<int>:;Iterator pos = l1.Find( 3 );

	l1.Insert( pos, 30 );
}
           

C++中:

左右都存在 -> 赋值  

左不存在 -> 拷贝构造

*it = 10;

it 如果是普通迭代器 调用 operator*( ) 返回 Ref 我也不知道 Ref 是谁。  

但如果是普通迭代器 返回的是 T&  

const迭代器 则返回 const T&.

*it  返回_node->_data(Ref)       或者AA&   如何访问AA的成员呢.

(*it2)._a1   对象.成员   指向对象指针->成员    我们把迭代器想象为指向结点中数据(此时为AA)的指针。   如指向AA的指针 则it2->_a1更形象 所以迭代器支持 operator->( )

(it2.operator->( ))->_a1;   const迭代器,(it2.operator->( )) 返回 const T*    所以此时_a1不能赋值。 const 修饰指针指向内容。

const迭代器用于const对象, 如Print函数 我传const对象,是不想让你修改,你只遍历访问就可以了。 但我要传引用,而不是传值(因为拷贝构造代价太大). 但引用我又不能让你改变我。 所以 传 const List<int>& l1 传 const迭代器(如果是普通迭代器, 则虽然是const对象,但还是可以修改里面数据)  因为这个const只能修饰这个对象及对象成员(_node),但const不能修饰到 _node后面链接的结点。 如果普通迭代器取到后面结点, 依旧修改你的数据。这样不合理。 所以传 const迭代器。  const对象调用 const 成员函数Begin() 返回const迭代器。

const 对象 ->(使用) const 迭代器。  const对象调用成员函数Begin( )时是调用 const版本 Begin() -> 它返回const迭代器,也限制保证了const对象不能使用普通迭代器   不会出错。

string s( "1" );//拷贝构造

string s = "21213";  -> (隐式转换)先构造,再拷贝构造 但是编译器优化后 就是一个构造了.   凡是 返回值 ,赋值, 传参 在一个步骤里面时, 凡是临时对象时, 编译器都会把它优化成一个步骤。

STL 中  empty -> 时间复杂度O(1)   size ->时间复杂度 O(n)  

链表中别写出这样的代码

for ( size_t i = 0; i < l.size( ); ++i ){ }   不好的代码  时间复杂度为O(n*n)   因为每次  l.size( )  都会遍历链表一遍

list中接口介绍:

max_size( )    size_t( -1 )会转换成无符号最大的数;   最多可以有多少字节

pop_front/push_front 都是调用 Insert 和 Erase 实现

resize 更改大小      你的数据多则删除数据,  你的数据少则补充数据, 多出来的位置给缺省值或你传的值

cleae 清掉数据,不清头节点

assign:    

list<int> l1;  l1.push_back( 1 );    -----   list<int> l2;      

template<class InputInterator>

void assign( InputIterator first, InputIterator lase );

可以认为InputInterator就是一个迭代器, 因为它是一个模板函数

l2.assign( l1.begin( ), l1.end( ) );  迭代器区间, 左闭右开

PrintList( l1 );

PrintList( l2 );

也就是把我数据给你, 同时给数据也可以进行选择, 如   ++l1.begin( ), --l1.end( )  也就是给迭代器区间,

vectot<int> v; //可以给不同迭代器,但迭代器里必须是相同数据类型(如这里必须是 int  数据类型必须匹配)

v.push_back( 10 );

12.assign( v.begin( ), v.end( ) ); //也就是说 它是模板函数 根据实际实际类型进行推演 迭代器类型

remove  删除值   erase 位置

STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器
STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

尾插时: 原 双向循环链表 有结点 和 没有结点 两种情况分析如图:

1.有结点

STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

2.没有结点:

STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

如图分析可知,两种情况是一样的,可以用相同的逻辑处理尾插操作,而不需要分别处理。

尾删时:

STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

Insert 在指定位置pos的前面插入数据 和 

erase 删除指定位置的数据:

STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

迭代器失效:比如我们上面代码中,第一次实现的erase方法,返回void,  即没有返回值, 此时++it,程序会挂,这就是迭代器失效: 

此时,如图:

STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

STL 中解决迭代器失效采用的方法是, erase方法会返回一个迭代器, 这个迭代器是被删除位置的下个位置的迭代器

但如下使用方式就有问题:

list<int>::iterator it = l1.begin( );

while ( l1.end( ) != it )
{
	if ( 0 == *it % 2 )
		l1.erase( it );

	++it;
}
           

此时程序会跳过一些数据。

正确写法:

while ( l1.end( ) != it )
{
	if ( 0 == *it % 2 )
		it = l1.erase( it );
	else
		++it;
}
           

如果, 现在就是失效了, 怎么解决!即我们代码就是这样:

while ( l1.end( ) != it )

{

if ( 0 == *it % 2 )

l1.erase( it );

++it;

}

此时需要做的,就是修改 erase方法:

Iterator Erase( Iterator& pos )

{

assert( (End( ) != pos) && (NULL != pos._node) );

Node* del = pos._node;

Node* prev = del->_prev;

Node* next = del->_next;

delete del;

prev->_next = next;

next->_prev = prev;

pos = prev;//2

return next;

}

迭代器 用 * 和 -> 修改数据

STL算法中去重:

STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

测试各种list各种接口:

void TestList1( )
{
	list<int> l1;

	l1.push_back( 3 );
	l1.push_back( 2 );
	l1.push_back( 1 );
	l1.push_back( 0 );
	l1.push_back( 4 );

	l1.sort( );//默认升序, 降序需要给一个仿函数
	l1.sort( less<int>( ) );//   less 小于  <  即升序     greater  大于 >  降序 

	l1.unique( );//去重   逻辑如图

	l1.reverse( );//逆置

	l1.remove( 2 );//remove 删不存在元素 没什么影响(现象).

	//erase 需要 配合 Find(即返回它一个位置)  但 List 没Find方法  解决方法  -->  算法  用 STL 算法 find
	list<int>::iterator pos = find( l1.begin( ), l1.end( ), 4 );//#include <algorithm> 
	//l1.erase( pos );							//删除不存在元素时, 程序会挂, 所以像下面一样写

	if ( l1.end( ) != pos )
	{
		l1.erase( pos );
	}

	PrintList( l1 );
}
           

注意我们模拟实现list时:一定不能建立一个 list.h  再建立一个 list.cpp因为模板不能分离编译。

反向迭代器实现   --->   Iterator.h:

//反向迭代器的++, 即正向迭代器的 --
//所以, 利用适配器模式实现反向迭代器
//STL 中 反向迭代器 模板参数是 Iterator  -->  即你传我 什么容器的迭代器, 我就变成什么容器的反向迭代器
//32 位系统下指针 是4字节。 64位系统下, 8字节或4字节(关键看你是 32 位程序 还是 64位程序)。
//Node* 4字节, Iterator 包含封装Node* 但不增加它大小, 也是4字节。 从意义上变为新的类型。 反向迭代器也是4字节。


#pragma once


//class 和 typename 区别
template <class/*改为typename*/ Iterator>//编译错误 , 因为下面用到Iterator::Reference。  它不认识Iterator, 因为Iterator是模板, 所以没实例化前没有具体类型, 编译器找不到 Reference。  我们需要在 前面加 typename 才行
class ReverseIterator
{
protected:
	Iterator _current;

	typedef ReverseIterator<Iterator> Self;

public:
	ReverseIterator( Iterator it )
		: _current( it )
	{}

	typename Iterator::Reference operator*( )//编译器找不到Reference,但是在其它地方可以找到, 我们帮助编译器确认这个事情, 所以加 typename( 取模板类的内嵌类型时(没有实例化, 没有生成代码, 编译器不认识它) )
	{
		Iterator tmp = _current;

		return *( --tmp );
		//之所以要这样实现,如图,为了和正向迭代器的 begin 和 end 位置对应起来, 所以要取到第一个数据( 4 )时  --->  *( --rbegin( ) )
	}

	typename Iterator::Pointer operator->( )
	{
		return &operator*( );
	}

	Self& operator++( )//前置
	{
		--_current;

		return *this;
	}

	Self operator++( int );//后置  返回++前的临时对象, 所以不用引用

	Self& operator--( )
	{
		--_current;

		return *this;
	}

	Self operator--( int );

	bool operator!=( const Self& s )
	{
		return _current != s._current;
	}
};


void PrintMyListR( List<int>& l1 )
{
	List<int>::ReverseIterator rit = l1.RBegin( );

	while ( l1.REnd( ) != rit )
	{
		cout << *rit << " ";
		++rit;
	}

	cout << endl;
}

void PrintMyListR( const List<int>& l1 )
{
	List<int>::ConstReverseIterator rit = l1.RBegin( );

	while ( l1.REnd( ) != rit )
	{
		cout << *rit << " ";
		++rit;
	}

	cout << endl;
}
           
STL--&gt; list 双向循环链表容器 接口使用及介绍。 模拟实现 STL list容器

继续阅读