当前位置: 首页 > news >正文

C++list全解析

1.list的说明与使用

1.1list的说明

附个链接:cplusplus.com/reference/list/list/#google_vignette,list其实与我们之前学过的string,vector非常类似,接下来详细介绍list常用的一些接口

1.2list构造函数

以下列举listC++98的构造函数:

1.3list的iterator

函数名描述
begin返回指向容器第一个元素的迭代器 
end返回指向容器末尾(最后一个元素之后)的迭代器
rbegin返回指向反向容器第一个元素的反向迭代器
rend 返回指向反向容器末尾的反向迭代器 
cbegin返回指向容器第一个元素的常量迭代器 
cend返回指向容器末尾的常量迭代器 
crbegin返回指向反向容器第一个元素的常量反向迭代器
crend返回指向反向容器末尾的常量反向迭代器
list不支持operator[]这是因为如果list要实现,那么效率很低(要一个一个遍历)

 

void test_list01()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}//可以用迭代器就可以用范围forfor (const auto& e : lt){cout << e << " ";}//迭代器不支持+/*it = lt.begin();lt.erase(it+3);*/
}

拓展:STL迭代器类别详解(性质由底层结构决定,并且决定了可以用哪些算法)

输入迭代器 (Input Iterator)
支持单向只读访问,适用于单遍扫描算法。操作包括前向递增(++)、解引用(*,仅右值)、成员访问(->)以及相等性比较(==/!=)。典型应用如从输入流读取数据,示例容器为 istream_iterator

输出迭代器 (Output Iterator)
支持单向只写访问,适用于单遍扫描算法。操作仅包含前向递增(++)和解引用(*,仅左值),常用于写入数据到输出流,示例容器为 ostream_iterator

前向(单向)迭代器 (Forward Iterator)
扩展输入迭代器功能,支持读写操作和多遍扫描。允许重复解引用和移动,适用于需多次遍历的场景。示例容器包括 std::forward_liststd::unordered_mapstd::unordered_set

双向迭代器 (Bidirectional Iterator)
在前向迭代器基础上增加双向移动能力,支持前置和后置递减(--)。适用于需要逆向遍历的容器,如 std::liststd::mapstd::set

随机访问迭代器 (Random Access Iterator)
功能最强大,支持随机访问和算术运算。额外操作包括:

  • 偏移运算(it + nit - nit += nit -= n
  • 距离计算(it1 - it2
  • 下标访问(it[n],等价于 *(it + n)
  • 关系比较(<><=>=
    典型容器为 std::vectorstd::dequestd::array 及普通指针(如 int*)。

关键区别

  • 访问方向:输入/输出迭代器仅单向,双向迭代器可逆向移动。
  • 读写能力:输入迭代器只读,输出迭代器只写,其余均支持读写。
  • 随机访问:仅随机访问迭代器支持直接跳转和算术运算。

能力强度输入/输出迭代器 < 前向迭代器 < 双向迭代器 < 随机访问迭代器

1.4list的容量相关成员函数

1.5list的元素访问成员函数

注意:

  • 在调用 front() 或 back() 之前,必须确保容器不为空

  • 空容器调用这些函数会导致未定义行为

  • 建议先使用 empty() 检查容器状态

  • reference就是引用

1.6修改器成员函数

assign

参数size_type n, const T& valInputIterator first, InputIterator last
返回值void
时间复杂度:O(n)
用途:用指定数量的相同值或另一个序列中的元素替换链表当前内容。


push_front

参数const T& value
返回值void
时间复杂度:O(1)
用途:在链表头部插入一个新元素,无需移动其他元素。


pop_front

参数:无
返回值void
时间复杂度:O(1)
用途:删除链表头部的元素,链表不为空时调用。


push_back

参数const T& value
返回值void
时间复杂度:O(1)
用途:在链表尾部追加一个新元素,无需移动其他元素。


pop_back

参数:无
返回值void
时间复杂度:O(1)
用途:删除链表末尾的元素,链表不为空时调用。


insert

参数iterator position, const T& value(多种重载)
返回值iterator
时间复杂度:O(1)
用途:在指定位置插入一个或多个元素,返回指向新插入元素的迭代器。


erase

参数iterator positioniterator first, iterator last
返回值iterator
时间复杂度:O(1)
用途:删除指定位置或范围内的元素,返回指向被删除元素之后位置的迭代器。


swap

参数list& other
返回值void
时间复杂度:O(1)
用途:高效交换两个链表的内容,仅交换内部指针。


resize

参数size_type n, const T& val = T()
返回值void
时间复杂度:O(n)
用途:调整链表大小,若新大小超过当前大小,则用默认值或指定值填充;否则删除多余元素。


clear

参数:无
返回值void
时间复杂度:O(n)
用途:清空链表,删除所有元素并释放内存。

注意emplace系列现在这个阶段不会介绍,可以理解emplace类似push系列

1.7list 操作成员函数

2.list模拟实现

2.1list的push_back

void push_back(const T& x)
{Node* newnode = new Node(x);Node* tail=_head->_prev;tail->_next = newnode;newnode->prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

2.2list的iterator(重点)

我们之前学过的string,vector底层都是顺序表,所以之前模拟实现的iterator,++进行迭代或者*解引用访问数据就可以是原生指针,但是如果这里还使用原生指针,就会出现问题:

  1. 解引用访问的是当前节点,而不是当前节点的数据
  2. 无法进行++迭代,因为节点地址不连续

这里就给出一个解决方案,封装一个迭代器类接着重载运算符实现++和解引用

ps:迭代器本身就是节点的指针,只是节点的指针不满足那些需求(++,*,...),所以用类去包装一层重载运算符,因此迭代器里面真正的数据还是节点的指针


iterator begin()
{/*iterator it(_head->_next);return it;*///有名对象/*return iterator(_head->_next);*///匿名对象return _head->_next;//隐式类型转换
}iterator end()//end是数据的下一个位置,也就是_head
{return _head;
}

这里给了三种返回iterator的方法:

  1. 构造有名对象
  2. 构造匿名对象
  3. 隐式类型转换,将节点的指针转换为iterator

opreator->的实现

   我们在用到结构的指针时会使用到->

struct AA
{int _a1 = 1;int _a2 = 2;
};
void test_list02()
{list<AA> lta;lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator ita = lta.begin();while (ita != lta.end()){//cout << *ita << " ";err解决方法:1.给AA实现一个流插入2.重载->3.:如下面语句cout << (*ita)._a1 << (*ita)._a2 << endl;//特殊处理,本来是两个->,为了可读性,省略了一个cout << ita.operator->()->_a1 << ita->_a2 << endl;++ita;}cout << endl;
}

法一:重载流插入

struct AA
{int _a1 = 1;int _a2 = 2;// 重载流插入运算符friend ostream& operator<<(ostream& out, const AA& aa){out << "a1:" << aa._a1 << " a2:" << aa._a2;return out;}
};

法二:重载->

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

但这里可能有些读者会感到困惑,ita调用->,operator->返回T*(aa*)也就是_data的地址,那么aa*是怎么访问_a1和_a2的呢,实际上这里时两个->

  1. 第一个->是运算符重载,调用operator->返回aa*指针
  2. 第二个->是结构体指针的原生指针解引用

但编译器这里做了特殊处理(为了可读性两个->不方便)于是就省略了第二个箭头

方法三:直接访问成员,上面代码有演示。


const迭代器的实现

  我们在实现普通迭代器之后,将之前写过的打印任意容器的函数粘贴过来

template<class Container>
void print_container(const Container& con)
{for (auto e : con){cout << e << " ";}cout << endl;
}

在调用print_container(lta);发现程序报错,但是在这个函数外面使用范围for就正常,其实这里就涉及const迭代器问题

  • con 是 const Container&

  • 但 begin() 和 end() 返回的是普通迭代器

  • const 对象只能调用 const 成员函数

那么就要实现const的迭代器类。这里就要注意,之所以不直接使用const来修饰iterator,而是单独封装一个const_iterator类是因为

1. const iterator 的含义(修饰的是iterator本身)

  • it 本身是 const(不能 ++it

  • 但 *it 仍然可以修改内容

2. const_iterator 的含义

  • it 本身可以移动(可以 ++it

  • 但 *it 是 const 的(不能修改指向的内容)

这里再提一下按需实例化的概念:

模板采用两阶段编译检查:

  1. 定义阶段:只检查不依赖模板参数的语法(如缺少分号、括号不匹配等)
  2. 实例化阶段:检查依赖模板参数的语义(如成员访问、运算符重载等)

如果Container的const_iterator解引用返回const引用:

  1. 在模板定义阶段不会报错(语法正确)
  2. 但在实例化阶段会报错:给常量赋值

这就是模板的"按需实例化"特性——错误检查延迟到实际使用时进行。


我们发现单独封装一个list_const_iterator类只有*,->不一样,太冗余了,这里还有其他的方式这里介绍一个模板共享的方式

通过共享同一个模板来减少代码重复,普通对象,和const对象,分别增加两个模板参数Ref和Ptr,Ref作为operaor*的返回值,Ptr作为operator->的返回值,但是这个其实与我们之前的方式没有本质区别,这里也是有两个类,之前的两个类是我们自己实现的两个类,而这里是实现了类模板给编译器,编译器实例化出来了两个类


list的迭代器失效问题

由于链表的空间不是连续的,在使用insert()进行插入操作不会引起迭代器失效的问题

我们发现在it前面执行插入10操作后对当前的迭代器执行+=100操作正常,所以insert就没有迭代器失效的问题。但是我们接着来看erase

在 erase(ito) 后,ito 指向的节点已经被删除,但 ito 本身仍然指向那个已经被释放的内存地址。ito是野指针,于是+或者解引用程序崩溃,所以与之前一样,erase要实现返回下一个有效迭代器来更新迭代器

//iterator erase(iterator pos)
//{
//	assert(pos != end());
//	Node* prev = pos._node->_prev;
//	Node* next = pos._node->_next;
//	prev->_next = next;
//	next->_prev = prev;
//
//	delete pos._node;
//	--_size;
//	return next;
//}
auto ito = lt.begin();
while (ito!=lt.end())
{if (*ito % 2 == 0)ito = lt.erase(ito);else++ito;
}
print_container(lt);

2.3list的insert,push_front,push_back

其实我们在实现完insert后就可以直接调用insert的逻辑来进行头插和尾插了

	void insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;}void push_back(const T& x){insert(end(),x);}void push_front(const T& x){insert(begin(), x);}

2.4list的erase,pop_back,pop_front

注意在实现erase的时候,不能将哨兵位删除

void erase(iterator pos)
{assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;
}
void pop_back()
{erase(--end());//end()返回的是_head所以要--
}
void pop_front()
{erase(begin());
}

2.5list的析构函数,clear

要实现链表的clear函数。我们要遍历链表,一个一个节点释放,析构函数则是在clear函数基础上将头节点释放

~list()
{clear();delete _head;_head = nullptr;
}
void clear()
{auto it = begin();while (it!=end()){it = erase(it);}
}

2.6list的拷贝构造,空初始化函数,operator=

还是与之前一样当我们没有显示写拷贝构造或者赋值重载时,编译器自动生成的是浅拷贝,两个list指向同一块资源,就会出现问题。这里实现深拷贝

list(const list<T>& lt)
{for (auto& e : lt){push_back(e);}
}

这里还是有问题,因为调用push_back的前提是要有哨兵位,而且哨兵位的_next和_prev要指向自己形成最初的闭环结构,所以这里在实现一个空初始化函数

void empty_init()
{_head = new Node(T());//这里是哨兵头节点,不能直接给0初始化这是因为T有可能是自定义类型_head->_next = _head;_head->_prev = _head;_size = 0;
}
//lt2(lt1)
list(const list<T>& lt)
{empty_init();for (auto& e : lt){push_back(e);}
}

赋值重载实现:

void swap(list<int>& lt)
{std::swap(_head, lt._head);std::swap(_size, lt._size);
}
//lt3 = lt1
list<T>& operator=(list<T>lt)
{swap(lt);return *this;
}

传值传参,形参lt是lt1的拷贝,然后将lt3的旧值与形参交换,则lt3就得到了新值,旧值给了形参出了作用域就销毁

3.模拟实现list完整代码

List.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<assert.h>namespace name
{template<class T>class list_node{public:T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data=T())//自定义类型调用默认构造:_data(data),_next(nullptr),_prev(nullptr){}};template<class T,class Ref,class Ptr>class list_iterator//也可以使用struct(默认为public){public:typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}Ref operator*()//返回引用支持修改{return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++()//返回自己类型的迭代器{_node = _node->_next;return *this;}Self& operator++(int)//后置++{Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--()//返回自己类型的迭代器{_node = _node->_prev;return *this;}Self& operator--(int)//后置--{Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s)const{return _node != s._node;}bool operator==(const Self& s)const{return _node == s._node;}};//template<class T>//class list_iterator//也可以使用struct(默认为public)//{//public://	typedef list_node<T> Node;//	typedef list_iterator<T> Self;//	Node* _node;//	list_iterator(Node* node)//		:_node(node)//	{}//	T& operator*()//返回引用支持修改//	{//		return _node->_data;//	}//	T* operator->()//	{//		return &_node->_data;//	}//	Self& operator++()//返回自己类型的迭代器//	{//		_node = _node->_next;//		return *this;//	}//	Self& operator++(int)//后置++//	{//		Self tmp(*this);//		_node = _node->_next;//		return tmp;//	}//	Self& operator--()//返回自己类型的迭代器//	{//		_node = _node->_prev;//		return *this;//	}//	Self& operator--(int)//后置--//	{//		Self tmp(*this);//		_node = _node->_prev;//		return tmp;//	}//	bool operator!=(const Self&s)const//	{//		return _node != s._node;//	}//	bool operator==(const Self& s)const//	{//		return _node == s._node;//	}//};//template<class T>//class list_const_iterator//也可以使用struct(默认为public)//{//public://	typedef list_node<T> Node;//	typedef list_const_iterator<T> Self;//	Node* _node;//	list_const_iterator(Node* node)//		:_node(node)//	{//	}//	const T& operator*()//返回const引用不支持修改//	{//		return _node->_data;//	}//	const T* operator->()//	{//		return &_node->_data;//	}//	Self& operator++()//返回自己类型的迭代器//	{//		_node = _node->_next;//		return *this;//	}//	Self& operator++(int)//后置++//	{//		Self tmp(*this);//		_node = _node->_next;//		return tmp;//	}//	Self& operator--()//返回自己类型的迭代器//	{//		_node = _node->_prev;//		return *this;//	}//	Self& operator--(int)//后置--//	{//		Self tmp(*this);//		_node = _node->_prev;//		return tmp;//	}//	bool operator!=(const Self& s)const//	{//		return _node != s._node;//	}//	bool operator==(const Self& s)const//	{//		return _node == s._node;//	}//};template<class T>class list{typedef list_node<T> Node;public://typedef list_const_iterator<T> const_iterator;//typedef list_iterator<T> iterator;//注意typedef也是受到访问限定符的限制的,也就是说//iterator可以在外面使用而Node不行typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;iterator begin(){/*iterator it(_head->_next);return it;*///有名对象/*return iterator(_head->_next);*///匿名对象return _head->_next;}iterator end()//end是数据的下一个位置,也就是_head{return _head;}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}list(){_head = new Node(T());//这里是哨兵头节点,不能直接给0初始化这是因为T有可能是自定义类型_head->_next = _head;_head->_prev = _head;_size = 0;}list(initializer_list<T> il){empty_init();//给一个哨兵位节点for (auto& e : il){push_back(e);}}~list(){clear();delete _head;_head = nullptr;}void clear(){auto it = begin();while (it!=end()){it = erase(it);}}void empty_init(){_head = new Node(T());//这里是哨兵头节点,不能直接给0初始化这是因为T有可能是自定义类型_head->_next = _head;_head->_prev = _head;_size = 0;}//lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}void swap(list<int>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt3 = lt1list<T>& operator=(list<T>lt){swap(lt);return *this;}//void push_back(const T& x)//{//	Node* newnode = new Node(x);//	Node* tail=_head->_prev;//	tail->_next = newnode;//	newnode->_prev = tail;//	newnode->_next = _head;//	_head->_prev = newnode;//	_size++;//}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}void push_back(const T& x){insert(end(),x);}void push_front(const T& x){insert(begin(), x);}iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return next;}void pop_back(){erase(--end());//end()返回的是_head所以要--}void pop_front(){erase(begin());}size_t size()const{return _size;}bool empty()const{//也可以/*return _head->next == _head;*/return _size == 0;}private:Node* _head;size_t _size;//为了防止遍历,这里加一个成员size记录list的大小};/*template<class Container>//声明void print_container(const Container& con);*/template<class Container>void print_container(const Container& con){//const iterator->迭代器本身不能修改//const_iterator->指向的内容不能修改typename Container::const_iterator cit = con.begin();//要加typename来取模板里面的内容//list<int>::const_iterator cit = con.begin();/*auto cit = con.begin();*/while (cit != con.end()){//*cit+=10;cout << *cit << " ";++cit;}}struct AA{int _a1 = 1;int _a2 = 2;};}

Test.cpp

#pragma once
#include"List.h"namespace name
{void test_list01(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;//可以用迭代器就可以用范围forfor (const auto& e : lt){cout << e << " ";}cout << endl;print_container(lt);}void test_list02(){list<AA> lta;lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator ita = lta.begin();while (ita != lta.end()){//cout << *ita << " ";err解决方法:1.给AA实现一个流插入2.重载->3.:如下面语句cout << (*ita)._a1 << (*ita)._a2 << endl;//特殊处理,本来是两个->,为了可读性,省略了一个cout << ita.operator->()->_a1 << ita->_a2 << endl;++ita;}cout << endl;}void test_list03(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();lt.insert(it,10);*it += 100;print_container(lt);cout << endl;//删除所有的偶数auto ito = lt.begin();while (ito!=lt.end()){if (*ito % 2 == 0)ito = lt.erase(ito);else++ito;}print_container(lt);}void test_list04(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);print_container(lt1);print_container(lt2);list<int> lt3;lt3 = lt1;print_container(lt2);}void function(const list<int>& lt){cout<<"fuhction:"<< endl;print_container(lt);cout << endl;}void test_list05(){auto il = {10,20,30};//class std::initializer_list<int>cout<<typeid(il).name()<<endl;//initializer_list类里面有两个指针,一个指向列表开始位置,一个指向结束位置cout << sizeof(il) << endl;list<int> lt1 = { 1,2,3,4,5,6 };//隐式类型转换list<int> lt2({ 1,2,3,4,5,6 });//直接构造print_container(lt1);print_container(lt2);cout << endl;const list<int>& lt = {1,2,3,4,5,6};//引用临时对象function(lt1);function({ 1,2,3,4,5,6 });//单参数构造函数支持隐式类型转换}
}int main()
{//name::test_list01();//name::test_list02();//name::test_list03();//name::test_list04();name::test_list05();
}

 

http://www.dtcms.com/a/441914.html

相关文章:

  • KafKa概念与安装
  • 基于单片机和LabVIEW的多路数据采集器系统设计(论文+源码)
  • 网站你懂我意思正能量晚上在线下载免费软件魅族网站被黑客入侵怎么办
  • C语言笔记(2)
  • interface range 概述及题目
  • web:vue中方法watch和方法watchEffect的对比
  • 微信息公众平台微网站建设郴州网站建设费用价格
  • leetcode 35.搜索插入的位置 python
  • 探索 Docker/K8s 部署 MySQL 的创新实践与优化技巧——容器化部署深度解析
  • 信奥赛CSP-J复赛集训(语法基础专题)(1):三位数排序(文末附讲课视频)
  • 购物分享网站怎么做的网站建设服务中心
  • 【深度学习新浪潮】数据合成领域近三年研究进展与开源项目调研
  • 【嵌入式Linux - 应用开发】音频(ALSA 框架)
  • 获得场景视频API开发(02):H5前端上传视频之Java转 PHP实现方案
  • 枣阳网站建设公司c 在网站开发方面有优势吗
  • SpringMVC中的常用注解及使用方法
  • PyQt6实例_个股收盘价和市盈率TTM
  • Windows 环境下安装 Node.js 和 Vue.js 框架完全指南
  • C语言第3讲:分支和循环(上)—— 程序的“决策”与“重复”之旅
  • 09.Docker compose
  • 梁山专做网站的公司徐州便民信息网
  • HarmonyOS 应用开发深度解析:ArkTS 状态管理与渲染控制的艺术
  • ThreadX全家桶迎来移交Eclipse基金会后的第2次更新,发布V6.4.3版本,更新终于回到正轨
  • 中国工信备案查询网站哪个网站能免费下载
  • 网站图片上传功能怎么做设计网红店铺
  • 保姆级 Docker 入门到进阶
  • 网站建站网站80s隐秘而伟大新网站怎么做谷歌推广呢
  • uv 配置国内镜像加速教程
  • Leetcode 295. 数据流的中位数 堆
  • Go 语言的 channel