C++初阶:STL详解(六)——list的介绍和使用

✨✨小新课堂开课了,欢迎欢迎~✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++:由浅入深篇

小新的主页:编程版小新-CSDN博客

前言:

前面我们已经了解了string,vector的用法以及他们的底层实现,今天我们再来学习了一个新的容器list,这几个容器之间有很大的相通性,我们来一起看一下吧。

一.什么是list ?

std::list是C++模板库(STL)中的一个双向链表容器。它提供了一种灵活的方式来储存和操作一组元素。

和vector,string一样,list也是一个模板类,使用时只需指定元素类型,编译器会确保类型安全。它定义在头文件(include<list>)中,可以使用list来存储和访问任意类型的对象。

二.list的特性

1**.双向链表** ------每个节点包含指向前一个和后一个的指针,因此它可以在进行高效的插入删除操作

2.动态大小 ------根据需要动态增加和减少元素个数,没有空间浪费

3.非连续储存 ------元素在内存中是不连续的,不支持下标的随机访问

4.迭代器支持------支持STL迭代器,可以使用标准的算法进行遍历和操作。在进行插入和删除操作时,除了被指向被删除元素的迭代器失效,其他迭代器通常不失效,这点与vector也不同。vector在进行插入操作时迭代器不一定失效,在进行删除操作时被删除元素及其后面元素的迭代器都失效。

C++初阶:STL详解(四)------vector迭代器失效问题-CSDN博客(可供参考)

三.list的文档介绍

list - C++ Reference(英文版)

https://zh.cppreference.com/w/cpp/container/list(中文版)

四.list的使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理。在实际中我们熟悉常见的接口就可以,下面列出了哪些接口是要重点掌握的 。我们在后面模拟实现的时候,也是实现这些常见的接口。

4.1list的构造

|----------------------------------------------------------|-----------------------------|
| 构造函数( (constructor)) | 接口说明 |
| list (size_type n, const value_type& val =value_type()) | 构造的list中包含n个值为val的元素 |
| list() | 构造空的list |
| list (const list& x) | 拷贝构造函数 |
| list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |

1.list() :构造一个空的list

cpp 复制代码
list<int> st1;//构造一个空list

2.list (size_type n, const value_type& val =value_type()) :构造一个list,元素个数为n,元素为val

cpp 复制代码
list<int> st2(10, 1);//用n个值为val的元素构造一个list

3.list (const list& x) :拷贝构造

cpp 复制代码
list<int> st3(st2);//拷贝构造

4.list (InputIterator first, InputIterator last) : 使用迭代器区间进行初始化构造

cpp 复制代码
list<int> st4(st2.begin(), st2.end());//使用一个迭代器区间构造list

补充:构造数组某段区间的复制品

cpp 复制代码
int myints[] = { 16,2,77,29 };
list<int> st5(myints, myints + sizeof(myints) / sizeof(int));//构造数组某段区间的复制品

4.2list的迭代器

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

|-----------------|---------------------------------------------|
| 函数声明 | 接口说明 |
| begin +end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 。 |
| rbegin+rend | 返回指向列表最后一个元素的反向迭代器+返回指向列表第一个元素之前的反向迭代器。 |

begin+end

cpp 复制代码
//正向迭代器遍历
void list1()
{
	list<int> st(10, 1);

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

	while (it != st.end())
	{
		cout << *it << " ";
		it++;
	}

	cout << endl;
}

rbegin+rend

cpp 复制代码
//反向迭代器遍历
void list2()
{
	list<int> st(10, 1);

	list<int>::reverse_iterator rit = st.rbegin();

	while (rit != st.rend())
	{
		cout << *rit << " ";
		rit++;
	}

	cout << endl;
}

注意:

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

4.3list的容量

|-------|------------------------------|
| 函数声明 | 接口说明 |
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list中有效节点的个数 |

size和empty

cpp 复制代码
void list3()
{
	list<int>st(10, 1);

	cout << st.size() << endl;//list的有效节点个数

	cout << st.empty() << endl;//不为空返回0,为空返回非0
}

4.4list的访问

|-------|--------------------|
| 函数声明 | 接口说明 |
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |

front和back

cpp 复制代码
void list4()
{
	list<int> st;

	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);
	st.push_back(5);

	cout << st.front() << endl;//返回第一个节点中值的引用 1
	cout << st.back() << endl;//返回最后一个节点值的引用 5
}

4.5list的增删查改

push_front和pop_front

push_front用于头插一个元素,pop_front用于头删一个元素

cpp 复制代码
void list5()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);

	st.push_front(5);//在首元素前插入
	cout << "插入前:";
	for (auto ch : st)
	{
		cout << ch << " ";//5 1 2 3 4
	}

	cout << endl;
	st.pop_front();//删除首元素

	cout << "插入后:";
	for (auto ch : st)
	{
		cout << ch << " ";// 1 2 3 4
	}
}

push_back和pop_back

push_back用于尾插一个元素,pop_back用于尾删一个元素。

cpp 复制代码
void list6()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);

	st.push_back(5);//在尾部插入
	for (auto ch : st)
	{
		cout << ch << " ";// 1 2 3 4 5
	}

	cout << endl;
	st.pop_back();//删除尾部元素

	cout << "插入后:";
	for (auto ch : st)
	{
		cout << ch << " ";// 1 2 3 4
	}
}

insert

list当中的insert函数支持三种插入方式:

  1. 在指定迭代器位置插入一个数。
  2. 在指定迭代器位置插入n个值为val的数。
  3. 在指定迭代器位置插入一段迭代器区间(左闭右开)。
cpp 复制代码
void list7()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);

		//在指定迭代器位置插入一个数
	st.insert(st.begin(), 0); //在容器开头插入0

	//在指定迭代器位置插入n个val
	st.insert(st.begin(), 5, -1); //在容器开头插入5个-1

	//在指定迭代器位置插入一段迭代器区间
	int myarray[] = { 501,502,503 };
	st.insert(st.begin(), myarray, myarray + 3);//在容器开头插入 501 502 503

	for (auto ch : st)
	{
		cout << ch << " ";// 501 502 503 -1 -1 -1 -1 -1 0 1 2 3 4
	}

}

erase

list当中的erase函数支持两种删除方式:

  1. 删除指定迭代器位置的元素。
  2. 删除指定迭代器区间(左闭右开)的所有元素。
cpp 复制代码
void list8()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);
	st.push_back(5);
	st.push_back(6);

	//删除指定位置迭代器的元素
	st.erase(st.begin()); //删除容器中的第一个元素

	//删除指定迭代器区间(左闭右开)的所有元素。
	st.erase(st.begin(), st.end()); //删除在该迭代器区间内的元素(左闭右开)

	for (auto ch : st)
	{
		cout << ch << " ";
	}

}

resize

resize的两种情况:

  1. 当所给值大于当前的size时,将size扩大到该值,扩大的数据为第二个所给值,若未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
  2. 当所给值小于当前的size时,将size缩小到该值。
cpp 复制代码
void list9()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);
	st.push_back(5);
	st.push_back(6);

	st.resize(10, 9);//将size扩大到10,以9来填充

	for (auto ch : st)
	{
		cout << ch << " ";//1 2 3 4 5 6 9 9 9 9 
	}

	cout << endl;

	st.resize(4);//将size缩小到4

	for (auto ch : st)
	{
		cout << ch << " "; //1 2 3 4
	}

	cout << endl;

}

assign

list中的assign函数用于将新内容分配给容器,替换其当前内容,新内容的赋予方式有两种:

  1. 将n个值为val的数据分配给容器。
  2. 将所给迭代器区间当中的内容分配给容器。
cpp 复制代码
void list10()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);
	st.push_back(5);
	st.push_back(6);

	for (auto ch : st)
	{
		cout << ch << " ";//1 2 3 4 5 6
	}
	cout << endl;

	//将新内容分配给容器,替换当前的内容
	st.assign(10, 1);//用n个val替换
	for (auto ch : st)
	{
		cout << ch << " "; //1 1 1 1 1 1 1 1 1 1
	}
	
	cout << endl;

	//将新内容分配给容器,替换当前的内容
	int myints[] = { 1776,7,4 };
	st.assign(myints, myints + 3);//迭代器区间所指的内容进行替换

	for (auto ch : st)
	{
		cout << ch << " ";//1776 7 4
	}
	cout << endl;


}

swap

交换两个list的元素。

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

	cout << "st1交换前:";
	for (auto ch : st1)
	{
		cout << ch << " ";//1 2 3 4 5 6
	}
	cout << endl;

	list<int> st2;
	st2.push_back(1);
	st2.push_back(2);
	st2.push_back(3);
	st2.push_back(4);

	cout << "st2交换前:";
	for (auto ch : st2)
	{
		cout << ch << " ";//1 2 3 4
	}
	cout << endl;

	st1.swap(st2);
	cout << "st1交换后:";
	for (auto ch : st1)
	{
		cout << ch << " ";//1 2 3 4 
	}
	cout << endl;

	cout << "st2交换前:";
	for (auto ch : st2)
	{
		cout << ch << " ";//1 2 3 4 5 6
	}
	cout << endl;
}

clear

清空容器的内容。

cpp 复制代码
void list12()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);

	for (auto ch : st)
	{
		cout << ch << " ";//1 2 3 4 
	}
	cout << endl;
	cout << st.size() << endl;//4
	st.clear();//清空容器的内容

	cout << st.size() << endl;//0

}

4.6list的操作函数

sort

给容器中的内容排序,默认是排升序。

cpp 复制代码
void list01()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(9);
	st.push_back(3);
	st.push_back(5);
	st.push_back(4);
	st.push_back(10);
	st.push_back(6);

	st.sort();//默认排升序

	for (auto ch : st)
	{
		cout << ch << " ";//1 2 3 4 5 6 9 10
	}
	cout << endl;

}

splice

用于两个list的拼接,其有三种拼接方式:

  1. 将整个容器拼接到另一个容器的指定迭代器位置。
  2. 将容器当中的某一个数据拼接到另一个容器的指定迭代器位置。
  3. 将容器指定迭代器区间的数据拼接到另一个容器的指定迭代器位置。
cpp 复制代码
void list02()
{
	list<int> st1(4, 1);
	list<int> st2(5, 2);
	st1.splice(st1.begin(), st2); //将容器st2拼接到容器st1的开头
	for (auto ch : st1)
	{
		cout << ch << " "; //2 2 2 2 2 1 1 1 1
	}
	cout << endl;

	list<int> st3(4, 1);
	list<int> st4(5, 2);
	st3.splice(st3.begin(), st4, st4.begin()); //将容器st4的第一个数据拼接到容器st4的开头
	for (auto ch : st3)
	{
		cout << ch << " ";//2 1 1 1 1
	}
	cout << endl;


	list<int> st5(4, 1);
	list<int> st6(5,2);
	st5.splice(st5.begin(), st6, st6.begin(), st6.end()); //将容器st6的指定迭代器区间内的数据拼接到容器st6的开头
	for (auto ch : st5)
	{
		cout << ch << " ";//2 2 2 2 2 1 1 1 1
	}
	cout << endl; 
	
}

remove

删除容器中特定元素。

cpp 复制代码
void list03()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);

	st.remove(3);//删除指定元素
	for (auto ch : st)
	{
		cout << ch << " ";//1 2 4
	}
	cout << endl;

}

remove_if

删除容器中满足条件元素。

cpp 复制代码
bool single_digit(const int& val)
{
	return val < 10;
}

void list04()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(3);
	st.push_back(4);
	st.push_back(10);
	st.push_back(20);
	st.push_back(30);
	st.push_back(40);

	st.remove_if(single_digit);//删除满足条件的元素

	for (auto ch : st)
	{
		cout << ch << " ";//10 20 30 40
	}
	cout << endl;

}

unique

删除容器中连续重复元素。

cpp 复制代码
void list05()
{
	list<int> st;
	st.push_back(1);
	st.push_back(2);
	st.push_back(2);
	st.push_back(4);
	st.push_back(10);
	st.push_back(10);
	st.push_back(10);
	st.push_back(40);//有序

	//如果list无序,可以使用sort使其有序
	st.unique();//删除连续重复元素

	for (auto ch : st)
	{
		cout << ch << " ";//1 2 4 10 40
	}
	cout << endl;

}

merge

合并两个有序容器,合并后依然有序。

cpp 复制代码
void list06()
{
	list<int> st1;
	st1.push_back(8);
	st1.push_back(3);
	st1.push_back(1);
	st1.push_back(6);

	list<int> st2;
	st2.push_back(0);
	st2.push_back(10);
	st2.push_back(2);
	st2.push_back(5);

	st1.sort();
	st2.sort();//先满足是两个有序链表

	//合并两个有序链表
	st1.merge(st2);

	//合并后依然有序
	for (auto ch : st1)
	{
		cout << ch << " ";//0 1 2 3 5 6 8 10
	}
	cout << endl;
}

reverse

将容器中的元素位置进行逆置。

cpp 复制代码
void list07()
{
	list<int> st1;
	st1.push_back(8);
	st1.push_back(3);
	st1.push_back(1);
	st1.push_back(6);

	cout << "逆置前:";
	for (auto ch : st1)
	{
		cout << ch << " ";//8 3 1 6
	}
	cout << endl;

	st1.reverse();
	cout << "逆置后:";
	for (auto ch : st1)
	{
		cout << ch << " ";//6 1 3 8
	}
	cout << endl;
}

五.list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。vector的迭代器失效理解了,这里就没什么可以说的了。

C++初阶:STL详解(四)------vector迭代器失效问题-CSDN博客

cpp 复制代码
void TestListIterator1()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
			l.erase(it);
		++it;
	}
} 

运行结果:

改正:万能钥匙:在使用前,对迭代器重新赋值即可。

cpp 复制代码
void TestListIterator()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		l.erase(it++); // it = l.erase(it);
	}
}

六.list 和vector的对比

|-------------|---------------------------------|--------------------------------------|---------------|-------------------------------------------------------------------------|-----------------------------------|---------------|-------------------------------------------------------------------------|-----------------------------------------------|
| | vector | list |
| | vector | list | 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率O(1)**** | 不支持随机访问,访问某个元素O(N) | 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率O(1)**** | 不支持随机访问,访问某个元素O(N) | 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) | 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心 随机访问 | 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行 封装 | 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行 封装 |

总结:

list的常见接口及其用法,我们已经介绍完了,准备好一起来模拟实现list了嘛,我们下篇见。

感谢各位大佬的观看,创作不易,还请各位大佬支持~

相关推荐
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
‘’林花谢了春红‘’4 小时前
C++ list (链表)容器
c++·链表·list
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024065 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it5 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康5 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神6 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
机器视觉知识推荐、就业指导6 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
宅小海6 小时前
scala String
大数据·开发语言·scala