1 std::list 概述
std::list 是 C++ 標準庫中的一個雙向鏈表容器。它支持在容器的任何位置進行常數時間的插入和刪除操作,但不支持快速隨機訪問。與 std::vector 或 std::deque 這樣的連續存儲容器相比,std::list 在插入和刪除元素時不需要移動其他元素,因此這些操作通常更快。然而,由于鏈表的結構,訪問單個元素(特別是位于容器中間的元素)通常比連續存儲的容器慢。
std::list 的主要特點包括:
(1)雙向鏈表結構: 每個元素在 std::list 中都有一個指針指向前一個元素,也有一個指針指向后一個元素。這種結構允許 std::list 在兩個方向上進行迭代,即可以向前也可以向后遍歷。
(2)插入和刪除操作的高效性: std::list 在任何位置進行插入和刪除操作的時間復雜度都是 O(1),即常量時間。這是因為插入和刪除操作不需要移動其他元素,只需要更新相關節點的指針即可。
(3)不支持隨機訪問: 由于 std::list 是基于鏈表實現的,因此它不支持像數組或向量那樣的隨機訪問。訪問特定位置的元素需要從頭節點或尾節點開始遍歷鏈表,直到到達所需的位置。
(4)空間效率較低: 由于每個節點都需要存儲指向前后節點的指針,因此與連續存儲的容器相比,std::list 在空間效率方面可能較低。每個節點除了存儲實際的數據外,還需要額外的空間來存儲指針。
(5)類型安全: 由于 std::list 是一個模板類,因此它是類型安全的。可以為 std::list 指定元素的類型,并確保只能存儲該類型的元素。
1.1 std::list 的內部實現
std::list 的內部實現細節通常由 C++ 標準庫的具體實現決定,根據 C++ 標準,它必須滿足 std::list 所提供的功能和性能要求。在大多數實現中,std::list 的內部實現可以大致描述為以下結構:
節點結構
每個 std::list 的元素都被封裝在一個節點中。每個節點至少包含兩部分:
- 元素值:存儲實際的數據值。
- 指針:至少包含兩個指針,一個指向前一個節點(prev),另一個指向后一個節點(next)。
節點結構可以定義為如下:
struct list_node
{ T value; // 存儲的數據值 list_node* prev; // 指向前一個節點的指針 list_node* next; // 指向后一個節點的指針
};
容器結構
std::list 容器本身通常包含指向鏈表第一個元素和最后一個元素的指針,以及可能的其他元數據,如大小(size)和容量(capacity)信息:
class list
{
public: // 其他成員函數和類型定義 private: list_node* head; // 指向鏈表第一個節點的指針 list_node* tail; // 指向鏈表最后一個節點的指針 size_t size; // 鏈表的大小 // 其他內部狀態
};
迭代器
std::list 也提供了迭代器,用于遍歷鏈表。迭代器內部通常包含一個指向當前節點的指針:
struct list_iterator
{ list_node* current; // 指向當前節點的指針 // 其他用于支持迭代器的狀態
};
成員函數
std::list 提供了一系列成員函數,如 push_front、push_back、pop_front、pop_back、insert、erase 等,這些函數通常通過操作節點指針來實現其功能。
注意:由于 std::list 的內部實現細節通常由標準庫的實現者決定,上述描述只是一種常見的實現方式。不同的編譯器和庫可能會有細微的差異,但它們都必須滿足 C++ 標準對 std::list 的規定。但是在實際使用中,通常不需要關心這些內部細節,除非需要在進行底層優化或調試時需要深入了解它們。
1.2 std::list 的性能特點
std::list 的性能特點主要基于其雙向鏈表的數據結構。以下是關于 std::list 性能的一些關鍵點:
(1)插入和刪除操作的高效性:
在鏈表中的任何位置進行插入和刪除操作的時間復雜度都是 O(1),即常數時間。這是因為插入和刪除操作只需要更新相鄰節點的指針,而不需要移動其他元素。
這一特點使得 std::list 在需要頻繁插入和刪除元素的場景中表現出色。
(2)不支持快速隨機訪問:
訪問鏈表中特定位置的元素需要從頭節點或尾節點開始遍歷鏈表,直到到達所需的位置。因此,隨機訪問 std::list 中元素的時間復雜度是 O(n),其中 n 是元素的位置。
對于需要頻繁進行隨機訪問的場景,std::list 可能不是最佳選擇。
(3)空間效率較低:
由于 std::list 是基于鏈表實現的,每個節點除了存儲實際的數據外,還需要額外的空間來存儲指向前后節點的指針。因此,與連續存儲的容器(如 std::vector)相比,std::list 在空間效率方面可能較低。
(4)迭代器的穩定性:
在 std::list 內或在數個 std::list 間添加、移除和移動元素不會非法化迭代器或引用。只有當對應元素被刪除時,迭代器才會變得非法。
(5)內存使用:
std::list 使用非連續的內存空間進行存儲。這意味著它不會像 std::vector 那樣在添加元素時導致內存重新分配和復制。然而,由于每個節點都需要額外的空間來存儲指針,所以總體內存使用可能會比連續存儲的容器更高。
綜上所述,std::list 的性能特點包括高效的插入和刪除操作、不支持快速隨機訪問、較低的空間效率以及穩定的迭代器。這些特點使得 std::list 在需要頻繁插入和刪除元素的場景中表現出色,但在需要快速隨機訪問或高效空間利用的情況下可能不是最佳選擇。
2 std::list 的基本使用
2.1 std::list 的聲明與初始化
聲明
首先,需要包含<list>頭文件以使用 std::list:
#include <string> // 聲明一個整數類型的鏈表
std::list<int> vals;// 聲明一個字符串類型的鏈表
std::list<std::string> strs;// 聲明一個自定義類型的鏈表
struct MyStruct
{int id;std::string name;
};
std::list<MyStruct> myStructs;
初始化
可以使用多種方法來初始化std::list。
(1)默認初始化:
創建一個空的 std::list 容器。
std::list<int> vals; // 空的 list
(2)使用初始化列表:
在聲明時直接使用初始化列表來添加元素。
std::list<int> vals = {1, 2, 3, 4, 5};
(3)使用迭代器或范圍構造:
使用另一個容器的迭代器或范圍來初始化 std::list。
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> vals(vec.begin(), vec.end());
(4)指定數量并填充默認值:
創建一個具有指定數量的元素,并使用默認構造函數生成這些元素。
std::list<int> vals(10); // 創建一個包含 10 個默認構造的 int 的 list
(5)指定數量和值填充:
創建一個具有指定數量的元素,并用給定的值填充它們。
std::list<int> vals(5, 1); // 創建一個包含 5 個值為 1 的 int 的 list
(6)拷貝構造:
使用另一個 std::list 來初始化一個新的 std::list。
std::list<int> firstVals = {1, 2, 3};
std::list<int> secondVals(firstVals); // 拷貝構造
(7)移動構造:
使用另一個 std::list 的移動語義來初始化一個新的 std::list。
std::list<int> firstVals = {1, 2, 3};
std::list<int> secondVals(std::move(firstVals)); // 移動構造
(8)運算符賦值:
使用 = 運算符將一個 std::list 的內容賦給另一個 std::list。
std::list<int> firstVals = {1, 2, 3};
std::list<int> secondVals = firstVals; // 運算符賦值
2.2 std::list 的大小與容量
std::list 中與大小與容量相關的方法有如下幾種:
- empty(): 如果 list 為空則返回 true。
- size(): 返回 list 中的元素數量。
- max_size(): 返回 list 可以容納的最大元素數量。
- resize(size_type n): 改變 list 的大小,增加或減少元素。
- resize(size_type n, const value_type& val): 改變 list 的大小,并用 val 填充新元素。
如下為樣例代碼:
#include <iostream>
#include <list> int main()
{// 創建一個空的 std::list std::list<int> myList;// 檢查 list 是否為空,并輸出結果 if (myList.empty()) {std::cout << "List is empty." << std::endl;}// 向 list 中添加元素 myList.push_back(10);myList.push_back(20);myList.push_back(30);// 再次檢查 list 是否為空,并輸出結果 if (!myList.empty()) {std::cout << "List is not empty." << std::endl;}// 獲取 list 的大小,并輸出結果 std::cout << "Size of the list: " << myList.size() << std::endl;// 獲取 list 可以容納的最大元素數量,并輸出結果 std::cout << "Max size of the list: " << myList.max_size() << std::endl;// 改變 list 的大小,增加元素 myList.resize(5);std::cout << "After resizing to 5 elements (no value specified), size of the list: " << myList.size() << std::endl;// 再次改變 list 的大小,并用特定值填充新元素 myList.resize(8, 12);std::cout << "After resizing to 8 elements with value 12, size of the list: " << myList.size() << std::endl;// 遍歷并輸出 list 中的所有元素 for (const auto& element : myList) {std::cout << element << " ";}std::cout << std::endl;return 0;
}
上面代碼的輸出為:
List is empty.
List is not empty.
Size of the list: 3
Max size of the list: 768614336404564650
After resizing to 5 elements (no value specified), size of the list: 5
After resizing to 8 elements with value 12, size of the list: 8
10 20 30 0 0 12 12 12
在上面代碼中,首先創建了一個空的 std::list,并使用 empty() 方法來檢查它是否為空。然后向 std::list 中添加了一些元素,并再次檢查它是否為空。接著使用 size() 方法來獲取 std::list 的大小,并使用 max_size() 方法來獲取它可以容納的最大元素數量。
之后使用 resize() 方法來改變 std::list 的大小。首先將它的大小增加到 5,沒有指定新的元素值,所以 std::list 的尾部將添加一些默認構造的元素(對于 int 類型,這些元素的值是未定義的)。然后將 std::list 的大小增加到 8,并用值 12 來填充新增的元素。
最后遍歷 std::list 并輸出它的所有元素,以驗證 resize() 方法的效果。
注意:std::list 的 max_size() 方法通常返回的是一個非常大的數字,代表理論上的最大大小限制,這通常比實際可用的內存要大得多。在實際使用中,由于內存限制,可能無法接近這個理論上的最大值。
2.3 std::list 的構造函數與析構函數
構造函數
std::list 有多個構造函數,允許以不同的方式創建 list 對象。下面是一些常用的構造函數:
- list(): 默認構造函數,創建一個空的 list。
- list(const list& other): 拷貝構造函數,創建一個與 other 相同內容的 list。
- list(list&& other) noexcept: 移動構造函數,移動 other 中的元素到新的 list(C++11 新加入)。
- list(const_iterator first, const_iterator last): 用迭代器范圍構造 list。
- list(InitializerList init): 使用初始化列表構造 list(C++11 新加入)。
析構函數
- ~list(): 析構函數,釋放 list 中的所有元素。
如下為樣例代碼:
#include <iostream>
#include <list> int main()
{// 使用默認構造函數創建一個空的 list std::list<int> emptyList;std::cout << "Empty list size: " << emptyList.size() << std::endl;// 向 emptyList 添加一些元素 for (int i = 0; i < 6; i++) {emptyList.push_back(i);}// 使用拷貝構造函數創建一個與 emptyList 相同內容的 list std::list<int> copiedList(emptyList);std::cout << "Copied list size: " << copiedList.size() << std::endl;// 使用移動構造函數移動 emptyList 中的元素到新的 list std::list<int> movedList(std::move(emptyList));std::cout << "Moved list size: " << movedList.size() << std::endl;std::cout << "Original list (after move) size: " << emptyList.size() << std::endl;// 使用迭代器范圍構造 list std::list<int>::const_iterator itBegin = copiedList.begin();std::list<int>::const_iterator itEnd = copiedList.end();std::list<int> rangedList(itBegin, itEnd);std::cout << "Ranged list size: " << rangedList.size() << std::endl;// 使用初始化列表構造 list (C++11) std::initializer_list<int> initList = { 7, 8, 9, 10 };std::list<int> initListList(initList);std::cout << "Init list size: " << initListList.size() << std::endl;// 打印所有 list 的內容 std::cout << "Copied list: ";for (int num : copiedList) {std::cout << num << " ";}std::cout << std::endl;std::cout << "Moved list: ";for (int num : movedList) {std::cout << num << " ";}std::cout << std::endl;std::cout << "Ranged list: ";for (int num : rangedList) {std::cout << num << " ";}std::cout << std::endl;std::cout << "Init list: ";for (int num : initListList) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
上面代碼的輸出為:
Empty list size: 0
Copied list size: 6
Moved list size: 6
Original list (after move) size: 0
Ranged list size: 6
Init list size: 4
Copied list: 0 1 2 3 4 5
Moved list: 0 1 2 3 4 5
Ranged list: 0 1 2 3 4 5
Init list: 7 8 9 10
上面代碼展示了如何使用 std::list 的不同構造函數來創建 list 對象。每種構造函數都用于創建一個具有不同特性的 list。另外,還展示了如何使用迭代器范圍和初始化列表來構造 list。
注意:std::list 的析構函數是自動調用的,當 list 對象離開其作用域時,它會自動釋放其分配的內存。上面代碼沒有顯式調用析構函數,因為 C++ 的內存管理會自動處理這些事項。
3 std::list 的元素操作
3.1 std::list 元素的訪問與修改
3.1.1 使用成員函數
std::list 中與元素的訪問相關的方法有如下幾種:
- front(): 返回對第一個元素的引用。
- back(): 返回對最后一個元素的引用。
如下為樣例代碼:
#include <iostream>
#include <list> int main()
{// 創建一個std::list并添加一些元素 std::list<int> numbers = { 5, 10, 15, 20, 25 };// 檢查列表是否為空 if (numbers.empty()) {std::cout << "The list is empty and cannot retrieve the first or last element." << std::endl;}else {// 使用front()獲取并打印第一個元素 std::cout << "The first element: " << numbers.front() << std::endl;// 使用back()獲取并打印最后一個元素 std::cout << "Last element: " << numbers.back() << std::endl;// 修改第一個和最后一個元素的值 numbers.front() = 1;numbers.back() = 100;// 打印修改后的列表 std::cout << "Modified list: ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;}return 0;
}
上面代碼的輸出為:
The first element: 5
Last element: 25
Modified list: 1 10 15 20 100
在上面代碼中,首先創建了一個包含五個整數的 std::list。然后檢查列表是否為空,如果不為空,則使用 front() 方法獲取并打印第一個元素,并且使用 back() 方法獲取并打印最后一個元素。接著修改了第一個和最后一個元素的值,并打印出修改后的整個列表。
注意:在實際使用中,應該始終在調用 front() 或 back() 之前檢查列表是否為空,以避免在未定義的情況下訪問元素。
3.1.2 使用迭代器
在 std::list 中,元素的訪問和修改更常見的是通過迭代器來完成的。std::list 提供了幾種不同的迭代器,包括正向迭代器(iterator)和常量迭代器(const_iterator)。正向迭代器允許你修改元素的值,而常量迭代器則只允許你讀取元素的值。
訪問元素
要訪問 std::list 中的元素,可以使用正向迭代器或常量迭代器。以下是如何使用這些迭代器來訪問元素的示例:
#include <iostream>
#include <list> int main()
{// 創建一個包含整數的 std::list std::list<int> my_list = { 10, 20, 30, 40, 50 };// 使用正向迭代器訪問元素 for (std::list<int>::iterator it = my_list.begin(); it != my_list.end(); ++it) {std::cout << *it << " "; // 輸出: 10 20 30 40 50 }std::cout << std::endl;// 使用常量迭代器訪問元素 for (const auto& num : my_list) {std::cout << num << " "; // 輸出: 10 20 30 40 50 }std::cout << std::endl;return 0;
}
在上面代碼中,首先使用正向迭代器遍歷列表并打印每個元素的值。然后使用基于范圍的 for 循環和常量引用來遍歷列表并打印每個元素的值。注意,常量引用不允許修改元素的值。
修改元素
要修改 std::list 中的元素,則必須使用正向迭代器。以下是如何使用正向迭代器來修改元素的示例:
#include <iostream>
#include <list> int main()
{// 創建一個包含整數的 std::list std::list<int> my_list = { 10, 20, 30, 40, 50 };// 假設我們要修改第一個元素的值 int new_value = 99;// 使用 begin() 獲取起始迭代器,并解引用它來修改元素的值 if (!my_list.empty()) {auto it = my_list.begin();*it = new_value;std::advance(it, 2); // 移動到第三個位置 *it = new_value;// 打印修改后的列表以驗證結果 for (const auto& num : my_list) {std::cout << num << " "; // 輸出: 99 20 99 40 50 }std::cout << std::endl;}else {std::cout << "List is empty." << std::endl;}return 0;
}
在上面代碼中,使用了 begin() 方法獲取列表的第一個元素的迭代器,并通過解引用迭代器來修改其值。注意,在修改元素之前,檢查了列表是否為空,以避免在空列表上進行操作。
3.2 std::list 元素的插入與刪除
std::list 中與元素插入與刪除相關的方法有如下幾種:
- push_front(const value_type& val): 在 list 開頭插入元素。
- push_back(const value_type& val): 在 list 末尾插入元素。
- pop_front(): 刪除 list 開頭的元素。
- pop_back(): 刪除 list 末尾的元素。
- insert(const_iterator position, const value_type& val): 在指定位置插入元素。
- erase(const_iterator position): 刪除指定位置的元素。
- erase(const_iterator first, const_iterator last): 刪除指定范圍內的元素。
- unique(): 刪除 list 中的重復元素。
- unique(BinaryPredicate binary_pred): 使用自定義二元謂詞刪除指定元素。
- clear(): 刪除 list 中的所有元素。
如下為樣例代碼:
#include <iostream>
#include <list>
#include <algorithm> // 自定義二元謂詞,用于判斷兩個元素的關系
struct MyPredicate {bool operator()(const int& a, const int& b) const {// 在這個例子中,我們定義相等為兩個數模3后相等 return (a % 3) == (b % 3);}
};int main()
{// 創建一個std::list std::list<int> numbers;// 使用push_front和push_back添加元素 numbers.push_front(10);numbers.push_back(20);numbers.push_front(5);numbers.push_back(15);// 打印原始列表 std::cout << "Original list: ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 在指定位置插入元素 auto it = numbers.begin();std::advance(it, 2); // 移動到第三個位置 numbers.insert(it, 12);// 打印更新后的列表 std::cout << "List after insert: ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 刪除指定位置的元素 it = numbers.begin();std::advance(it, 3); // 移動到第四個位置 numbers.erase(it);// 打印更新后的列表 std::cout << "List after erase: ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 刪除重復元素(默認比較) numbers.unique();// 打印更新后的列表 std::cout << "List after unique (default comparison): ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 使用自定義二元謂詞刪除指定元素 numbers.unique(MyPredicate());// 打印更新后的列表 std::cout << "List after unique (custom comparison): ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 刪除列表中的所有元素 numbers.clear();// 打印清空后的列表 std::cout << "List after clear: ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 再次添加元素以展示pop_front和pop_back numbers.push_back(3);numbers.push_back(6);numbers.push_back(9);// 打印列表 std::cout << "List before popping: ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 刪除開頭和末尾的元素 numbers.pop_front();numbers.pop_back();// 打印更新后的列表 std::cout << "List after popping: ";for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
上面代碼的輸出為:
Original list: 5 10 20 15
List after insert: 5 10 12 20 15
List after erase: 5 10 12 15
List after unique (default comparison): 5 10 12 15
List after unique (custom comparison): 5 10 12
List after clear:
List before popping: 3 6 9
List after popping: 6
上面代碼展示了如何使用 std::list 的基本方法,包括在列表的開頭和末尾插入元素、刪除元素、插入元素到指定位置、刪除重復元素(包括使用自定義比較函數)以及清空整個列表。最后,它還展示了如何使用 pop_front 和 pop_back 方法刪除列表的第一個和最后一個元素。
3.3 std::list 元素的移動
std::list 中與元素移動相關的方法有如下幾種:
- splice(const_iterator position, list& other): 將 other 中的所有元素移動到 list 的指定位置。
- splice(const_iterator position, list&& other): 將 other 中的所有元素移動到 list 的指定位置(移動語義)。
- splice(const_iterator position, other, const_iterator i): 將 other 中從 i 開始的單個元素移動到 list 的指定位置。
- splice(const_iterator position, other, const_iterator first, const_iterator last): 將 other 中從 first 到 last 的元素移動到 list 的指定位置。
如下為樣例代碼:
#include <iostream>
#include <list> int main()
{// 創建兩個std::list并初始化 std::list<int> numbers1{ 1, 3, 5, 7, 9 };std::list<int> numbers2{ 2, 4, 6, 8, 10 };// 打印初始列表 std::cout << "List 1 (numbers1): ";for (const auto& num : numbers1) {std::cout << num << " ";}std::cout << std::endl;std::cout << "List 2 (numbers2): ";for (const auto& num : numbers2) {std::cout << num << " ";}std::cout << std::endl;// 使用splice移動numbers2的所有元素到numbers1的開頭 numbers1.splice(numbers1.begin(), numbers2);// 打印更新后的列表 std::cout << "List 1 after splicing all of numbers2: ";for (const auto& num : numbers1) {std::cout << num << " ";}std::cout << std::endl;// numbers2現在是空的 std::cout << "List 2 after splicing: ";for (const auto& num : numbers2) {std::cout << num << " ";}std::cout << std::endl;// 使用splice移動numbers1中的一個元素到另一個位置 auto it = numbers1.begin();std::advance(it, 3); // 移動到第三個位置 numbers1.splice(numbers1.begin(), numbers1, it);// 打印更新后的列表 std::cout << "List 1 after moving a single element: ";for (const auto& num : numbers1) {std::cout << num << " ";}std::cout << std::endl;// 重置numbers2為初始狀態 numbers2 = { 2, 4, 6, 8, 10 };// 使用splice移動numbers1中的一個元素范圍到另一個位置 it = numbers1.begin();std::advance(it, 2); // 移動到第三個位置 auto it2 = std::next(it);std::advance(it2, 3); // 移動到第六個位置(不包括) numbers1.splice(numbers1.begin(), numbers1, it, it2);// 打印更新后的列表 std::cout << "List 1 after moving a range of elements: ";for (const auto& num : numbers1) {std::cout << num << " ";}std::cout << std::endl;// 使用移動語義的splice移動numbers2的所有元素到numbers1的開頭 numbers2 = { 2, 4, 6, 8, 10 }; // 重置numbers2 numbers1.splice(numbers1.begin(), std::move(numbers2));// 打印更新后的列表 std::cout << "List 1 after splicing all of numbers2 using move semantics: ";for (const auto& num : numbers1) {std::cout << num << " ";}std::cout << std::endl;// numbers2現在是空的,因為使用了移動語義 std::cout << "List 2 after splicing with move semantics: ";for (const auto& num : numbers2) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
上面代碼的輸出為:
List 1 (numbers1): 1 3 5 7 9
List 2 (numbers2): 2 4 6 8 10
List 1 after splicing all of numbers2: 2 4 6 8 10 1 3 5 7 9
List 2 after splicing:
List 1 after moving a single element: 8 2 4 6 10 1 3 5 7 9
List 1 after moving a range of elements: 4 6 10 1 8 2 3 5 7 9
List 1 after splicing all of numbers2 using move semantics: 2 4 6 8 10 4 6 10 1 8 2 3 5 7 9
List 2 after splicing with move semantics:
上面代碼展示了如何使用 splice 方法的各個版本。首先創建了兩個std::list對象并初始化。然后分步驟地展示了如何將一個列表的元素移動到另一個列表的不同位置,包括移動整個列表、單個元素和元素范圍。最后展示了如何使用移動語義來移動一個列表的所有元素,這通常比復制更高效,因為它避免了不必要的拷貝操作。
3.4 std::list 的交換與合并
std::list 中與交換與合并的方法有如下幾種:
- merge(list& other): 合并 other 到當前 list。
- merge(list& other, BinaryPredicate binary_pred): 使用自定義二元謂詞合并 other 到當前 list。
- swap(list& other): 交換兩個 list 的內容。
如下為樣例代碼:
#include <iostream>
#include <list>
#include <algorithm> // 用于std::less // 自定義二元謂詞,用于降序合并
struct DescendingPredicate {bool operator()(const int& a, const int& b) const {return a > b;}
};int main()
{// 創建兩個std::list并初始化 std::list<int> list1{ 1, 3, 5, 7, 9 };std::list<int> list2{ 2, 4, 6, 8, 10 };// 打印初始列表 std::cout << "List 1 (before merge): ";for (const auto& num : list1) {std::cout << num << " ";}std::cout << std::endl;std::cout << "List 2 (before merge): ";for (const auto& num : list2) {std::cout << num << " ";}std::cout << std::endl;// 使用默認合并,按升序合并list2到list1 list1.merge(list2);// 打印合并后的列表 std::cout << "List 1 (after merge, ascending order): ";for (const auto& num : list1) {std::cout << num << " ";}std::cout << std::endl;// 要將兩個 std::list 降序合并,需要確保兩個列表在合并之前都是降序排列的// 反轉 list1 中元素的順序,使其為降序排序list1.reverse();// 重置 list2 為初始狀態 list2 = { 10, 8, 6, 4, 2 };// 使用自定義二元謂詞合并,按降序合并list2到list1 list1.merge(list2, DescendingPredicate());// 打印合并后的列表 std::cout << "List 1 (after merge, descending order): ";for (const auto& num : list1) {std::cout << num << " ";}std::cout << std::endl;// 打印list2,它現在應該是空的,因為元素被合并到了list1 std::cout << "List 2 (after merge): ";for (const auto& num : list2) {std::cout << num << " ";}std::cout << std::endl;// 現在交換兩個列表的內容 list1.swap(list2);// 打印交換后的列表 std::cout << "List 1 (after swap): ";for (const auto& num : list1) {std::cout << num << " ";}std::cout << std::endl;std::cout << "List 2 (after swap): ";for (const auto& num : list2) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
上面代碼的輸出為:
List 1 (before merge): 1 3 5 7 9
List 2 (before merge): 2 4 6 8 10
List 1 (after merge, ascending order): 1 2 3 4 5 6 7 8 9 10
List 1 (after merge, descending order): 10 10 9 8 8 7 6 6 5 4 4 3 2 2 1
List 2 (after merge):
List 1 (after swap):
List 2 (after swap): 10 10 9 8 8 7 6 6 5 4 4 3 2 2 1
上面代碼首先合并了 list2 到 list1,先是使用默認的升序合并,然后是使用自定義的降序合并。DescendingPredicate 結構用于定義降序合并的規則。最后使用了 swap 方法交換了 list1 和 list2 的內容。
注意:要將兩個 std::list 降序合并,需要確保兩個列表在合并之前都是降序排列的。反之,要將兩個 std::list 升序合并,需要確保兩個列表在合并之前都是升序排列的。
3.5 std::list 的正向遍歷刪除
在 std::list 中遍歷并刪除元素需要小心處理,因為直接刪除元素會導致迭代器失效。一種常見的方法是使用 std::list 的 erase 方法結合 std::remove_if 算法來遍歷并刪除滿足特定條件的元素。
如下為樣例代碼:
#include <iostream>
#include <list>
#include <algorithm> int main()
{std::list<int> my_list = { 1, 2, 3, 4, 5, 6 };// 使用 std::remove_if 算法標記要刪除的元素 auto it = std::remove_if(my_list.begin(), my_list.end(), [](int n) {return n % 2 == 0; // 假設要刪除所有偶數 });// 使用 erase 方法刪除標記過的元素 my_list.erase(it, my_list.end());// 輸出結果 for (const auto& elem : my_list) {std::cout << elem << ' ';}return 0;
}
上面代碼的輸出為:
1 3 5
在上面代碼中,std::remove_if 算法遍歷 list 并將不滿足條件(即不是偶數)的元素移動到容器的前面,并返回一個指向新邏輯結尾的迭代器。然后,erase 方法用于刪除從該迭代器到 list 實際結尾的所有元素。
如果需要在遍歷過程中逐個刪除元素(效率更高),那么可以使用 std::list::erase 方法結合普通的循環,但每次刪除元素后,都需要更新迭代器。以下是一個逐個刪除特定元素的例子:
如下為樣例代碼:
#include <iostream>
#include <list> int main()
{std::list<int> my_list = { 1, 2, 3, 4, 5, 6 };auto it = my_list.begin();while (it != my_list.end()) {if ((*it) % 2 == 0) {it = my_list.erase(it); // erase 返回下一個有效元素的迭代器 }else {++it; // 繼續到下一個元素 }}// 輸出結果 for (const auto& elem : my_list) {std::cout << elem << ' ';}return 0;
}
上面代碼的輸出為:
1 3 5
在上面代碼中,使用一個循環來遍歷 list,并在每次迭代中檢查當前元素是否滿足刪除條件。如果滿足條件,則使用 erase 方法刪除該元素,并更新迭代器。如果不滿足條件,則簡單地遞增迭代器以繼續遍歷。
注意:在刪除元素后,迭代器 it 會被 erase 方法更新為指向被刪除元素之后的位置,因此在下一次循環迭代中,it 仍然有效。
3.6 std::list 的反向遍歷刪除
在 std::list 中反向遍歷并刪除元素,雖然可以使用 rbegin() 和 rend() 方法來獲取反向迭代器,但是在循環過程中仍然要再將其轉換為正向迭代器,使用起來并不方便,可以采用如下方法做反向遍歷:
#include <iostream>
#include <list> int main() {std::list<int> my_list = { 1, 2, 3, 4, 5 };if (my_list.size()>0){auto it = my_list.end();if (it != my_list.begin()){--it;}while (it != my_list.begin()){if ((*it) % 2 == 0) {it = my_list.erase(it); // erase 返回下一個有效元素的迭代器 }else{--it;}}if (my_list.size() > 0){it = my_list.begin();if ((*it) % 2 == 0) {my_list.erase(it);}}}// 打印列表以驗證結果 for (const auto& num : my_list) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
上面代碼的輸出為:
1 3 5
上面代碼的核心思路是使用 --it 來完成反向迭代的過程,要注意對 my_list.begin() 的單獨校驗處理。
4 std::list 的迭代器
4.1 std::list 迭代器的基本使用
std::list 中與迭代器相關的方法有如下幾種:
- begin(): 返回一個指向容器中第一個元素的迭代器。
- end(): 返回一個指向容器中最后一個元素之后位置的迭代器。
- rbegin(): 返回一個指向容器中最后一個元素的反向迭代器。
- rend(): 返回一個指向容器中第一個元素之前位置的反向迭代器。
- cbegin(), cend(), crbegin(), crend(): 與上述類似,但返回的是常量迭代器或常量反向迭代器。
如下為樣例代碼:
#include <iostream>
#include <list> int main()
{// 創建一個包含整數的std::list std::list<int> my_list = { 5, 10, 15, 20, 25 };// 使用正向迭代器遍歷列表 std::cout << "Iterating over the list using forward iterators:" << std::endl;for (auto it = my_list.begin(); it != my_list.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用常量正向迭代器遍歷列表 std::cout << "Iterating over the list using const forward iterators:" << std::endl;for (auto cit = my_list.cbegin(); cit != my_list.cend(); ++cit) {std::cout << *cit << " ";}std::cout << std::endl;// 使用反向迭代器反向遍歷列表 std::cout << "Iterating over the list in reverse using reverse iterators:" << std::endl;for (auto rit = my_list.rbegin(); rit != my_list.rend(); ++rit) {std::cout << *rit << " ";}std::cout << std::endl;// 使用常量反向迭代器反向遍歷列表 std::cout << "Iterating over the list in reverse using const reverse iterators:" << std::endl;for (auto crit = my_list.crbegin(); crit != my_list.crend(); ++crit) {std::cout << *crit << " ";}std::cout << std::endl;return 0;
}
上面代碼的輸出為:
Iterating over the list using forward iterators:
5 10 15 20 25
Iterating over the list using const forward iterators:
5 10 15 20 25
Iterating over the list in reverse using reverse iterators:
25 20 15 10 5
Iterating over the list in reverse using const reverse iterators:
25 20 15 10 5
在上面代碼例中,首先創建了一個包含 5 個整數的 std::list。接著使用 begin() 和 end() 來獲取正向迭代器,并使用它們來遍歷列表。然后使用 cbegin() 和 cend() 來獲取常量正向迭代器,并再次遍歷列表。這樣做是為了展示即使通過常量迭代器訪問元素,列表的內容也不會被修改。
之后使用 rbegin() 和 rend() 來獲取反向迭代器,并使用它們來反向遍歷列表。最后使用 crbegin() 和 crend() 來獲取常量反向迭代器,并反向遍歷列表。
注意:cbegin(), cend(), crbegin()和 crend()返回的是常量迭代器,這意味著不能通過它們來修改列表中的元素。嘗試這樣做會導致編譯錯誤。
4.2 std::list 迭代器使用的注意事項
在使用 std::list 的迭代器時,有一些重要的注意事項需要牢記,以確保代碼的正確性和穩定性:
(1)迭代器失效:
當通過迭代器刪除或插入元素時,所有指向被修改位置的迭代器、引用和指針都會失效。這意味著不能再使用它們來訪問或修改列表中的元素。
在刪除或插入元素后,任何指向被刪除或插入元素之前的元素的迭代器仍然有效,但指向被刪除或插入元素之后的元素的迭代器將失效。
(2)迭代器的范圍和邊界:
迭代器的范圍是從 begin() 到 end(),但不包括 end() 所指向的位置。end() 是一個哨兵迭代器,用于指示列表的末尾,不應被解引用。
(3)線程安全性:
如果多個線程同時修改 std::list,則必須采取適當的同步措施來避免數據競爭和未定義行為。在這種情況下,迭代器的使用可能變得更加復雜和危險。
(4)注意性能:
雖然 std::list 的插入和刪除操作在平均情況下是常數時間的,但頻繁地在列表中間插入或刪除元素可能會導致列表的重新分配和元素的復制,這可能會影響性能。