C++入门List类使用介绍 + 进阶【模拟实现】

在这里插入图片描述

常用接口函数介绍

构造函数

构造函数接口说明
list()构造空的list
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list
list<int> ls1();
//构造空的list对象
list<int> ls2(10, 2);
//创建10个元素值为2的对象

list<int> ls3(ls2);
//拷贝构造函数
list<int> ls4(l3.begin(), l3.end());
//使用迭代器区间创建一个ls4对象

迭代器

函数声明接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的

在这里插入图片描述
1、begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

三种遍历方式


list<int> ls; //构造空的list对象
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	ls.push_back(4);
	//迭代器遍历
	list<int>::iterator it = ls.begin();
	while (it != ls.end()) 
	{
		cout << *it << " ";
		it++;
	}
	//范围for遍历
	for(auto e : ls)
	{
		cout << e << " ";
	}
	
	//使用数组区间构造一个对象,对这个对象进行遍历
	int arr[] = { 8,4,5,11,23,66,89,45,8,65,65 };
	list<int>ls1(arr, arr + sizeof(arr) / sizeof(arr[0]));
	for (auto e : ls1) 
	{
		cout << e << " ";
	}
	cout << endl;

常用的操作函数

读者使用的时候可以结合文档去查询使用

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素
assign将新内容分配给容器
splice将元素从x转移到容器中,并将它们插入到位置。
remove在容器中删除val这个值,如果没有该值不做处理
unique只会删除一段区间连续重复的值,前提是要求这个容器是有序的

front和back

front函数原型

reference front();  
//返回list的第一个元素的引用
const_reference front() const;
//函数返回返回list的第一个元素的引用类型是const_reference,对象属性就不允许被修改了

back函数原型

reference back();
//返回list的最后一个元素的引用
const_reference back() const;
//函数返回一个const_reference,对象属性就不允许被修改了

注意:
成员类型reference和const_reference是对容器元素的引用类型

测试:

void func() 
{
	list<int> ls;
	ls.push_back(11);
	ls.push_back(4);
	//返回list的第一个元素和list的最后一个元素将他们的值相减
	cout << ls.front() - ls.back() << endl;//7
}

push_front 和 pop_front

push_front

函数原型:
void push_front (const value_type& val);
在list的开头,当前的第一个元素之前头插入一个新元素

pop_front

void pop_front();
删除list容器中的第一个元素,有效地将其大小减少1

测试:

void func() 
{
	list<int> ls;
	for (int i = 0; i < 5; i++) 
	{
		ls.push_front(i);	//往list容器插入5个值
	}
	
	for (auto e : ls) 
	{
		cout << e << " ";  //4 3 2 1 0
	}
	
	cout << endl;
	for (int i = 0; i < 5; i++)
	{
		ls.pop_front(); //头删list数据 
	}
	cout << ls.size() << endl;  //0
}

push_back 和 pop_back

push_back

void push_back (const value_type& val);
在列表容器的末尾,当前最后一个元素之后添加一个新元素
这有效地增加了容器的大小1

pop_back

void pop_back();
删除列表容器中的最后一个元素,有效地将容器大小减少1

测试:

void func() 
{
	list<int> ls;
	for (int i = 0; i < 5; i++) 
	{
		ls.push_back(i);//往list容器插入5个值
	}
	
	for (auto e : ls) 
	{
		cout << e << " ";  //0 1 2 3 4
	}
	
	cout <<"\n插入值后size: "<< ls.size() << endl;
	for (int i = 0; i < 5; i++)
	{
		cout << "删除值时size: " << ls.size() << endl;
		ls.pop_front(); //尾删list数据 
	}
	
}

运行结果:
在这里插入图片描述

insert 和 erase

insert 的三个版本

//通过在指定位置的元素之前插入新元素来扩展容器。  
iterator insert (iterator position, 
			const value_type& val);
			
//通过在指定位置的元素之前插入n个值为val的新元素来扩展容器。
void insert (iterator position, size_type n,
				 const value_type& val);
				 
//通过在指定位置的元素之前插入一段迭代器区间的值来扩展容器。			 
template <class InputIterator>
    void insert (iterator position, 
    InputIterator first, InputIterator last);

测试:

void func() 
{
	list<int> ls;
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	ls.push_back(4);

	list<int> ::iterator it = ls.begin();
	it++;	//it++完后指向的时list的第二个元素
	ls.insert(it, 30); //it位置前插入30
	for (auto e : ls) { cout << e << " ";  } //1 30 2 3 4 
	cout << endl;

	it++; //it此时指向值为3的元素
	//pos位置前插入一段连续的值
	int arr[] = { 10,20,30 };
	ls.insert(it,arr,arr + sizeof(arr) / sizeof(arr[0]));
	for (auto e : ls) { cout << e << " "; }   //1 30 2 10 20 30 3 4 
	cout << endl;

	it--;
	ls.insert(it, 5,1);
	for (auto e : ls) { cout << e << " "; }   //1 30 2 10 20 1 1 1 1 1 30 3 4 
	cout << endl;

}

效果:
在这里插入图片描述

erase

//从列表容器中移除指定pos的单个元素(position) 
iterator erase (iterator position);
//从列表容器中移除指定一段区间的元素([first,last))。 
iterator erase (iterator first, iterator last);

返回值:
返回指向被函数调用删除元素下一个元素的迭代器。 如果操作删除了序列中的最后一个元素,

测试:

void func1() 
{
	list<int> ls;
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	ls.push_back(4);
	for (auto e : ls) { cout << e << " "; }
	cout << endl;
	cout << "移除前capacity:" << ls.size() << endl;
	//移除值后打印
	list<int> ::iterator it = ls.begin();
	ls.erase(it); //删除第一个值
	for (auto e : ls) { cout << e << " "; }
	cout << "\n移除后capacity:" << ls.size() << endl;

	it = ls.begin();
	//移除剩余的值
	ls.erase(it,ls.end()); //删除一段区间的值
	for (auto e : ls) { cout << e << " "; }
	cout << "\n移除后capacity:" << ls.size() << endl;
}

效果:
在这里插入图片描述

swap

在list类的内部也有一个swap函数,swap函数常用于交换两个list容器里面的元素
测试:

void func2() 
{
	list<int>l1;  //1 2 
	l1.push_back(1);
	l1.push_back(2);

	list<int>l2;	//2 3 4
	l2.push_back(2);
	l2.push_back(3);
	l2.push_back(4);

	l1.swap(l2);
	for (auto e : l1) { cout << e << " "; } //2 3 4
	cout << endl;
	for (auto e : l2) { cout << e << " "; }// 1 2
}

从运行结果可以看出确实swap函数是交换容器中的元素的

splice和remove函数介绍

void func2() 
{
	list<int>L1, L2;
	L1.push_back(1);
	L1.push_back(2);
	L1.push_back(2);
	L1.push_back(3);
	L1.push_back(4);

	L2.push_back(10);

	list<int>::iterator it = L2.begin();
	L2.splice(it, L1);

	for (auto e : L2)
	{
		cout << e << " ";  //此时L2容器里面的元素是1 2 2 3 4 10 
	}
	cout << endl;
	cout << " L1.size():" << L1.size() << endl;//0
	cout << " L2.size():" << L2.size() << endl;//6
	L2.remove(10);
	for (auto e : L2)
	{
		cout << e << " ";  //此时L2容器里面的元素是1 2 2 3 4 10 
	}
	cout << endl;

	L2.remove(10);
	//remove只会删除容器中已有的元素,如果没有该值,remove不处理
	for (auto e : L2)
	{
		cout << e << " ";  //此时L2容器里面的元素是1 2 2 3 4 10 
	}
}

在这里插入图片描述

unique

功能:去重
注意:unique只会处理连续的相等元素组中删除除第一个元素外的所有元素。

测试:

void func2() 
{
	int arr[] = {1,2,2,2,2,5,2,3,2,5};
	list<int> ls(arr, arr+ sizeof(arr) / sizeof(arr[0]) - 1);
	cout << "容器创建" << endl;
	for (auto e : ls)
	{
		cout << e << " ";//1 2 2 2 2 5 2 3 2
	}
	cout << "\n开始去重" << endl;
	ls.unique();
	for (auto e : ls) 
	{
		cout << e << " ";//1 2 5 2 3 2
	}
	cout << "\n--------------------------" << endl;
	//现象 :容器中剩余的重复的值并没有去除掉
	//原因1:unique只会处理连续的相等元素组中删除除第一个元素外的所有元素。
	//原因2:数组是无序的
	int arr1[] = { 1,2,2,2,2,5,2,3,2,5 };
	list<int> ls1(arr1, arr1 + sizeof(arr1) / sizeof(arr1[0]) - 1);
	cout << "\n容器创建" << endl;
	for (auto e : ls1)
	{
		cout << e << " ";//1 2 2 2 2 5 2 3 2
	}
	cout << "\n开始去重" << endl;
	ls1.sort(); //先排序再去重
	ls1.unique();
	for (auto e : ls1)
	{
		cout << e << " ";//1 2 3 5
	}
}

在这里插入图片描述

迭代器失效问题探讨

程序的功能是去除容器中的偶数值

void func3() 
{
	int arr[] = { 1,2,3,4,5,6 };
	list<int> ls(arr,arr + sizeof(arr) / sizeof(arr[0]) - 1);
	list<int>::iterator it = ls.begin();
	while (it != ls.end()) 
	{
		if (*it % 2 == 0) 
		{
			ls.erase(it);
			//erase 会删除it指向的这个结点,
			//当这个结点被删除了,那么it指向的这个空间就会
			//被操作系统回收,程序员没有使用权限,
			//再去解引用的时候就会有非法访问,程序崩溃
		}
		it++;
	}

	for (auto e : ls) 
	{
		cout << e << " "; // 预期结果是:1 3 5
	}
	
}

当程序运行后,直接就崩溃了
在这里插入图片描述
解决方案:

void func3() 
{
	int arr[] = { 1,2,3,4,5,6 };
	list<int> ls(arr,arr + sizeof(arr) / sizeof(arr[0]) - 1);
	list<int>::iterator it = ls.begin();
	while (it != ls.end()) 
	{
		if (*it % 2 == 0) 
		{
			it = ls.erase(it);
			//删除该值后,返回此元素的下一个元素的迭代器,
			//	it被更新,即使旧值失效也不影响
		}
		else 
		{
			it++;
		}
	}
	
}

list模拟实现

类声明

template<class T>
	struct __list_node
	{
		__list_node<T>* prev;
		__list_node<T>* next;
		T data;
		//提供全缺省参数,这里使用匿名对象,
		//T()会去调用T类型的构造函数
		__list_node(const T& val = T())
			:prev(nullptr)
			,next(nullptr)
			,data(val)
		{ }
	};

定义 __list_node结构用于创建结点

迭代器类

用于去模拟原生指针的行为,和原生指针的区别是意义是不一样的,但是模拟实现的行为是类似的

template<class T,class Ref, class Ptr>
	struct __list_iterator 
	{
		typedef __list_iterator<T, Ref, Ptr> Self;
		typedef __list_node<T> Node;
		Node* _node;
		__list_iterator(Node* node)
			:_node(node) 
		{ }

		Ref operator*()
		{
			return _node->data;
		}
		Ptr operator->()
		{
			return &_node->data;
		}
		
		bool operator!=(const Self &it)
		{
			return it._node != _node;
		}
		Self operator++(int)
		{
			
			Self tmp((*this)._node);
			++(*this);
			return tmp;
		}
		Self& operator++()
		{
			_node = _node->next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->prev;
			return *this;
		}
		Self operator--(int)
		{
			
			Self tmp((*this)._node);
			--(*this);
			return tmp;
		}

	};

模板参数

//这里分别提供三个模板参数
// T:表示data的类型
//Ref:表示引用的类型
//Ptr:表示指针类型
template<class T,class Ref, class Ptr>

迭代器框架

//三个模板参数前面已经解释了
template<class T,class Ref, class Ptr>
	struct __list_iterator 
	{
		typedef __list_iterator<T, Ref, Ptr> Self;
		//使用typedef 为迭代器类型取别名的方式会更简洁,
		//所以推荐使用这种方式,另外也可以针对不同的模板参数类型
		//产生不同类型的迭代器对象,读者可以细细体会其中的妙用
		typedef __list_node<T> Node;
		
		Node* _node;//这里的_node用于记录迭代器此时的位置
		
		//通过Node类型的指针作为参数调用迭代器的构造函数
		//来初始化迭代器的对象的成员_node
		__list_iterator(Node* node)
			:_node(node) //初始化列表方式
		{ }
		
	};

这里想先让读者读懂list的框架,再来看后面实现的接口函数,便于整体的学习

operator* 和 operator->

//重载*运算符
Ref operator*() //Ref的类型是跟模板参数类型有关的,引用或常引用
{
	return _node->data;
	//上面说过_node是用来记录迭代器的位置的,
	//对指针解引用返回该位置的值
}
//重载->运算符
Ptr operator->()  //Ptr 的类型是跟模板参数类型有关的, T* 或者 const T *
{
	return &_node->data;
	//返回对象的指针就能通过->访问其成员变量
}

前置++/-- 后置 ++/–

//后置++
Self operator++(int) //Self :这是一个迭代器对象
{
	//调用iterator的构造函数用指针去创建一个局部迭代器对象,
	//tmp出了作用域会被销毁掉,
	Self tmp((*this)._node);
	++(*this); //复用 operator++()
	return tmp; //后置++返回++之前的值,中间为了返回会创建临时对象
}

Self& operator++() // Self :这是一个迭代器对象
{
	_node = _node->next;//更新迭代器的位置,往后走
	return *this;  //前置++返回++之后的值
}

Self& operator--()//Self :这是一个迭代器对象
{
	_node = _node->prev;//更新迭代器的位置,往前走
	return *this; //前置--返回--之后的值
}

Self operator--(int)//Self :这是一个迭代器对象
{
	//使用指针创建一个局部的迭代器对象
	Self tmp((*this)._node);
	--(*this); //复用operator--()
	return tmp; //后置--返回--之前的值
}

list类

类声明

template<class T>
	class list
	{
		typedef __list_node<T> Node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		list() 
		{
			_head = new Node();
			_head->prev = _head;
			_head->next = _head;
		}
		//拷贝构造函数
		list(const list<T>& ls) 
		{
			_head = new Node();
			_head->next = _head;
			_head->prev = _head;

			for (auto& e : ls) 
			{
				push_back(e);
			}
		}
		//operator=
		list<T>& operator=(list<T> ls)
		{
			
			swap(_head, ls._head);
			return *this;
		}
		//返回普通迭代器
		iterator begin() 
		{
			return iterator(_head->next);
		}
		iterator end() 
		{
			return iterator(_head);
		}
		//const迭代器
		const_iterator begin()const
		{
			return const_iterator(_head->next);
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}
		//指定pos位置插入结点
		void Insert(iterator pos, const T& val)
		{	
			Node* cur = pos._node;
			Node* newNode = new Node(val);
			Node* prev = cur->prev;

			prev->next = newNode;
			newNode->prev = prev;
			newNode->next = cur;
			cur->prev = newNode;
		}
		//尾插
		void push_back(const T& val)
		{
			Insert(end(),val);
		}
		//头插
		void push_Front(const T& val)
		{
			Insert(begin(), val);
		}

		iterator erase(iterator pos)
		{
			assert(pos._node != _head);
			Node* cur = pos._node;
			Node* prev = cur->prev;
			Node* next = cur->next;
			prev->next = next;
			next->prev = prev;
			delete cur;

			return next;
		}
		bool empty() { return _head->next == _head->prev; }
		//尾删
		void Pop_back()
		{
			assert(!empty());
			erase(--end());
		}
		//头删
		void Pop_Front() 
		{
			assert(!empty());
			erase(begin());
		}
		//清空容器
		void clear() 
		{
			iterator it = begin();
			while (it != end()) 
			{
				erase(it++);
			}
		}
		//析构
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		
	private:
		Node* _head;
	};

框架

template<class T>
class list
{
	//推荐写法,简洁
	typedef __list_node<T> Node;
public:
	//普通迭代器类型:可读可写
	typedef __list_iterator<T, T&, T*> iterator;
	//const迭代器类型:只读
	typedef __list_iterator<T, const T&, 
				const T*> const_iterator;
 
private:
	Node* _head;  //list是一个双向带头循环的链表结构,
				//所以这里我们需要先定义一个_head头节点
};

这里同样的为了便于后面的学习也是先理解框架,为整体的理解梳理一个好的思路

begin和end

//返回普通迭代器
iterator begin() 
{
	//使用指针创建一个迭代器对象,返回可读可写迭代器对象
	//这个迭代器对象记录的是第一个结点的位置
	return iterator(_head->next);
}

iterator end() 
{
	//使用指针创建一个迭代器对象,返回可读可写迭代器对象
	//这个迭代器对象记录的是最后一个结点的下一个位置
	return iterator(_head);
}

//const迭代器 
const_iterator begin()const
{
	//使用指针创建一个迭代器对象,返回只读的迭代器对象
	//这个迭代器对象记录的是第一个结点的位置
	return const_iterator(_head->next);
	
}

const_iterator end()const
{
	//使用指针创建一个迭代器对象,返回可读迭代器对象
	//这个迭代器对象记录的是最后一个结点的下一个位置
	return const_iterator(_head);
}

以上几种方式均会产生临时对象,读者了解即可

增删查改

//指定pos位置插入结点
void Insert(iterator pos, const T& val)
{	
	Node* cur = pos._node;
	Node* newNode = new Node(val);
	Node* prev = cur->prev;

	prev->next = newNode;
	newNode->prev = prev;
	newNode->next = cur;
	cur->prev = newNode;
}
//指定pos位置删除结点
iterator erase(iterator pos)
{
	assert(pos._node != _head);
	Node* cur = pos._node;
	Node* prev = cur->prev;
	Node* next = cur->next;
	prev->next = next;
	next->prev = prev;
	delete cur;

	return next;
}

头插尾插

//尾插
void push_back(const T& val)
{
	Insert(end(),val);
}
//头插
void push_Front(const T& val)
{
	Insert(begin(), val);
}

头删尾删

bool empty() { return _head->next == _head->prev; }
//尾删
void Pop_back()
{
	assert(!empty());
	erase(--end());
	//end()返回的是_head->prev,
	// --end() 复用operator--()
	//也可以写成这种形式 :erase(_head->prev);  
}
//头删
void Pop_Front() 
{
	assert(!empty());
	erase(begin()); 
	//begin()返回的是第一个结点的迭代器
	//erase删除第第一个结点
}

清空容器

void clear() //使用迭代器遍历删除结点
{
	iterator it = begin();
	while (it != end()) 
	{
		erase(it++); 
	}
}

构造和拷贝构造

构造函数

list()  //构造函数创建一个头节点
{
	_head = new Node();
	_head->prev = _head;
	_head->next = _head;
}

拷贝构造函数

//拷贝构造函数
list(const list<T>& ls) 
{
	//创建头节点
	_head = new Node();
	_head->next = _head;
	_head->prev = _head;
	//将ls对象遍历的同时将结点取出来尾插在_head的后面
	for (auto& e : ls) 
	{
		push_back(e);
	}
}

operator=

//operator=
list<T>& operator=(list<T> ls)
{
	//传统写法
	if (this != &ls) 
	{
		clear(); //清空this对象,只保留头节点
		for (auto e : ls) 
		{
			push_back(e);
			 //将ls对象的结点尾插在this对象后面
		}
	}
}

operator=

list<T>& operator=(list<T> ls)
{
	//现代写法
	swap(_head, ls._head);
	//交换this对象的_head和ls对象的_head
	//_head就可以去连接ls对象中的结点了
	return *this;
}

析构函数

~list()
{
	//完成资源清理,释放list对象的所有的结点
	clear();  
	delete _head; 
	_head = nullptr;//防止野指针
}

总结vector和list的区别:

vector是一个动态增长的数组,可以支持随机访问,很好的支持一些排序比如:二分查找、堆算法等等
缺点:头插和指定pos位置插入效率低,因为要挪动数据,空间不够需要增容,增容有性能的消耗

list是一个双向带头循环的链表,是一个任意位置插入删除数据时间效率上都是O(1)
缺点:不支持随机访问,遍历速度慢

总结:vector和list是两个相辅相成的容器

  • 44
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱生活,爱代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值