突破編程_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.pswp.cn/news/718240.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/718240.shtml
英文地址,請注明出處:http://en.pswp.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 …