list【1】介绍与使用(超详解哦)

list的介绍与使用

引言

继vector之后,我们继续来介绍STL容器:list

对于容器的使用其实有着类似的模式,参考之前vector的使用可以让我们更快的上手:
戳我看vector介绍与使用详解哦

list介绍

在之前C语言部分我们就认识了链表,STL中的list是一个双向链表

相对于vector的底层空间是连续的,list的底层空间是通过指针链接的链状的不连续的空间

这样的结构相较连续空间扩容更加方便 :不需要重开空间移动数据,只需要在开辟一个新的结点后,将其与前面的结点链接起来即可。其在任意位置插入删除都不需要挪动数据,效率较高 :只需要释放或增加对应结点的数据,然后将剩下的结点链接起来即可;

相较于vector,list不能实现高效的任意访问其中的元素,要随机访问元素只能从头或尾遍历访问,效率较低 。所以list中也就直接没有实现operator[]

所以,list适用于需要经常在任意位置插入删除大量数据,且不需要经常访问任意位置元素的数据的存储

list是一个类模板,可以支持存储任意类型:

接口使用

与vector类似,list也有默认成员函数、迭代器、容量、元素访问、数据修改等接口 (使用库list时需要包含头文件#include<list>

默认成员函数

构造

list的构造函数重载了4个版本,支持无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造 。迭代器区间构造是一个函数模板,即可以使用任一InputIterator迭代器区间来构造list。

使用时与vector类似,由于list是一个类模板,所以在使用list来实例化对象是,就需要显式指定模板参数,如list<int>

cpp 复制代码
#include<iostream>
#include<list>
#include<vector>
using namespace std;

int main()
{
	list<int> l1; //无参初始化
	for (auto e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int> l2(10, 6);    //使用10个6初始化l2
	for (auto e : l2)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<int> v1(10, 5);  //使用10个5初始化v1
	list<int> l3(v1.begin(), v1.end());  //使用迭代器区间初始化
	for (auto e : l3)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int> l4(l3); //拷贝构造
	for (auto e : l4)
	{
		cout << e << " ";
	}
	cout << endl;
}

析构

析构函数会在list对象生命周期结束时由编译器自己调用,以释放其内部资源。

赋值重载

l2赋值给l1

cpp 复制代码
int main()
{
	list<int> l1;
	for (auto e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int> l2(10, 6);
	for (auto e : l2)
	{
		cout << e << " ";
	}
	cout << endl;

	l1 = l2;  //将l2赋值给l1
	for (auto e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
}

迭代器

vector的迭代器的底层就是原生指针,与vector不同,list的迭代器是指针的封装 (在list实现时会详细介绍),但是在使用list迭代器时是与vector几乎一致的。
但是需要注意的是,list的迭代器由于不是原生指针,在+-数字时的成本太高,所以就不支持迭代器+-,只允许迭代器进行++--的双向移动,即为bidirectional iterator

begin返回首元素位置的迭代器,end返回尾元素下一个位置的迭代器、rbegin返回尾元素的反向迭代器、rend返回首元素前一个位置的反向迭代器 。后面的cbegincendcrbegincrend都是返回其对应的const迭代器,但是前面的函数都有重载const版本:

cpp 复制代码
int main()
{
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(5);
	l1.push_back(6);
	for (auto e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << *l1.begin() << endl;
	cout << *(--l1.end()) << endl;
	cout << *l1.rbegin() << endl;
	cout << *(--l1.rend()) << endl;

	return 0;
}

容量

对于list,没有容量的概念,也就不需要有扩容的操作。

所以在容量部分,list只提供了sizeempty,用于返回list中元素个数与判断list是否为空

cpp 复制代码
int main()
{
	vector<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(5);
	l1.push_back(6);
	for (auto e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << "size:" << l1.size() << " ";
	cout << "is empty:" << l1.empty() << " ";

	return 0;
}

元素访问

list不是连续的空间,想要通过下标访问任意位置的元素时成本较高,所以list没有重载operator[] ,而只能通过迭代器访问元素;

同时,list提供了frontback接口来实现对首尾元素的访问

cpp 复制代码
int main()
{
	vector<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(5);
	l1.push_back(6);
	for (auto e : l1)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << l1.front() << endl;
	cout << l1.back() << endl;

	return 0;
}

数据修改


与vector类似

push_frontpush_back用于在头、尾插入一个元素;
pop_frontpop_back用于在头、尾删除一个元素;

cpp 复制代码
int main()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_front(4);
	l.push_front(5);
	l.push_front(6);
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;

	l.pop_back();
	l.pop_front();
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}


insert用于在pos位置(迭代器位置)插入一些数据,包括单个元素、多个指定元素以及一个迭代器区间中的元素:

cpp 复制代码
int main()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	l.push_back(6);
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;

	l.insert(l.begin(), 10);  //相当于头插一个10
	l.insert(l.begin(), 3, 20);  //相当于头插3个20
	list<int> l2(3, 30);  //使用3个30构造l2
	l.insert(l.end(), l2.begin(), l2.end()); //相当于尾插一个l2
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}


erase用于删除pos位置的一个元素或一个迭代器区间中的元素

cpp 复制代码
int main()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	l.push_back(6);

	l.erase(++l.begin(), --l.end());
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;

	l.erase(l.begin());  //相当于头删
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}


resize用于修改list中的容量:

n小于list的元素个数时,就删除至n个元素;

n大于list的元素个数时,就用指定元素value补足:

cpp 复制代码
int main()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	l.push_back(6);
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;

	l.resize(3);  //删至3个元素
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;

	l.resize(10, 6); //补到10个元素,不足的用6补
	for (auto e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

swap用于交换两个list中的数据
clear用于清理list中的数据,这里就不再演示了。

list的算法接口


同时,list还提供了一些算法接口,如reversesort等。

  1. 对于reverse算法库中的接口,是通过循环左右交换的方式来实现的:

    对于list这样,通过逻辑连续的线性结构,我们可以使用更为简单的改变链接顺序的方法来实现反转 ,list的算法中就是这样实现的(戳我看反转链表OJ 思路与该算法类似)

  2. 对于算法库中sort的接口,使用的是快排的方式来排序的,快排算法是需要三数取中来优化的,即他需要能够+-RandomAccessIterator迭代器作为参数:

    而list的迭代器却不支持+-的操作,所以不能使用算法库中的sort函数,所以list提供了这个接口。

    但是list中的sort函数效率远没有快排的效率高,所以在进行大量list数据的排序时,可以将其数据转移到vector中排序后再转移回来;也可以干脆不适用list来存储需要大量排序的数据

总结

到此,关于list的简介与接口使用就介绍完了

接下来会进行模拟实现,欢迎持续关注哦

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

相关推荐
秋夫人35 分钟前
B+树(B+TREE)索引
数据结构·算法
代码雕刻家1 小时前
数据结构-3.1.栈的基本概念
c语言·开发语言·数据结构
AlexMercer10121 小时前
【C++】二、数据类型 (同C)
c语言·开发语言·数据结构·c++·笔记·算法
^^为欢几何^^3 小时前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
ahauedu4 小时前
案例分析-Stream List 中取出值最大的前 5 个和最小的 5 个值
数据结构·list
X同学的开始6 小时前
数据结构之二叉树遍历
数据结构
AIAdvocate9 小时前
Pandas_数据结构详解
数据结构·python·pandas
jiao0000110 小时前
数据结构——队列
c语言·数据结构·算法
kaneki_lh10 小时前
数据结构 - 栈
数据结构
铁匠匠匠10 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计