【C++】List容器(1)-STL标准库-List举例说明-定义和初始化-成员函数的使用-运行效率对比-链接数据结构-和顺序表的对比

C++学习:list容器详解(一)

1.STL标准库

    C++ Standard Template Library(STL)是C++编程语言的一个库,它提供了一系列模板化的数据结构(比如向量、列表、队列等)和算法(比如排序、搜索、变换等),用于快速开发高效的程序。STL是C++程序设计中非常核心的部分,它不仅提高了编程的效率,也保证了程序的性能和可维护性。STL算法是标准算法,我们可以把它们应用在那些容器中的对象上。这些算法都有很著名的执行特性。它们可以给对象排序,删除它们,给它们记数,比较,找出特殊的对象,把它们合并到另一个容器中,以及执行其他有用的操作。 STL iterator就象是容器中指向对象的指针。STL的算法使用iterator在容器上进行操作。Iterator设置算法的边界 ,容器的长度,和其他一些事情。举个例子,有些iterator仅让算法读元素,有一些让算法写元素,有一些则两者都行。 Iterator也决定在容器中处理的方向。通过调用容器的成员函数begin()来得到一个指向一个容器起始位置的iterator。你可以调用一个容器的 end() 函数来得到过去的最后一个值(就是处理停在那的那个值)。 STL所有的东西,容器、算法、和允许算法工作在容器中的元素上的iterator。 算法以合适、标准的方法操作对象,并可通过iterator得到容器精确的长度。一旦做了这些,它们就在也不会“跑出边界”。 还有一些其他的对这些核心组件类型有功能性增强的组件,例如函数对象。我们将会看到有关这些的例子,现在 ,我们先来看一看STL的list。

2. list

std::list 是 C++ 标准库中的一个双向链表容器,它允许在序列的任何位置进行高效的插入和删除操作。以下是对 std::list 的定义、初始化以及常用成员函数的详细解释,并包括一个对比同类型算法运行效率的示例。

2.1 定义与初始化

定义 std::list 非常简单,只需要指定列表中元素的类型即可:

cpp代码

#include <list>  std::list<int> myList; // 定义一个存储 int 类型元素的 list

初始化 std::list 可以通过插入元素的方式来实现

cpp代码

#include <list>  
#include <iostream>  int main() {  std::list<int> myList; // 空 list  // 插入元素  myList.push_back(1);  myList.push_front(0);  myList.insert(myList.begin(), 2); // 在开始位置插入元素  myList.insert(myList.end(), 3);   // 在末尾位置插入元素  // 输出 list 的内容  for (const auto& elem : myList) {  std::cout << elem << ' ';  }  std::cout << '\n';  return 0;  
}

输出:2 0 1 3

2.2 成员函数使用

std::list 提供了许多成员函数来操作链表。以下是一些常用的成员函数:

push_back(const value_type& val): 在链表尾部插入一个元素。
push_front(const value_type& val): 在链表头部插入一个元素。
pop_back(): 删除链表尾部的元素。
pop_front(): 删除链表头部的元素。
insert(iterator pos, const value_type& val): 在指定位置插入一个元素。
erase(iterator pos): 删除指定位置的元素。
remove(const value_type& val): 删除所有等于给定值的元素。
sort(): 对链表进行排序。
unique(): 删除所有连续重复的元素,只保留一个。
begin(), end(): 返回指向链表第一个元素和尾后位置的迭代器。
cbegin(), cend(): 返回指向链表第一个元素和尾后位置的常量迭代器。
size(): 返回链表中元素的数量。
empty(): 检查链表是否为空。

代码示例:

#include <iostream>  
#include <list>  int main() {  // 创建一个空的 std::list  std::list<int> myList;  // 使用 push_back 在链表尾部插入元素  myList.push_back(1);  myList.push_back(2);  myList.push_back(3);  // 打印链表元素  std::cout << "List after push_back: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 push_front 在链表头部插入元素  myList.push_front(0);  // 打印链表元素  std::cout << "List after push_front: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 pop_back 删除链表尾部的元素  myList.pop_back();  // 打印链表元素  std::cout << "List after pop_back: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 pop_front 删除链表头部的元素  myList.pop_front();  // 打印链表元素  std::cout << "List after pop_front: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 insert 在指定位置插入元素  auto it = myList.begin();  ++it; // 指向第二个元素  myList.insert(it, 4);  // 打印链表元素  std::cout << "List after insert: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 erase 删除指定位置的元素  it = myList.begin();  ++it; // 指向要删除的元素  myList.erase(it);  // 打印链表元素  std::cout << "List after erase: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 remove 删除所有等于给定值的元素  myList.push_back(2);  myList.push_back(2);  myList.remove(2);  // 打印链表元素  std::cout << "List after remove: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 sort 对链表进行排序  myList.push_back(4);  myList.push_back(1);  myList.sort();  // 打印链表元素  std::cout << "List after sort: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 unique 删除所有连续重复的元素  myList.push_back(3);  myList.push_back(3);  myList.unique();  // 打印链表元素  std::cout << "List after unique: ";  for (int value : myList) {  std::cout << value << " ";  }  std::cout << std::endl;  // 使用 begin 和 end 获取迭代器,然后遍历链表  std::cout << "List using begin and end: ";  for (auto it = myList.begin(); it != myList.end(); ++it) {  std::cout << *it << " ";  }  std::cout << std::endl;  // 使用 size 获取链表大小  std::cout << "Size of the list: " << myList.size() << std::endl;  // 使用 empty 检查链表是否为空  std::cout << "Is the list empty? " << (myList.empty() ? "Yes" : "No") << std::endl;  return 0

【输出结果】:

List after push_back: 1 2 3
List after push_front: 0 1 2 3
List after pop_back: 0 1 2
List after pop_front: 1 2
List after insert: 1 4 2
List after erase: 1 2
List after remove: 1
List after sort: 1 4
List after unique: 1 4
List using begin and end: 1 4
Size of the list: 2
Is the list empty? No
在这个示例中,我们首先创建了一个空的 std::list,然后依次展示了如何使用不同的成员函数来操作链表。每个操作后,我们都通过遍历链表来打印其内容,以验证操作的结果。最后,我们使用 size() 函数获取链表的元素数量,使用 empty() 函数检查链表是否为空,并将结果打印出来。

请注意,std::list 是一个双向链表,其迭代器是双向的,这意味着它们可以向前和向后移动。此外,std::list 的插入和删除操作在链表中的任何位置都是常数时间复杂度,因为链表节点不需要像数组那样移动来保持连续的内存空间。这使得 std::list 在某些情况下比 std::vector 或 std::array 更为灵活和高效。

2.3 运行效率对比

std::list 在随机访问方面不如 std::vector 或 std::array,但在插入和删除操作方面效率更高。这是因为链表中的元素通过指针或迭代器链接在一起,插入和删除操作只需要更新几个指针,而不需要像数组那样移动大量元素。

下面是一个简单的示例,比较 std::list 和 std::vector 在插入元素时的效率:

cpp 代码

```cpp
#include <iostream>  
#include <chrono>  
#include <list>  
#include <vector>  int main() {  const int N = 100000;  // 测试 std::list 的插入效率  {  std::list<int> list;  auto start = std::chrono::high_resolution_clock::now();  for (int i = 0; i < N; ++i) {  list.push_back(i);  }  auto end = std::chrono::high_resolution_clock::now();  std::cout << "Time for std::list insertion: "   << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()   << " ms\n";  }  // 测试 std::vector 的插入效率  {  std::vector<int> vec;  vec.reserve(N); // 预分配空间以提高效率  auto start = std::chrono::high_resolution_clock::now();  for (int i = 0; i < N; ++i) {  vec.push_back(i);  }  auto end = std::chrono::high_resolution_clock::now();  std::cout << "Time for std::vector insertion: "   << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()   << " ms\n";  }  return 0;  
}

在上面的代码中,我们分别测试了向 std::list 和 std::vector中插入N个元素所需的时间。对于std::vector,我们使用了 reserve 函数预先分配了足够的空间,以避免在插入过程中进行多次内存分配和元素移动。

通常,对于少量的插入操作或者随机访问需求较高的场景,std::vector 会更加高效。但是,在需要频繁地在序列中间插入或删除元素的场景中,std::list 通常会提供更好的性能,因为它不需要移动其他元素来腾出空间。

在上面的示例中,由于 std::vector 预先分配了空间,其插入操作的性能与 std::list 相差不大,甚至可能更快一些(特别是在现代编译器和处理器上,std::vector 的优化可能更加出色)。然而,如果 std::vector 没有预先分配空间,或者需要进行大量的中间插入操作,那么 std::list 的性能优势就会更加明显。

需要注意的是,性能比较结果会受到多种因素的影响,包括数据规模、编译器优化、硬件性能等。因此,在实际应用中,应该根据具体的需求和场景来选择合适的数据结构。

总结起来,std::list 是一个双向链表,适合在序列中间进行频繁的插入和删除操作。它提供了丰富的成员函数来操作链表,但在随机访问方面不如 std::vector。在选择数据结构时,应该根据具体需求来权衡不同数据结构的优缺点。

2.4 链表数据结构

具有其独特的优点和缺点。以下是链表的主要优缺点分析:

优点:

动态大小:链表可以在运行时动态地增加或减少元素,而不需要预先分配固定大小的空间。这使得链表在处理不确定大小的数据集时非常灵活。高效插入和删除:在链表中,插入和删除操作可以在常数时间内完成,特别是在链表的头部或已知位置的节点处。相比之下,数组或向量在插入或删除元素时可能需要移动大量元素,导致时间复杂度较高。无需连续内存空间:链表中的元素是通过指针或引用链接在一起的,不需要像数组那样占用连续的内存空间。这使得链表在内存分配上更加灵活,可以充分利用碎片化的内存空间。

缺点:


```bash
访问元素慢:链表中访问特定位置的元素通常比数组慢,因为需要从链表头部开始遍历,直到找到目标元素。这种顺序访问的特性使得链表在需要频繁访问元素的场景中表现不佳。额外的空间开销:链表中的每个节点都需要额外的空间来存储指向下一个节点的指针或引用。这增加了链表的内存开销,尤其是在存储小型数据时,指针或引用的开销可能会占比较大。缓存不友好:由于链表中的元素在内存中可能不连续,这可能导致缓存未命中的概率增加,从而降低访问性能。相比之下,数组或向量等连续内存结构更能充分利用缓存优势。操作复杂:链表的操作相对于数组来说通常更为复杂,需要处理指针或引用的操作,这增加了编程的难度和出错的可能性。

综上所述,链表在需要动态调整大小、频繁插入和删除元素的场景中表现出色,但在需要快速访问元素的场景中则可能不是最佳选择。在实际应用中,应根据具体需求和数据特点来选择合适的数据结构。

2.5 链表和顺序表相比优缺点

链表和顺序表相比,各自具有不同的优缺点。下面我将通过代码示例来演示这些优缺点。

链表
优点:
动态性:链表可以在运行时动态地添加或删除元素。
插入和删除效率高:在链表的头部或尾部插入或删除元素时效率很高。缺点:
访问元素慢:需要从头节点开始遍历才能找到指定位置的元素。
额外的空间开销:每个节点除了存储数据外,还需要存储指向下一个节点的指针。

代码示例:

#include <iostream>  struct ListNode {  int val;  ListNode *next;  ListNode(int x) : val(x), next(nullptr) {}  
};  int main() {  // 创建链表:1 -> 2 -> 3  ListNode *head = new ListNode(1);  head->next = new ListNode(2);  head->next->next = new ListNode(3);  // 访问链表的第三个元素(值为3)  ListNode *current = head;  for (int i = 0; i < 2; ++i) { // 需要遍历两次才能到达第三个元素  current = current->next;  }  std::cout << "Third element: " << current->val << std::endl;  // 在链表头部插入元素0  ListNode *newHead = new ListNode(0);  newHead->next = head;  head = newHead;  // 删除链表的第二个元素(值为2)  ListNode *prev = head;  current = head->next;  prev->next = current->next;  delete current;  // 遍历并打印链表元素  current = head;  while (current != nullptr) {  std::cout << current->val << " ";  current = current->next;  }  std::cout << std::endl;  // 释放链表内存(略)  return 0;  
}

顺序表(数组)

优点:
访问元素快:可以通过下标直接访问任意位置的元素。
空间利用率高:数据在内存中连续存储,没有额外的指针开销。缺点:
插入和删除操作效率低:在数组中间插入或删除元素时,需要移动大量元素。
大小固定:数组在创建时需要预先分配固定大小的内存空间。

代码示例:

#include <iostream>  
#include <vector>  int main() {  // 创建顺序表(数组)并初始化:1, 2, 3  std::vector<int> arr = {1, 2, 3};  // 访问顺序表的第三个元素(值为3)  std::cout << "Third element: " << arr[2] << std::endl;  // 在顺序表头部插入元素0(需要移动所有元素)  arr.insert(arr.begin(), 0);  // 删除顺序表的第二个元素(值为2)  arr.erase(arr.begin() + 1);  // 遍历并打印顺序表元素  for (int i = 0; i < arr.size(); ++i) {  std::cout << arr[i] << " ";  }  std::cout << std::endl;  return 0;  
}

通过上面的代码示例,我们可以看到链表和顺序表在访问元素、插入和删除操作方面的不同。链表在动态性和插入/删除操作方面表现出色,而顺序表则在访问元素和空间利用率方面更胜一筹。在实际应用中,我们需要根据具体需求和数据特点来选择合适的数据结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/816974.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI的Whisper

由于篇幅限制&#xff0c;我将以概要形式介绍OpenAI的Whisper模型&#xff0c;并说明其是端到端模型&#xff0c;而非序列到序列模型。如需更详细的介绍&#xff0c;请查阅相关论文和官方文档。 OpenAI的Whisper模型介绍 Whisper是OpenAI发布的一个通用的语音识别模型&#x…

解决动态规划问题

文章目录 动态规划的定义动态规划的核心思想青蛙跳阶问题解法一&#xff1a;暴力递归解法二&#xff1a;带备忘录的递归解法&#xff08;自顶向下&#xff09;解法三&#xff1a;动态规划&#xff08;自底向上&#xff09; 动态规划的解题套路什么样的问题考虑使用动态规划&…

TransactionEventListener使用

使用场景 需要在事务提交成功后执行其他操作。 测试 事务A里执行业务逻辑&#xff0c;并发布事件。Listener里执行事务B。 TransactionEventListener执行phase为AFTER_COMMIT 这个操作的效果是在事务A提交成功后执行事务B。 1.事务B抛出异常后&#xff0c;是否会影响事务…

Java GUI制作双人对打游戏(上)

文章目录 前言什么是Java GUI一、打开IDEA 新建一个Maven项目(后续可以打包、引入相关依赖也很容易)二、引入依赖三.绘制UI界面四.绘制JPanel面板总结 前言 什么是Java GUI Java UI&#xff0c;即Java用户界面&#xff0c;是指使用Java编程语言创建的图形用户界面&#xff08…

springBoot+vue编程中使用mybatis-plus遇到的问题

mybatis-plus中遇到的问题Code Companion Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)…

7天八股速记之C++后端——Day 5

坚持7天&#xff0c;短期内快速完成C后端面试突击。每天10题&#xff0c;弥补后端八股知识缺漏&#xff0c;熟练掌握后端的高频考点&#xff0c;后端面试更有把握。 1. InnoDB 和 MyISAM 的比较&#xff1f; 事务支持&#xff1a; InnoDB 支持事务&#xff08;ACID特性&#x…

02 SQL基础 -- 初识SQL

一、初识 SQL 1.1 概念介绍 数据库中存储的表结构类似于 excel 中的行和列,在数据库中,行称为记录,它相当于一条结论,列称为字段,它代表了表中存储的数据项目 行和列交汇的地方称为单元格,一个单元格只能输入一条记录 SQL是为操作数据库而开发的语言。国际标准化组织(…

Rust语言入门第二篇-Cargo教程

文章目录 Rust语言入门第二篇-Cargo教程一&#xff0c;Cargo 是什么二&#xff0c;Cargo教程Cargo.toml文件src/main.rs 文件构建并运行Cargo项目 Rust语言入门第二篇-Cargo教程 本节提供对cargo命令行工具的快速了解。我们演示了它为我们生成新包的能力&#xff0c;它在包内编…

windows如何卸载干净 IDEA

Windows 系统要想彻底卸载 IDEA, 步骤如下&#xff1a; 1、卸载 IDEA 程序 点击屏幕左下角 Windows 图标 -> 设置&#xff1a; 在应用中找到 IDEA, 单击它会出现卸载按钮&#xff0c;点击开始卸载&#xff1a; 勾选第一栏 Delete IntelliJ IDEA 2022.2 caches and local hi…

Go语言开发工具Vscode配置

Go语言开发工具Vscode配置方法分享&#xff1a; 1.下载安装vscode https://code.visualstudio.com/ 2.汉化vscode 3.vscode中安装Go语言插件 源自&#xff1a;大地老师Golang语言beego入门实战视频教程下载地址

c# 实现Quartz任务调度

使用 Quartz.NET&#xff0c;你可以很容易地安排任务在应用程序启动时运行&#xff0c;或者每天、每周、每月的特定时间运行&#xff0c;甚至可以基于更复杂的调度规则。 官网&#xff1a;http://www.quartz-scheduler.net/ 实现任务类 创建一个实现了 IJob 接口的类(MailJo…

乐观锁与悲观锁如何实现?

本文主要是对【MySQL中Update语句是悲观锁&#xff1f;还是乐观锁&#xff1f;】中提到乐观锁与悲观锁的补充。 在 MySQL中&#xff0c;悲观锁是需要依靠数据库提供的锁机制实现的&#xff0c;在 InnoDB 引擎中&#xff0c;使用悲观锁&#xff0c;就需要先关闭 MySQL 数据库的自…

【noVNC】使用noVNC实现浏览器远程VNC(基于web的远程桌面)

一、操作的环境 windows 10系统乌班图 Ubuntu 22 二、noVNC 部署方式 原理&#xff1a;开启 Websockify 代理来做 WebSocket 和 TCP Socket 之间的转换 2.1 noVNC和VNC服务端同一台机器 使用方式&#xff0c;查看另一篇博文 &#xff1a;【noVNC】使用noVNC实现浏览器网页访…

双向链表的实现(详解)

目录 前言初始化双向链表的结构为双向链表的节点开辟空间头插尾插打印链表尾删头删查找指定位置之后的插入删除pos节点销毁双向链表 前言 链表的分类&#xff1a; 带头 不带头 单向 双向 循环 不循环 一共有 (2 * 2 * 2) 种链表 带头指的是&#xff1a;带有哨兵位节点 哨兵位&a…

基于springboot实现人事管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现人事管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为学校以及一些培训机构&#xff0c;都在用信息化战术来部署线上学习以及线上考试&#xff0c;可以与线下的考试有机的结合在一起&#xff0c;实现基于vue的人事系统在技术…

numpy学习笔记(3),数组连接

6. 连接数组 6.1. 连接数组&#xff0c; 6.2. 分割数组&#xff0c; 6.3. 算术运算&#xff0c; 6.4. 广播&#xff08;重点&#xff09; 6.1 连接数组 concatenatehstackvstack 6.1.1 使用concatenate函数 沿指定轴连接多个数组&#xff0c;语法格式如下&#xff1a; num…

Linux:调试器 - gdb

Linux&#xff1a;调试器 - gdb gbd基本概念gbd调试浏览断点运行变量 gbd基本概念 GDB (GNU Debugger) 是一个强大的命令行调试工具,用于调试各种编程语言(如C、C、Java、Python等)编写的程序。使用 gdb可以帮助开发人员更快地定位和修复程序中的缺陷,提高代码质量和开发效率。…

软考 - 系统架构设计师 - ETL工具

概念 ETL 工具是一种用于将数据从源系统中提取、进行转换和加载到目标系统中的软件工具&#xff08;数据迁移工具&#xff09;。它们在数据仓库和商业智能项目中起到至关重要的作用。 ETL 的主要步骤 ETL 过程包括数据抽取&#xff08;Extract&#xff09;、数据转换&#xff…

二叉树经典OJ题(2)

一、根据二叉树创建字符串 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public://前序遍历&#xff1a;根 左 右//左子树为空&#xff0c;右子树不为空的时候&#xff0c;不能省略左//左不为空&#xff0c;右子树为空的时候&#xff0c;可以省略右//都为空&am…

Java基于微信小程序的校园外卖平台设计与实现,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…