std::list 中 C++ 与例子

⚡ 智能摘要

std::list 中 C++ 是一个以双向链表实现的序列容器,允许在任何位置快速插入和删除元素,同时将元素存储在非连续内存中,并支持双向顺序访问而不是随机访问。

  • 🔗 双向链表: 每个元素都保留着与其前一个节点和后一个节点的链接,因此 std::list 数据存在于非连续内存中。
  • 快速插入和删除: 与移动元素的向量不同,在已知位置添加或删除元素的时间复杂度是恒定的。
  • 🚫 无随机访问: 元素只能从两端依次遍历,因此无法使用 list[3] 之类的索引。
  • 🧩 构造函数: 默认构造函数、填充构造函数、范围构造函数、复制构造函数、移动构造函数和初始化列表构造函数以不同的方式构建 std::list。
  • 🛠️ 成员功能: push_front()、push_back()、insert()、erase()、size()、reverse() 和 merge() 用于管理列表内容。
  • 🤖 人工智能辅助: GitHub Copilot 和类似的助手可以根据简短的注释生成 std​​::list 声明、迭代器以及插入或删除逻辑。

std::list 中 C++

什么是 std::list?

In C++`std::list` 指的是一个存储容器。`std::list` 允许你从任意位置插入和删除元素。`std::list` 实现为双向链表。这意味着可以双向顺序地访问列表中的数据。

标准模板库列表不支持快速随机访问,但支持从所有方向的顺序访问。

您可以将列表元素分散到不同的内存块中。顺序访问数据所需的信息存储在容器中。std::list 可以在运行时根据需要从两端扩展和收缩。内部分配器会自动满足存储要求。

这些特点引出了一个实际问题:什么时候应该真正拿出清单呢?

为什么使用 std::list?

以下是使用 std::list 的原因:

  • 与其他序列容器(如数组和向量)相比,std::list 的表现更好。
  • 它们在插入、移动和取出方面表现更佳。trac从任意位置加载元素。
  • std::list 在执行此类密集操作的算法方面也表现得更好。

既然原因已经明确,下一步就是声明该原因的语法。

列表语法

要定义 std::list,我们必须导入头文件。以下是 std::list 定义语法:

template < class Type, class Alloc =allocator<T> > class list;

以下是上述参数的说明:

  • T – 定义所包含元素的类型。您可以将 T 替换为任何数据类型,甚至是用户自定义类型。
  • Alloc – 定义分配器对象的类型。默认情况下,它使用分配器类模板。它是值相关的,并使用简单的内存分配模型。

例子1

#include <algorithm>
#include <iostream>
#include <list>
int main() {
	std::list<int> my_list = { 12, 5, 10, 9 };

	for (int x : my_list) {
		std::cout << x << '\n';
	}
}

输出:

std::list 创建和迭代示例的输出

以下是代码截图:

C++ 这段代码创建了一个 std::list 对象,并使用 for 循环将其打印出来。

Code 说明:

  1. 包含算法头文件以使用其函数。
  2. 包含 iostream 头文件以使用其功能。
  3. 包含列表头文件以使用其功能。
  4. 调用 main() 函数。程序逻辑应添加在此函数主体内。
  5. 创建一个名为 my_list 的列表,其中包含一组 4 个整数。
  6. 使用 for循环 创建一个循环变量 x。该变量将用于遍历列表元素。
  7. 在控制台上打印出列表的值。
  8. for 循环主体的结束。
  9. main() 函数体结束。

C++ 列出函数

以下是常见的 std::list 函数:

功能 描述
插() 此函数在迭代器指向的位置前插入一个新项目。
推回() 此功能在列表末尾添加一个新项目。
推前() 它在列表的前面添加了一个新项目。
弹出前端() 它删除列表的第一个项目。
尺寸() 该函数确定列表元素的数量。
正面() 确定列表的第一项。
背部() To 确定列表的最后一项。
撤销() 它会反转列表项。
合并() 它合并两个已排序的列表。

构造函数

这是列表 功能 由头文件:

  • 默认构造函数 std::list::list()- 它创建一个空列表,其中包含零个元素。
  • 填充构造函数 std::list::list()-它创建一个包含 n 个元素的列表,并为每个元素分配一个零 (0) 值。
  • 范围构造函数 std::list::list()- 创建一个包含从第一个到最后一个范围内多个元素的列表。
  • 复制构造函数 std::list::list()-它创建一个列表,其中包含现有列表中每个元素的副本。
  • 移动构造函数 std::list::list()- 使用移动语义创建一个包含另一个列表的元素的列表。
  • 初始化列表构造函数 std::list::list()-它使用移动语义创建一个包含另一个列表的元素的列表。

例子2

#include <iostream>
#include <list>
using namespace std;
int main(void) {
	list<int> l;
	list<int> l1 = { 10, 20, 30 };
	list<int> l2(l1.begin(), l1.end());
	list<int> l3(move(l1));  
	cout << "Size of list l: " << l.size() << endl;
	cout << "List l2 contents: " << endl;
	for (auto it = l2.begin(); it != l2.end(); ++it)
	      cout << *it << endl;
	cout << "List l3 contents: " << endl;
	for (auto it = l3.begin(); it != l3.end(); ++it)
		cout << *it << endl;
	return 0;
}

输出:

std::list 构造函数示例的输出

以下是代码截图:

C++ 演示 std::list 的 default、range 和 move 构造函数的代码

Code 说明:

  1. 包含 iostream 头文件以使用其功能。
  2. 包含列表头文件以使用其功能。
  3. 在代码中包含 std 命名空间以便使用其类而不调用它。
  4. 调用 main() 函数。程序逻辑应添加在此函数主体内。
  5. 创建一个名为 l 的空列表。
  6. 创建一个名为 l1 的列表,其中包含一组 3 个整数。
  7. 创建一个名为 l2 的列表,其中包含列表 l1 中的所有元素(从头到尾)。
  8. 使用移动语义创建一个名为 l3 的列表。列表 l3 将具有与列表 l2 相同的内容。
  9. 在控制台上与其他文本一起打印名为 l 的列表的大小。
  10. 在控制台上打印一些文本。
  11. 创建一个名为it的迭代器并用它来迭代名为l2的列表的元素。
  12. 在控制台上打印名为 l2 的列表的元素。
  13. 在控制台上打印一些文本。
  14. 创建一个名为it的迭代器并用它来迭代名为l3的列表的元素。
  15. 在控制台上打印名为 l3 的列表的元素。
  16. 程序成功完成后必须返回值。
  17. main() 函数体结束。

容器属性

以下是容器属性的列表:

特性 描述
序列 序列容器按严格的线性顺序排列其元素。元素通过其在序列中的位置进行访问。
双向链表 每个元素都包含有关如何定位前一个和下一个元素的信息。这允许插入和删除操作在恒定时间内完成。
分配器感知 分配器对象用于动态修改存储大小。

插入列表

我们可以使用不同的函数向列表中插入值。下面我们来演示一下:

例子3

#include <algorithm>
#include <iostream>
#include <list>
int main() {
	std::list<int> my_list = { 12, 5, 10, 9 };
	my_list.push_front(11);
	my_list.push_back(18);
	auto it = std::find(my_list.begin(), my_list.end(), 10);
	if (it != my_list.end()) {
		my_list.insert(it, 21);
	}
	for (int x : my_list) {
		std::cout << x << '\n';
	}
}

输出:

将元素插入 std::list 后的输出

以下是代码截图:

C++ 使用 push_front、push_back 和 insert 操作 std::list 的代码

Code 说明:

  1. 包含算法头文件以使用其函数。
  2. 包含 iostream 头文件以使用其功能。
  3. 包含列表头文件以使用其功能。
  4. 调用 main() 函数。程序逻辑应添加在此函数主体内。
  5. 创建一个名为 my_list 的列表,其中包含一组 4 个整数。
  6. 将元素 11 插入到名为 my_list 的列表的前面。
  7. 将元素 18 插入到名为 my_list 的列表的末尾。
  8. 创建一个迭代器 it 并使用它从列表 my_list 中查找元素 10。
  9. 使用 if 语句来确定是否找到上述元素。
  10. 如果找到元素 21,则将元素 XNUMX 插入到上述元素之前。
  11. if 语句主体结束。
  12. 使用 for 循环创建循环变量 x。此变量将用于迭代列表元素。
  13. 在控制台上打印出列表的值。
  14. 循环主体的结束。
  15. main() 函数体结束。

列表中的元素可以很容易地从列表中删除。

从列表中删除

可以从列表中删除项目。`erase()` 函数允许您从列表中删除一个项目或一系列项目。

  • 要删除单个项目,只需传递一个整数位置即可。该项目将被删除。
  • 要删除一个范围,你需要传入起始迭代器和结束迭代器。让我们来演示一下。

例子4

#include <algorithm>
#include <iostream>
#include <list>
using namespace std;
int main() {
	std::list<int> my_list = { 12, 5, 10, 9 };
	cout << "List elements before deletion: ";
	for (int x : my_list) {
		std::cout << x << '\n';
	}
	list<int>::iterator i = my_list.begin();
	my_list.erase(i);
	cout << "\nList elements after deletion: ";
	for (int x : my_list) {
		std::cout << x << '\n';
	}
	return 0;
}

输出:

从 std::list 中删除元素后的输出

以下是代码截图:

C++ 使用 std::list 的 erase 函数的代码

Code 说明:

  1. 包含算法头文件以使用其函数。
  2. 包含 iostream 头文件以使用其功能。
  3. 包含列表头文件以使用其功能。
  4. 在我们的程序中包含 std 命名空间,以便使用它的类而不调用它。
  5. 调用 main() 函数。程序逻辑应添加在此函数主体内。
  6. 创建一个名为 my_list 的列表,其中包含一组 4 个整数。
  7. 在控制台上打印一些文本。
  8. 使用 for 循环创建循环变量 x。此变量将用于迭代列表元素。
  9. 在控制台上打印出列表的值。
  10. for 循环主体的结束。
  11. 创建一个指向列表第一个元素的迭代器 i。
  12. 使用迭代器i指向的erase()函数。
  13. 在控制台上打印一些文本。
  14. 使用 for 循环创建循环变量 x。此变量将用于迭代列表元素。
  15. 在控制台上打印出列表的值。这是在删除之后发生的。
  16. for 循环主体的结束。
  17. 程序成功完成后必须返回一个值。
  18. main() 函数体结束。

常见问题

std::vector 将元素存储在连续内存中,随机访问时间复杂度为 O(1);而 std::list 是一个双向链表,任意位置的插入或删除操作时间复杂度均为 O(1)。建议选择 vector 用于索引,而选择 list 用于频繁的中间插入操作。

不。`std::list` 没有随机访问运算符,所以 `list[2]` 无法编译。要访问某个元素,需要从 `begin()` 或 `end()` 一次迭代一个节点,对于较深的节点,这需要线性 O(n) 的时间复杂度。

std::list 是一个双向链表,支持双向遍历和 push_back 方法。std::forward_list 是一个单向链表,只能向前移动,每个节点占用的内存更少,并且不提供 size() 或反向迭代器。

调用成员函数 `my_list.sort()`,其运行时间约为 N log N,并且能够保持相等元素的稳定性。`std::sort` 算法无法正常工作,因为它需要随机访问迭代器。将 `std::greater` 传递给 `sort()` 以进行降序排序。

一旦持有指向节点位置的迭代器,插入或删除节点的时间复杂度就变为常数 O(1),因为只有相邻的指针会发生变化。即使先通过遍历找到该位置,时间复杂度仍然为 O(n)。

是的。`std::list` 不是集合,所以它可以自由地存储重复值。每次 `push_back`、`push_front` 或 `insert` 操作都会添加一个新节点,而不管现有节点的内容如何。当需要排除重复元素时,请使用 `std::set`。

是的。 GitHub 副驾驶 它会根据简短的注释或函数名写入 std::list 声明、迭代器循环,以及插入或删除调用。当连续存储更适合特定任务时,它通常会建议使用 std::vector。

AI 代码助手可以自动补全 STL 容器代码,标记错误的迭代器用法,将 std::list 转换为 std::vector,并解释复杂度权衡。它们可以加快学习 STL 的速度,但每个建议仍然需要审核。

总结一下这篇文章: