突破编程_C++_STL教程( list 的实战应用)

1 std::list 的排序

1.1 基础类型以及 std::string 类型的排序

std::list的排序可以通过调用其成员函数sort()来实现。sort()函数使用默认的比较操作符(<)对std::list中的元素进行排序。这意味着,如果元素类型定义了<操作符,std::list将使用它来比较元素。

例如,如果有一个std::list<int>,则可以这样排序它:

#include <list>  
#include <algorithm>  int main() 
{std::list<int> myList = { 4, 2, 5, 1, 3 };myList.sort();// myList现在包含{1, 2, 3, 4, 5}  return 0;
}

如果 list 里面的元素类型没有定义<操作符,或者需要使用自定义的比较函数,则可以通过传递一个比较函数或对象给sort()函数来实现。这个比较函数或对象应该接受两个参数(即需要比较的两个元素)并返回一个布尔值,指示第一个参数是否应该在排序后出现在第二个参数之前。

例如有一个std::list<std::string>,并且需要按照字符串的长度来排序它,可以这样做:

#include <list>  
#include <algorithm>  
#include <string>  bool compareByStringLength(const std::string& a, const std::string& b) {return a.size() < b.size();
}int main() 
{std::list<std::string> myList = { "apple", "banana", "cherry", "date" };myList.sort(compareByStringLength);// myList现在按照字符串长度排序,可能包含{"date", "apple", "cherry", "banana"}  return 0;
}

上面代码中定义了一个名为 compareByStringLength 的比较函数,它比较两个字符串的长度。然后将这个函数作为参数传递给 sort() 函数,以按照字符串长度对 std::list 进行排序。

1.2 自定义类型的排序

如果需要对 std::list 中的自定义类型进行排序,则需要提供一个自定义的比较函数或比较对象,该函数或对象定义了如何比较两个自定义类型的对象。这个比较函数或对象应该接受两个参数并返回一个布尔值,用于确定第一个参数是否应该在排序后出现在第二个参数之前。

下面是一个例子,展示了如何对 std::list 中的自定义类型进行排序:

#include <list>  
#include <string>  
#include <iostream>  
#include <algorithm>  // 自定义类型  
struct Person {std::string name;int age;// 构造函数  Person(const std::string& name, int age) : name(name), age(age) {}// 为了方便输出,重载<<操作符  friend std::ostream& operator<<(std::ostream& os, const Person& person) {os << "Name: " << person.name << ", Age: " << person.age;return os;}
};// 自定义比较函数,用于排序Person对象  
bool comparePersonByAge(const Person& a, const Person& b) {return a.age < b.age; // 按年龄升序排序  
}int main() 
{// 创建一个包含Person对象的std::list  std::list<Person> people = {{"Alice", 25},{"Bob", 20},{"Charlie", 30},{"David", 25}};// 使用自定义比较函数对people进行排序  people.sort(comparePersonByAge);// 输出排序后的结果  for (const auto& person : people) {std::cout << person << std::endl;}return 0;
}

上面代码的输出为:

Name: Bob, Age: 20
Name: Alice, Age: 25
Name: David, Age: 25
Name: Charlie, Age: 30

上面代码中定义了一个 Person 结构体,它包含 name 和 age 两个成员。然后创建了一个 comparePersonByAge 函数,它接受两个 Person 对象并比较它们的年龄。这个函数将用于 std::list 的 sort 成员函数来按照年龄对 Person 对象进行排序。

最后,在main函数中,创建了一个 std::list<Person>并初始化了几个 Person 对象。然后调用 sort 成员函数并传递 comparePersonByAge 函数作为比较对象,从而对 people 列表进行排序。

注意:如果需要按照降序排序,可以在comparePersonByAge函数中将比较操作从<改为>。此外,如果自定义类型定义了<操作符,也可以直接使用默认的排序而不需要提供自定义比较函数。

2 std::list 的主要应用场景

以下是一些std::list的主要应用场景:

(1)频繁插入和删除操作: std::list 允许在序列中的任意位置进行常数时间的插入和删除操作。这使得它非常适合需要频繁添加或移除元素的场景,如日志记录、任务队列、事件处理等。

(2)双向迭代: std::list 支持双向迭代,这意味着可以轻松地遍历列表,向前或向后移动。这种特性在处理需要前后关联的数据时非常有用,如文本编辑器中的撤销与重做操作。

(3)保持元素顺序: std::list 通过链表结构保持元素的插入顺序。这对于需要维护元素原始顺序的场景(如数据库查询结果、事件历史记录等)非常有用。

(4)动态数据结构: 当数据结构的大小经常变化时,std::list 是一个很好的选择。与基于数组的容器(如 std::vector)相比,std::list 在插入和删除元素时不需要移动大量数据,因此性能更高。

(5)内存管理: 在某些情况下,可能希望更好地控制内存使用。std::list 允许管理每个元素的内存分配,这在处理大量数据或需要精确控制内存使用的场景中非常有用。

(6)与线程安全容器结合使用: std::list 可以与其他线程安全容器(如 std::list<std::unique_ptr<T>>)结合使用,以在多线程环境中安全地管理资源。

2.1 std::list 应用于任务队列

std::list 在任务队列的应用场景中特别有用,特别是当需要频繁地在队列的任意位置插入和删除任务时。由于 std::list 的双向链表结构,它支持在常数时间内完成这些操作,这使得它成为处理动态任务队列的理想选择。

下面是一个简单的示例,展示了如何使用 std::list 来实现一个任务队列:

#include <iostream>  
#include <list>  
#include <thread>  
#include <chrono>  
#include <mutex>  
#include <condition_variable>  // 任务类型定义  
struct Task {Task() {}Task(int id, std::function<void()> work) : id(id), work(work) {}int id;std::function<void()> work;
};// 任务队列类  
class TaskQueue 
{
public:// 向队列中添加任务  void pushTask(Task task) {std::lock_guard<std::mutex> lock(mtx);tasks.push_back(task);condVar.notify_one(); // 通知等待的线程有新任务到来  }// 从队列中取出任务并执行  bool popTask(Task& task) {std::unique_lock<std::mutex> lock(mtx);condVar.wait(lock, [this] { return !tasks.empty(); }); // 等待直到有任务到来  task = tasks.front();tasks.pop_front(); // 移除并返回队列前端的任务  return true;}// 检查队列是否为空  bool isEmpty() {std::lock_guard<std::mutex> lock(mtx);return tasks.empty();}
private:std::list<Task> tasks;std::mutex mtx;std::condition_variable condVar;};// 模拟任务执行的函数  
void simulateWork(int taskId) {std::cout << "Executing task " << taskId << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时任务  
}int main() 
{TaskQueue taskQueue;// 启动生产者线程,向队列中添加任务  std::thread producer([&taskQueue]() {for (int i = 0; i < 10; ++i) {taskQueue.pushTask(Task(i, std::bind(simulateWork, i)));std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟任务生成间隔  }});// 启动消费者线程,从队列中取出并执行任务  std::thread consumer([&taskQueue]() {Task task;while (taskQueue.popTask(task)) {task.work(); // 执行任务  }});// 等待生产者和消费者线程完成  producer.join();consumer.join();return 0;
}

上面代码的输出为:

Executing task 0
Executing task 1
Executing task 2
Executing task 3
Executing task 4
Executing task 5
Executing task 6
Executing task 7
Executing task 8
Executing task 9

上面代码中定义了一个 Task 结构体来表示任务,它包含一个标识符和一个工作函数。TaskQueue 类提供了 pushTask 方法来向队列中添加任务,以及 popTask 方法来从队列中取出并执行任务。TaskQueue 还使用了一个互斥锁和一个条件变量来确保线程安全,并允许消费者在任务可用时被唤醒。

simulateWork 函数模拟了任务的执行过程,它只是打印任务 ID 并休眠一秒钟来模拟耗时任务。在 main 函数中,创建了一个生产者线程来生成任务,并将其添加到任务队列中,同时还创建了一个消费者线程来从队列中取出任务并执行。

2.2 std::list 应用于文本编辑器中的撤销与重做操作

在文本编辑器的撤销与重做操作中,使用 std::list 的双向迭代特性可以使得在向前和向后遍历编辑历史时都非常高效。下面是一个示例,该示例演示了如何使用 std::list 来管理编辑历史,并支持撤销和重做操作,同时体现了双向迭代在处理这种需要前后关联的数据时的优势。

#include <iostream>  
#include <list>  
#include <string>  // 文本编辑器类  
class TextEditor 
{
public:// 构造函数  TextEditor() : currentPos(history.end()) {}// 输入文本  void inputText(const std::string& text) {// 添加新文本到历史记录  history.push_back(text);// 更新当前位置迭代器  currentPos = std::prev(history.end());}// 撤销操作  void undo() {if (currentPos != history.begin()) {// 如果不是第一次编辑,则向前移动迭代器  --currentPos;}}// 重做操作  void redo() {if (std::next(currentPos) != history.end()) {// 如果还有后续的历史记录,则向后移动迭代器  ++currentPos;}}// 获取当前文本  std::string getCurrentText() const {if (currentPos != history.end()) {return *currentPos;}return "";}// 显示编辑历史  void showHistory() const {for (const auto& text : history) {std::cout << text << std::endl;}}private:std::list<std::string> history; // 存储编辑历史的列表  std::list<std::string>::iterator currentPos; // 当前编辑位置的迭代器  
};int main() {TextEditor editor;// 用户输入一些文本  editor.inputText("Initial text.");editor.inputText("Edited text.");editor.inputText("Edited again.");// 显示编辑历史  editor.showHistory();// 执行撤销操作  editor.undo();std::cout << "After undo: " << editor.getCurrentText() << std::endl;// 执行重做操作  editor.redo();std::cout << "After redo: " << editor.getCurrentText() << std::endl;// 再次显示编辑历史来验证状态  editor.showHistory();return 0;
}

上面代码的输出为:

Initial text.
Edited text.
Edited again.
After undo: Edited text.
After redo: Edited again.
Initial text.
Edited text.
Edited again.

在上面代码中,TextEditor 类使用 std::list 来维护编辑历史。每次调用 inputText 方法时,它都会清除当前位置之后的所有历史记录,并将新文本添加到历史列表的末尾。撤销操作通过向前移动迭代器并删除当前元素来实现,而重做操作则通过向后移动迭代器来实现。由于 std::list 支持双向迭代,因此这些操作都是常数时间复杂度的,非常高效。

3 std::list 的扩展与自定义

在大多数情况下,不需要对 std::list 做扩展与自定义,因为它已经提供了非常完整的功能集。然而,如果需要更高级的功能或定制行为,则可以考虑以下几种方法:

(1)继承自 std::list:
可以通过继承 std::list 并添加自定义成员函数来扩展其功能。然而,这通常不是推荐的做法,因为继承标准库组件可能导致未定义的行为或意外的副作用。标准库组件通常设计为不可继承的,并且它们的内部实现可能会在不同版本的编译器或标准库中发生变化。

(2)使用适配器模式:
适配器模式允许将一个类的接口转换为另一个类的接口,而不需要修改原始类。可以创建一个适配器类,它封装了一个 std::list 实例,并提供了自定义的接口。这样,可以在不修改 std::list 本身的情况下添加新功能。

(3)自定义迭代器:
std::list 允许使用自定义迭代器。开发人员可以创建自己的迭代器类,该类提供对 std::list 元素的访问,并在迭代过程中添加自定义逻辑。然后,可以将这些自定义迭代器传递给 std::list 的成员函数,以在遍历元素时应用自定义行为。

(4)使用代理类:
代理类是一个设计模式,它允许一个类代表另一个类的功能,并在调用功能时添加额外的逻辑。你可以创建一个代理类,它封装了一个 std::list 实例,并提供与 std::list 相同的接口。在代理类的实现中,可以在调用 std::list 的方法之前或之后添加自定义逻辑:

(5)自定义分配器:
std::list 使用分配器来管理内存。可以通过提供一个自定义的分配器来定制 std::list 的内存分配行为。自定义分配器可以允许你控制内存的分配策略,例如使用内存池、共享内存或其他高级内存管理技术。

4 实现一个简单的 std::list 容器

如下是一个一个简化的 std::list 容器的实现,仅包含基本的构造函数、析构函数、插入和遍历功能:

#include <iostream>  template <typename T>
class MyList 
{
public:struct Node {T data;Node* next;Node* prev;Node(const T& value) : data(value), next(nullptr), prev(nullptr) {}};MyList() : head(nullptr), tail(nullptr) {}~MyList() {clear();}void push_back(const T& value) {Node* newNode = new Node(value);if (head == nullptr) {head = newNode;tail = newNode;}else {tail->next = newNode;newNode->prev = tail;tail = newNode;}}void push_front(const T& value) {Node* newNode = new Node(value);if (head == nullptr) {head = newNode;tail = newNode;}else {newNode->next = head;head->prev = newNode;head = newNode;}}void clear() {Node* current = head;while (current != nullptr) {Node* next = current->next;delete current;current = next;}head = nullptr;tail = nullptr;}void display() const {Node* current = head;while (current != nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}private:Node* head;Node* tail;
};int main() 
{MyList<int> myList;myList.push_back(10);myList.push_back(20);myList.push_front(5);myList.display();myList.clear();return 0;
}

上面代码的输出为:

5 10 20

这个简化的 MyList 类包含了一个内部的Node结构,用于存储数据以及指向下一个和上一个节点的指针。MyList 类提供了 push_back 和 push_front 方法用于在列表的末尾和开头插入元素,clear 方法用于删除所有元素,以及 display 方法用于遍历并打印列表中的所有元素。

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

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

相关文章

身份证识别系统(安卓)

设计内容与要求&#xff1a; 通过手机摄像头捕获身份证信息&#xff0c;将身份证上的姓名、性别、出生年月、身份证号码保存在数据库中。1&#xff09;所开发Apps软件至少需由3-5个以上功能性界面组成。要求&#xff1a;界面美观整洁、方便应用&#xff1b;可以使用Android原生…

ChatGPT聊图像超分

笔者就YOLO系列方法询问了ChatGPT的看法&#xff0c;可参考&#xff1a; ChatGPT是如何看待YOLO系列算法的贡献呢&#xff1f; 续接前文&#xff0c;今天继续拿图像超分领域的经典方法来询问ChatGPT的看法&#xff0c;这里主要挑选了以下几个方案SRCNN、ESPSRN、EDSR、RCAN、…

JS 对象数组排序方法测试

输出 一.Array.prototype.sort() 1.默认排序 sort() sort() 方法就地对数组的元素进行排序&#xff0c;并返回对相同数组的引用。默认排序是将元素转换为字符串&#xff0c;然后按照它们的 UTF-16 码元值升序排序。 由于它取决于具体实现&#xff0c;因此无法保证排序的时…

数据可视化基础与应用-02-基于powerbi实现医院数据集的指标体系的仪表盘制作

总结 本系列是数据可视化基础与应用的第02篇&#xff0c;主要介绍基于powerbi实现医院数据集的指标体系的仪表盘制作。 数据集描述 医生数据集doctor 医生编号是唯一的&#xff0c;名称会存在重复 医疗项目数据projects 病例编号是唯一的&#xff0c;注意这个日期编号不是真…

面试时如何回答接口测试怎么进行

一、什么是接口测试 接口测试顾名思义就是对测试系统组件间接口的一种测试&#xff0c;接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。 …

【C++ 07】string 类的常用接口介绍

文章目录 &#x1f308; Ⅰ string 类对象的常见构造函数&#x1f308; Ⅱ string 类对象的容量相关操作&#x1f308; Ⅲ string 类对象的访问及遍历1. 下标访问及遍历2. 正向迭代器访问3. 反向迭代器访问 &#x1f308; Ⅳ string 类对象的修改操作1. 插入字符或字符串2. 字符…

数据分析业务面试题

目录 Q1:请简述数据分析的工作流程? Q2:你经常用到的数据分析方法有哪些,举例说明? Q3:公司最近一周的销售额下降了,你如何分析下降原因? Q4:店铺销售额降低如何分析? Q5:若用户留存率下降如何分析? Q6:店铺商品销售情况分布后 Q7:如何描述店铺经营状况?…

Vue前端的工作需求

加油&#xff0c;新时代打工人&#xff01; 需求&#xff1a; 实现带树形结构的表格&#xff0c;父数据显示新增下级&#xff0c;和父子都显示编辑。 技术&#xff1a; Vue3 Element Plus <template><div><el-table:data"tableData"style"width…

了解游戏中的数据同步

目录 数据同步 通过比较来看状态同步和帧同步 状态同步 帧同步 帧同步实现需要的条件 两者相比较 数据同步 在联机游戏中&#xff0c;我的操作和数据要同步给同一局游戏中其他所有玩家&#xff0c;其他玩家的操作和数据也会同步给我。这叫做数据同步&#xff0c;目前数据…

国产数据库概述

这是ren_dong的第33篇原创 1、什么是数据库&#xff1f; 1.1、基本概念 定义&#xff1a;数据库是 按照一定的数据结构组织、存储和管理数据的仓库。可视为电子化的文件柜&#xff0c;用户可以对文件中的数据进行新增、查询、更新、删除等操作。 作用&#xff1a;业务数据 存储…

kettle下载及安装

JDK下载 安装kettle之前需要安装JDK JDK下载链接&#xff1a;JDK下载 配置环境变量&#xff1a; 新建系统变量&#xff1a;变量值为JDK安装路径 Path新增&#xff1a; kettle下载 链接地址&#xff1a;PDI&#xff08;kettle&#xff09; 点击下载 同意 Click here to a…

【XIAO ESP32S3 sense 通过 ESPHome 与 Home Assistant 连接】

XIAO ESP32S3 sense 通过 ESPHome 与 Home Assistant 连接 1. 什么是 ESPHome 和 Home Assistant&#xff1f;2. 软件准备3. 开始4. 将 Grove 模块与 ESPHome 和 Home Assistant 连接5. Grove 连接和数据传输6. Grove -智能空气质量传感器 &#xff08;SGP41&#xff09;7. OV2…

Filter(过滤器)

文章目录 过滤器的编写&#xff1a;过滤器 APIFilterFilterConfigFilterChain 生命周期过滤器核心方法的细节多个过滤器执行顺序<br /> 过滤器——Filter&#xff0c;它是JavaWeb三大组件之一。另外两个是Servlet和Listener。 它是在2000年发布的Servlet2.3规范中加入的一…

Go语言基础基础

简介 Go语言&#xff08;也称为Golang&#xff09;是一种静态类型、编译型语言&#xff0c;由Google的Robert Griesemer、Rob Pike和Ken Thompson于2007年设计&#xff0c;首次公开发布于2009年。Go的设计初衷是解决当时谷歌内部面临的软件开发问题&#xff0c;特别是在处理大…

百度文库旋转验证码识别

最近研究了一下图像识别&#xff0c;一直找到很好的应用场景&#xff0c;今天我就发现可以用百度的旋转验证码来做一个实验。没想到效果还挺好&#xff0c;下面就是实际的识别效果。 1、效果演示 2、如何识别 2.1准备数据集 首先需要使用爬虫&#xff0c;对验证码图片进行采…

区块链媒体发布推广10个热门案例解析-华媒舍

区块链技术的发展已经引起了媒体的广泛关注&#xff0c;越来越多的区块链媒体纷纷发布推广相关的热门案例。本文将介绍10个成功的区块链媒体推广案例&#xff0c;并分享它们的成功秘诀&#xff0c;帮助读者更好地了解区块链媒体推广的方法与技巧。 随着区块链技术的成熟和应用场…

第二证券:富时罗素扩容 A股引入国际增量资金

日前&#xff0c;英国富时罗素指数公司&#xff08;FTSE Russell&#xff0c;简称“富时罗素”&#xff09;公布的全球股票指数&#xff08;FTSE Global Equity Index Series&#xff09;半年度指数检查陈述显现&#xff0c;将新调入A股76只、调出1只。此前&#xff0c;富时罗素…

Leetcode 3049. Earliest Second to Mark Indices II

Leetcode 3049. Earliest Second to Mark Indices II 1. 解题思路2. 代码实现3. 算法优化 题目链接&#xff1a;3049. Earliest Second to Mark Indices II 1. 解题思路 这道题我看貌似难度报表&#xff0c;比赛的时候貌似只有36个人搞定了这道题目&#xff0c;然后最快的人…

【LeetCode】升级打怪之路 Day 12:单调队列

今日题目&#xff1a; 239. 滑动窗口最大值 | LeetCode 今天学习了单调队列这种特殊的数据结构&#xff0c;思路很新颖&#xff0c;值得学习。 Problem&#xff1a;单调队列 【必会】 与单调栈类似&#xff0c;单调队列也是一种特殊的数据结构&#xff0c;它相比与普通的 que…

Get Your Back Covered! Coverage, CodeCov和Tox

1. Coverage - 衡量测试的覆盖率 我们已经掌握了如何进行单元测试。接下来,一个很自然的问题浮现出来,我们如何知道单元测试的质量呢?这就提出了测试覆盖率的概念。覆盖率测量通常用于衡量测试的有效性。它可以显示您的代码的哪些部分已被测试过,哪些没有。 coverage.py …