C++初阶-list的使用1
目录
1.std::list简介
2.成员函数
2.1构造函数的使用
2.2list::operator=的使用
3.迭代器
4.容量
4.1list::empty函数的使用
4.2list::size函数的使用
4.3list::max_size函数的使用
5.元素访问
6.修饰符
6.1list::assign函数的使用
6.2push_back和pop_back和push_front和pop_front函数的使用
6.3emplace_back和emplace_front和emplace的函数的使用
基本概念
完整示例代码
输出结果分析
关键点说明
实际应用场景
6.4list::insert和list::erase函数的使用
6.5list::swap函数的使用
6.6list::resize的使用
7.总结
1.std::list简介
std::list就是C++实现的双向循环链表。所以需要大家有预备知识,可以看我数据结构的博客:https://blog.csdn.net/2401_86446710/article/details/145201866?sharetype=blogdetail&sharerId=145201866&sharerefer=PC&sharesource=2401_86446710&sharefrom=mp_from_linkhttps://blog.csdn.net/2401_86446710/article/details/145201866?sharetype=blogdetail&sharerId=145201866&sharerefer=PC&sharesource=2401_86446710&sharefrom=mp_from_link
list是一个容器,允许在任意位置进行常数个时间的插入/删除,是一个全名为:带头双向循环链表。
我们可以在C++官网(进入链接:https://legacy.cplusplus.com/reference)中搜索list看到:
那么如果把下面的一大部分的英文翻译一下就能简单了解一下这个list了:
其中forward的意思就是提前的意思,也就是说forward_list是单向链表,不过单向链表的功能在双向链表上面都有,所以我们常使用双向链表。而且在数据结构部分我们了解到:链表是由一个一个结点组成,而且也没有空间是否足够的概念,也就是说链表不会有reserve和是否要扩容的问题,虽然看似简单了,但是每个结点都有指向前面一个结点的prev指针,与指向后面一个结点的next指针,以及存储的数据data。所以在理解上相对于之前的string、vector的难度也上升了!而且现在遍历方式也是不能用下标的方式了,只可以用迭代器的方式了,但是链表这种东西复杂了,应对多种情况也是有很大的提升的。所以学好链表这一个容器还是有比较大的意义的!
链表这个容器包含于头文件:<list>
此外list还有以下的typedef后的类型,后面可能会用到,这是英语界面:
这是简单翻译后的中文界面:
这些类型我们基本上可以通过英文能看出来,而且我们基本上在vector上见过了。
2.成员函数
成员函数包括以下函数:
2.1构造函数的使用
除了第5个我们现在用不上以外,其他的我们基本上都是要使用的,其中我们不需要管那个const allocator_type& alloc这个参数,因为那个涉及到内存池,所以我们在这里可以不看,参数我们也不考虑,因为有缺省值。
所以这样看了之后:(1)这是一个无参的构造函数;(2)这是一个用n个val或者构造n个结点的空间但不进行初始化的构造函数;(3)这是用一段迭代器区间进行初始化;(4)这是一个拷贝构造函数,用一个已经实例化的对象去实例化另外一个对象;(6)这是一个用一个数组实例化对象的构造。
以下是每个函数的代码演示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
#include<vector>
//构造函数的使用
int main()
{//(1)list<int> l1;//(2).1list<int> l2(3);//(2).2list<int> l3(4, 1);//(3)vector<int> v1 = { 3,5,62,5,6,3,7,9,20 };list<int> l4(v1.begin() + 1, v1.end() - 2);//(4)list<int>l5(l3);//(6)list<int>l6 = { 3,4,6,5,7,3 };//也可以这样写//list<int>l6({3,4,6,5,7,3});//这个是迭代器实现的,所以可以这样遍历for (const auto& e : l1){cout << e << " ";}cout << endl;for (const auto& e : l2){cout << e << " ";}cout << endl;for (const auto& e : l3){cout << e << " ";}cout << endl;for (const auto& e : l4){cout << e << " ";}cout << endl;for (const auto& e : l5){cout << e << " ";}cout << endl;for (const auto& e : l6){cout << e << " ";}cout << endl;return 0;
}
那么运行结果为:
可以知道,如果是用(2)的第一个函数,那么开始就会初始化为0,而不是不初始化!
2.2list::operator=的使用
我们可以看到这个函数有三种赋值方式,第二种是右值引用,这个会在之后讲解。
(1)就是直接拿一个已经实例化的list对象进行赋值;(3)拿一个数组进行赋值,和我们之前构造函数(6)的意思差不多。
用法如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
#include<string>
//operator=的使用
int main()
{//(1)list<string> l1;string a = "Hello world";l1.push_back(a);//这样是不行的,因为这相当于拷贝构造//list<string> l2 = l1;//这样才是正确的list<string> l2;l2 = l1;//(3)list<string> l3;l3 = { "张三","李四","王五" };for (const auto& e : l1){cout << e << " ";}cout << endl;for (const auto& e : l2){cout << e << " ";}cout << endl;for (const auto& e : l3){cout << e << " ";}cout << endl;return 0;
}
那么运行结果如下:
3.迭代器
迭代器包括以下函数:
这些函数我们用begin和end作为共同点,如果前面没有任何字母,那么就是普通迭代器;如果前面有'r',那么就是反向迭代器;如果前面有'c',那么就是const迭代器;如果前面有"cr",那么就是const的反向迭代器,这些函数我直接用deepseek给出的示例就可以了:
#include <vector>
#include <iostream>int main()
{std::vector<int> v = {5, 4, 3, 2, 1};// 常规遍历std::cout << "Normal: ";for (auto it = v.begin(); it != v.end(); ++it) {std::cout << *it << " ";}// 反向遍历std::cout << "\nReverse: ";for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {std::cout << *rit << " ";}// 常量遍历const auto& cv = v;std::cout << "\nConst: ";for (auto cit = cv.cbegin(); cit != cv.cend(); ++cit) {std::cout << *cit << " ";}// 常量反向遍历std::cout << "\nConst Reverse: ";for (auto crit = cv.crbegin(); crit != cv.crend(); ++crit) {std::cout << *crit << " ";}
}
/* 输出:
Normal: 5 4 3 2 1
Reverse: 1 2 3 4 5
Const: 5 4 3 2 1
Const Reverse: 1 2 3 4 5
*/
4.容量
容量部分包含以下函数:
4.1list::empty函数的使用
其中noexcept
是 C++11 引入的关键字,用于指定函数是否会抛出异常,是现代 C++ 异常处理机制的重要组成部分。noexcept
的正确使用可以显著提升C++程序的性能和可靠性,是现代C++编程的重要特性。这里就不做过多的noexcept的解释了,因为我们主要的目的是为了演示该函数的用法:
这是在list::empty后的解释的中文版本,我们可以知道它用处,那么我来简单演示一下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
//empty的使用
int main()
{list<int> l1 = { 2, 3, 4, 5, 6, 7 };cout << l1.empty() << endl;list<int> l2;cout << l2.empty() << endl;return 0;
}
运行结果为:
0代表false,非0代表true,所以l1非空,反之代表l2为空。
4.2list::size函数的使用
这个函数就是返回所存储数据的个数。太简单了,我就不演示了!
4.3list::max_size函数的使用
这个函数的功能就是返回可以容纳的最大元素数,也就是我们在数据结构中常说的capacity。这个函数也很简单,所以也不演示用法了!
5.元素访问
元素访问包括以下两个函数:
这两个函数的声明如下:
两个函数的功能分别是返回对列表容器中第一个元素的引用和对列表容器中最后一个元素的引用,使用如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
//front和back函数的使用
int main()
{list<int> a = { 3,5,6,3,4,6,43,2,4,5,2,9,0 };cout << a.front() << endl;cout << a.back() << endl;const list<int> b = { 45,6345,35,355,2,210,90,8 };cout << b.front() << endl;cout << b.back() << endl;return 0;
}
那么运行结果如下:
6.修饰符
修饰符部分包含以下函数:
由于涉及的函数比较多,我值讲解一些比较重要的内容:
6.1list::assign函数的使用
这个函数的功能如下:
这里面都有函数的使用解释,这个函数和构造函数有很多相似的,所以这里就直接演示用法了:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
#include<string>
//assign函数的使用
int main()
{//(1)string a("Hello world");list<char> l1;l1.assign(a.begin() + 2, a.end() - 2);//(2)list<string> l2;l2.assign(3, "Hello");//(3)string b("张三");string c("李四");string d("王五");list<string> l3;l3.assign({ b,c,d });for (const auto& e : l1){cout << e << " ";}cout << endl;for (const auto& e : l2){cout << e << " ";}cout << endl;for (const auto& e : l3){cout << e << " ";}cout << endl;return 0;
}
那么运行结果为:
6.2push_back和pop_back和push_front和pop_front函数的使用
这些函数都是针对一个结点的,而且也很容易知道它们的作用,一般用得也比较多,所以我在这里演示一下用法:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
//push_front和push_back和pop_front和pop_back函数的使用
int main()
{list<int> a({ 5,3,45,2,4,3,2,1,7 });for (auto& e : a){cout << e << " ";}cout << endl;a.push_back(56);a.push_back(42);for (auto& e : a){cout << e << " ";}cout << endl;a.push_front(23);a.push_front(34);a.push_front(11);for (auto& e : a){cout << e << " ";}cout << endl;a.pop_back();for (auto& e : a){cout << e << " ";}cout << endl;a.pop_front();for (auto& e : a){cout << e << " ";}cout << endl;return 0;
}
那么运行结果如下:
6.3emplace_back和emplace_front和emplace的函数的使用
这三个函数涉及的地方比较多,所以我直接用deepseek来讲解这三个函数的用法了:
基本概念
-
emplace_back - 在list末尾直接构造元素
-
emplace_front - 在list开头直接构造元素
-
emplace - 在指定位置直接构造元素
这些函数相比push_back/push_front/insert的优势在于避免了临时对象的创建和拷贝/移动操作。
完整示例代码
#include <iostream>
#include <list>
#include <string>class Person {
public:std::string name;int age;Person(const std::string& n, int a) : name(n), age(a) {std::cout << "构造Person: " << name << " (" << age << ")\n";}Person(const Person& other) : name(other.name), age(other.age) {std::cout << "拷贝Person: " << name << " (" << age << ")\n";}Person(Person&& other) noexcept : name(std::move(other.name)), age(other.age) {std::cout << "移动Person: " << name << " (" << age << ")\n";}
};int main() {std::list<Person> people;std::cout << "=== 使用emplace_back在末尾添加 ===" << std::endl;people.emplace_back("张三", 25); // 直接在list末尾构造std::cout << "\n=== 使用emplace_front在开头添加 ===" << std::endl;people.emplace_front("李四", 30); // 直接在list开头构造std::cout << "\n=== 使用emplace在指定位置添加 ===" << std::endl;auto it = people.begin();++it; // 指向第二个位置people.emplace(it, "王五", 22); // 在第二个位置构造std::cout << "\n=== 最终列表内容 ===" << std::endl;for (const auto& p : people) {std::cout << p.name << ", " << p.age << "岁\n";}return 0;
}
输出结果分析
=== 使用emplace_back在末尾添加 ===
构造Person: 张三 (25)=== 使用emplace_front在开头添加 ===
构造Person: 李四 (30)=== 使用emplace在指定位置添加 ===
构造Person: 王五 (22)=== 最终列表内容 ===
李四, 30岁
王五, 22岁
张三, 25岁
关键点说明
-
构造效率:
-
整个过程只看到"构造Person"的输出
-
没有发生任何拷贝或移动操作
-
相比push_back/insert等更高效
-
-
参数传递:
-
参数直接传递给Person的构造函数
-
不需要先创建Person对象再插入
-
-
插入位置:
-
emplace_back
- 末尾 -
emplace_front
- 开头 -
emplace
- 任意指定位置
-
-
返回值:
-
emplace_back
返回void (C++17前) / 引用到插入元素 (C++17起) -
emplace_front
返回void (C++17前) / 引用到插入元素 (C++17起) -
emplace
返回指向新插入元素的迭代器
-
实际应用场景
-
构造成本高的对象(如大型类)
-
不可拷贝/移动的对象(只能用emplace)
-
需要精确控制构造过程的场景
-
性能敏感的关键代码路径
这些emplace函数是现代C++高效编程的重要组成部分,能显著提升容器操作的性能。
6.4list::insert和list::erase函数的使用
这个函数的第一个参数都为position,即插入的位置,那么我们就可以在对应的位置插入对应的数据(并且返回该位置开始的迭代器):
(1)在该位置插入一个值为val的数据;(2)在该位置插入n个val的数据;(3)插入一段迭代器区间;(5)插入一个类型为value_type(即T)的intitializer_list对象。
erase函数可以清除一个position这个迭代器位置的数据,也可以清除一个迭代器区间。那么我们就可以通过这两个函数进行演示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
#include<vector>
//insert和erase
int main()
{list<int> a({ 2,3,4,6,3,4,2,9,0 });for (const auto& e : a){cout << e << " ";}cout << endl;//(1)//错误//a.insert(a.begin()+2, 4);//还是错误//auto il = a.begin();//il = il + 2;//正确使用方法1auto it1 = a.begin();//把it移动到第三个数据的位置//第二个参数是往后的步数std::advance(it1, 2);a.insert(it1, 4);for (const auto& e : a){cout << e << " ";}cout << endl;//(2)//正确使用方法2//第四个数据的位置auto it2 = std::next(a.begin(), 3);a.insert(it2, 3, 6);for (const auto& e : a){cout << e << " ";}cout << endl;//(3)auto it3 = std::next(a.begin(), 1);//错误//原因:没有这些操作符//a.insert(it3, a.begin() + 3, a.end() - 1);//正确vector<int> v1({ 4,3,42,2 });a.insert(it3, v1.begin() + 1, v1.end() - 1);for (const auto& e : a){cout << e << " ";}cout << endl;//(5)auto it4 = std::next(a.begin(), 5);a.insert(it4, { 5,3,2,1 });for (const auto& e : a){cout << e << " ";}cout << endl;//(1)auto it5 = std::next(a.begin(), 4);a.erase(it5);for (const auto& e : a){cout << e << " ";}cout << endl;//(2)auto it6 = a.begin();auto it7 = std::next(a.begin(), 7);a.erase(it6, it7);for (const auto& e : a){cout << e << " ";}cout << endl;return 0;
}
那么最终运行结果如下:
我们发现如果是连续的插入和删除,最好还是用vector,但是vector需要挪动数据,效率比较低,但是我们写起来又比较简单,所以,二者有各自的好处吧!
没想到一个insert函数要挪动数据我们很难获取位置,所以建议用vector更好一些。
6.5list::swap函数的使用
这个函数就是交换两个链表的包含头结点的所有结点,但是它的实现是只交换两个链表的头结点以及尾结点即可,所以交换效率很高,这里演示一下它的用法:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
//swap函数的使用
int main()
{list<int> l1({ 4,5,32,4,6,7,0,10,3,4 });list<int> l2(4, 3);cout << "交换前的l1:";for (const auto& e : l1){cout << e << " ";}cout << endl;cout << "交换前的l2:";for (const auto& e : l2){cout << e << " ";}cout << endl;l1.swap(l2);//也可以//l2.swap(l1)cout << "交换后的l1:";for (const auto& e : l1){cout << e << " ";}cout << endl;cout << "交换后的l2:";for (const auto& e : l2){cout << e << " ";}cout << endl;return 0;
}
那么运行结果为:
6.6list::resize的使用
这个函数的功能就是调整原来的size到n,如果原来的size比n大,那么就会删除从n到size的这些结点;反之则可能添加至n个结点(不会涉及到扩容),并把添加的每个结点所存储的数据置为val。
用法如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<list>
#include<iostream>
using namespace std;
//resize函数的使用
int main()
{list<int> l({ 5,3,2,4,7,10 });cout << "resize之前:";for (const auto& e : l){cout << e << " ";}cout << endl;l.resize(5, 10);cout << "resize第一次之后:";for (const auto& e : l){cout << e << " ";}cout << endl;l.resize(10);cout << "resize第二次之后:";for (const auto& e : l){cout << e << " ";}cout << endl;return 0;
}
那么运行结果如下:
7.总结
这是list的一些基本的函数,还有一些函数由于与之前我们学过的vector和string有些不同,所以需要进行更多的奖金,而且下一讲还会涉及到额外的知识,这个知识也是很多人没注意到的。OK,这讲内容就到这里,喜欢的可以一键三连哦,下讲再见!