【C++初阶】list的常见使用操作

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


一、list的基本概念

  • 功能:将数据进行链式存储(物理存储单元上非连续的存储结构)

  • STL中的链表是一个 带头双向循环链表。这样的设计使得在链表中插入、删除节点的操作更加高效;时间复杂度都是O(1)

  • list的数据域同样可以存储不同数据类型,因此它同样是一个类模板

在这里插入图片描述

二、list的构造

【函数原型】

在这里插入图片描述

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> l{ 1,2,3 };

	// 1. 默认构造。构造空对象
	list<int> l1;
	for (auto e : l1)
	{
		cout << e << ' ';
	}
	cout << endl;

	// 2. 拷贝构造
	list<int> l2(l);
	for (auto e : l2)
	{
		cout << e << ' ';
	}
	cout << endl;

	// 3. n个val的元素构造
	list<int> l3(3, 666); // 3个666
	for (auto e : l3)
	{
		cout << e << ' ';
	}
	cout << endl;

	// 4. 迭代区间的元素构造
	list<int> l4(l.begin(), l.end());
	for (auto e : l4)
	{
		cout << e << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

三、迭代器 begin + end

  • begin:返回第一个元素的迭代器
  • end:返回带头节点
    在这里插入图片描述

在这里插入图片描述

四、容量操作 size + empty

在这里插入图片描述

五、list的遍历

list本质是链表,不是用连续性空间存储数据的。因此,list是不支持下标访问[]

在这里插入图片描述

六、list的获取元素操作

6.1 front

功能:返回list的第一个节点中值的引用。

在这里插入图片描述

6.2 back

功能:返回list的最后一个节点中值的引用。

在这里插入图片描述

七、list的对容器修改操作

7.1 push_front

功能:头插

在这里插入图片描述

7.2 pop_front

功能:头删
在这里插入图片描述

7.3 push_back

功能:尾插

在这里插入图片描述

7.4 pop_back

功能:尾删

在这里插入图片描述

7.5 insert + 随机访问问题

vector开始insert都是使用迭代器来访问的

在这里插入图片描述

假设已有数据:1 2 3 4,现要在2后插入100

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> l5;
	l5.push_back(1);
	l5.push_back(2);
	l5.push_back(3);
	l5.push_back(4);

	l5.insert(l5.begin() + 2, 100);

	for (auto x : l5)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

在这里插入图片描述

以上代码报错了。原因是:list本质是链表,不一定连续性空间存储数据的,迭代器也是不支持随机访问(下标访问operator[]),只能支持++--操作(支持双向遍历)

【正确写法】

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> l5;
	l5.push_back(1);
	l5.push_back(2);
	l5.push_back(3);
	l5.push_back(4);

	auto it = l5.begin();
	for (int i = 0; i < 2; i++)
	{
		it++;
	}
	l5.insert(it, 100);

	for (auto x : l5)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

  • ++的底层就是+1,那么为什么+1不行,而++可以?

这都归功于类的封装。在对迭代器封装的时候,重新的定义了这些符号的意义,也就是符号的重载。这才使得我们能就像使用指针一样去使用迭代器。

下面是list的源代码(部分)

 self& operator++() 
 { 
     node = (link_type)((*node).next);
     return *this;
 }

 self operator++(int) 
 { 
     self tmp = *this;
     ++*this;
     return tmp;
 }
self& operator--() 
 { 
     node = (link_type)((*node).prev);
     return *this;
}
self operator--(int) 
{ 
    self tmp = *this;
    --*this;
    return tmp;
}

7.6 erase + 迭代器失效问题

功能:删除某个位置的元素

在这里插入图片描述

【代码示例】

目的:删除所有元素

#include <iostream>
#include <list>
using namespace std;

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(a, a + sizeof(a) / sizeof(a[0]));

	cout << "删除前:";
	for (auto x : l)
	{
		cout << x << ' ';
	}
	cout << endl;

	auto it = l.begin();
	while (it != l.end())
	{
		l.erase(it);
		++it;
	}

	cout << "删除后:";
	for (auto x : l)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

程序崩了!这和vector的情况类似,erase()函数执行后,it所指向的节点已被删除,因此it无效。

在这里插入图片描述

解决方法:在下一次使用it时,必须先给其赋值

【正确代码】

#include <iostream>
#include <list>
using namespace std;

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(a, a + sizeof(a) / sizeof(a[0]));

	cout << "删除前:";
	for (auto x : l)
	{
		cout << x << ' ';
	}
	cout << endl;

	auto it = l.begin();
	while (it != l.end())
	{
		// l.erase(it); 错误
		it = l.erase(it);
	}

	cout << "删除后:";
	for (auto x : l)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

【总结】

  • listinsert操作时不存在迭代器失效问题。因为链表不涉及扩容
  • erase操作会有迭代器失效问题

7.7 swap

功能:交换两个list中的元素

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> l1;
	l1.push_back(1);
	l1.push_back(1);
	l1.push_back(1);
	l1.push_back(1);

	list<int> l2;
	l2.push_back(2);
	l2.push_back(2);
	l2.push_back(2);
	l2.push_back(2);

	cout << "交换前" << endl;
	cout << "l1:";
	for (auto x : l1)
	{
		cout << x << ' ';
	}
	cout << endl;
	cout << "l2:";
	for (auto x : l2)
	{
		cout << x << ' ';
	}
	cout << endl;
	l1.swap(l2);

	cout << "交换后" << endl;
	cout << "l1:";
	for (auto x : l1)
	{
		cout << x << ' ';
	}
	cout << endl;
	cout << "l2:";
	for (auto x : l2)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

在这里插入图片描述

7.8 clear

功能:清空list中所有的有效元素

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> l1;
	l1.push_back(1);
	l1.push_back(1);
	l1.push_back(1);
	l1.push_back(1);

	l1.clear();
	if (l1.empty())
	{
		cout << "已清空" << endl;
	}
	return 0;
}

【输出结果】

在这里插入图片描述

八、其他操作(常见)

在这里插入图片描述

主要讲解画方括号的,剩下的自行了解即可 ~

8.1 reverse

功能:逆置链表元素

在这里插入图片描述

【代码示例】

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<char> lc1;
	lc1.push_back('a');
	lc1.push_back('b');
	lc1.push_back('c');
	lc1.push_back('d');

	// list的逆置接口
	lc1.reverse();
	for (auto x : lc1)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

在这里插入图片描述

其实list设计这个接口没有必要,因为算法库(algorithm)也设计了reverse算法

在这里插入图片描述

【代码示例】

#include <iostream>
#include <list>
#include <algorithm> // 使用算法库需要包含头文件
using namespace std;

int main()
{
	list<char> lc1;
	lc1.push_back('a');
	lc1.push_back('b');
	lc1.push_back('c');
	lc1.push_back('d');
	
	// 算法库逆置
	reverse(lc1.begin(), lc1.end());

	for (auto x : lc1)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

在这里插入图片描述

8.2 sort

  • 功能:排序list
  • 注意:list底层的sort是归并算法

在这里插入图片描述

【代码示例】

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> ll;
	ll.push_back(5);
	ll.push_back(4);
	ll.push_back(1);
	ll.push_back(2);
	ll.push_back(6);
	ll.push_back(3);
	
	ll.sort();
	for (auto x : ll)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

在这里插入图片描述

同样的,算法库里面也设计了一个sort,但注意:算法库里面的sort对于list是用不了的。

事实上,这是因为迭代器从功能上进行了分类:单向迭代器(只能++)、双向迭代器(可以++/--)、随机迭代器(可以++/--,还可以-+

那我们怎么知道一个容器是什么类型的迭代器呢?很简单,查文档就行:点击跳转

这里我为大家总结了一些常见容器的迭代器:

在这里插入图片描述

我们再来看看,由于算法库中的sort是随机迭代器(如下所示)

在这里插入图片描述

list是双向迭代器(如下所示)

在这里插入图片描述

因此,由于list适合双向迭代器,所以用不了库里的sort(RadomAccessIterator)

当然了,如果见到InputIterator,那么说明所有迭代器都可以用。

在这里插入图片描述

因此,list接口中实现sort还是有点意义的。我只是说“有点”。

  • 在排序中,vector的排序速度要比list。这是因为vector是一个连续存储的容器,它的元素在内存中是相邻的,可以利用局部性原理进行高效的排序算法,如快速排序。

  • 相比之下,list是一个链表结构,其元素在内存中是分散存储的,无法直接利用局部性原理,因此排序操作的性能通常较慢。

  • 在某些特定情况下,list可能更适合进行插入和删除操作,因为它对于这些操作的开销较小。因此,在选择容器时,应该根据具体的需求来决定使用哪种容器。

8.3 remove

功能:删除指定数据

在这里插入图片描述

【代码示例】

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> lit;
	lit.push_back(1);
	lit.push_back(2);
	lit.push_back(3);
	lit.push_back(4);
	
	for (auto x : lit)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 删除4
	lit.remove(4);
	for (auto x : lit)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

在这里插入图片描述

8.4 unique

功能:去重。但是要注意首先得先进行排序,才能进行去重。否则效率极低

在这里插入图片描述

【代码示例】

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> lit;
	lit.push_back(3);
	lit.push_back(4);
	lit.push_back(1);
	lit.push_back(2);
	lit.push_back(4);
	
	for (auto x : lit)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 去重
	lit.sort();
	lit.unique();
	for (auto x : lit)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

在这里插入图片描述

九、 list与vector的对比

vectolist
底层结构动态顺序表(数组)带头结点的双向循环链表
随机访问支持随机访问operator[],访问某个元素效率O(1)不支持随机访问
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效问题在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效;删除也会造成迭代器失效插入元素不会导致迭代器失效,删除元素会导致迭代器失效
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值