C++ list类

目录

0.前言

1.list介绍

1.1优势

1.2劣势

1.3容器属性

2.list使用

2.1构造函数

2.1.1默认构造函数

2.1.2填充构造函数

2.1.3范围构造函数

2.1.4拷贝构造函数

2.1.5初始化列表构造函数 

2.2迭代器

2.2.1 begin()

2.2.2 end()

2.2.3 cbegin()

2.2.4 cend()

2.2.5 rbegin()

2.2.6 rend()

2.2.7 crbegin()

2.2.8 crend()

2.3容量获取及元素访问函数

2.3.1 empty()

2.3.2 size()

2.3.3 front()

2.3.4 back()

2.4修改函数

2.4.1 assign

2.4.2 push_front 和 pop_front

 2.4.3 push_back 和 pop_back

 2.4.4 insert

2.4.5 erase

2.4.6 swap

2.4.7 clear

2.5其他功能函数

2.5.1 splice

2.5.2 unique

3.list的模拟实现

3.1基本实现代码

3.2 list正/反向迭代器实现

3.2.1正向迭代器 (ListIterator)

3.2.2反向迭代器 (ListReverseIterator)

4.list与vector的比较

4.1 内部实现

4.2 存储方式

4.3 访问和遍历

4.4 插入和删除操作

4.5 内存管理

4.6 使用场景

5.结语


(图像由AI生成) 

0.前言

在前面的文章中,我们已经详细介绍了 C++ 标准模板库中的 stringvector 类的用法及实现。接下来,我们将深入探讨 list 类,这是一种双向链表结构,适合于频繁插入和删除操作的场景。通过这篇文章,我们将全面了解 list 的构造函数、迭代器、容量获取与元素访问函数、修改函数、以及其他功能函数,并比较 listvector 的异同,帮助你在实际编程中做出更合适的数据结构选择。

1.list介绍

list 是 C++ 标准模板库中的一种序列容器,允许在序列中的任何位置进行常数时间的插入和删除操作,并且支持双向迭代。它的实现基于双向链表,这种结构允许元素存储在不同且不相关的内存位置,内部通过每个元素与前一个和后一个元素的链接来保持顺序。

双向链表与单向链表(如 forward_list)非常相似,主要区别在于单向链表只能向前迭代,而双向链表可以在两个方向上迭代。尽管单向链表在内存占用和效率上略优,但双向链表提供了更灵活的迭代和操作方式。

1.1优势

与其他基本标准序列容器(如 arrayvectordeque)相比,list 在以下方面通常表现更好:

  • 插入和删除操作:在任意位置插入、提取和移动元素的性能更优,特别是在已获得迭代器的位置。
  • 排序算法:在需要频繁插入和删除操作的排序算法中表现更佳。

1.2劣势

listforward_list 与其他序列容器相比存在一些缺点:

  • 缺乏直接访问:无法通过位置直接访问元素。例如,要访问 list 中的第六个元素,需要从已知位置(如开头或结尾)迭代到该位置,这需要线性时间。
  • 额外的内存开销:为了维护元素之间的链接信息,list 需要额外的内存,这在处理大量小元素时可能是一个重要因素。

1.3容器属性

  • 序列:序列容器中的元素按严格的线性顺序排列。可以通过其在序列中的位置访问单个元素。
  • 双向链表:每个元素都包含信息以定位下一个和前一个元素,从而允许在特定元素之前或之后(甚至是整个范围)进行常数时间的插入和删除操作,但不支持直接随机访问。
  • 分配器感知:容器使用分配器对象来动态管理其存储需求。

2.list使用

2.1构造函数

default (1)
explicit list (const allocator_type& alloc = allocator_type());
fill (2)
explicit list (size_type n);list (size_type n, const value_type& val,const allocator_type& alloc = allocator_type());
range (3)
template <class InputIterator>list (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
copy (4)
list (const list& x);
list (const list& x, const allocator_type& alloc);
initializer list (5)
list (initializer_list<value_type> il,const allocator_type& alloc = allocator_type());

C++ 中的 list 提供了多种构造函数,用于不同的初始化需求。下面我们详细介绍每种构造函数,并提供示例代码来说明它们的使用。

2.1.1默认构造函数

explicit list (const allocator_type& alloc = allocator_type());

创建一个空链表,可以指定一个分配器。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist;std::cout << "Size of mylist: " << mylist.size() << std::endl;return 0;
}
//输出:Size of mylist: 0

2.1.2填充构造函数

explicit list (size_type n);
list (size_type n, const value_type& val, const allocator_type& alloc = allocator_type());

创建一个包含 n 个默认值元素的链表,或创建一个包含 n 个指定值 val 的链表。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist1(5); // 创建一个包含5个默认值元素的链表std::list<int> mylist2(5, 100); // 创建一个包含5个值为100的元素的链表std::cout << "mylist1 contains:";for (int& x : mylist1) std::cout << ' ' << x;std::cout << '\n';std::cout << "mylist2 contains:";for (int& x : mylist2) std::cout << ' ' << x;std::cout << '\n';return 0;
}
//输出:
//mylist1 contains: 0 0 0 0 0
//mylist2 contains: 100 100 100 100 100

2.1.3范围构造函数

template <class InputIterator>
list (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());

使用迭代器范围 [first, last) 构造链表。

#include <iostream>
#include <list>int main() {std::list<int> original = {1, 2, 3, 4, 5};std::list<int> copy(original); // 使用拷贝构造函数std::cout << "copy contains:";for (int& x : copy) std::cout << ' ' << x;std::cout << '\n';return 0;
}

示例代码:

#include <iostream>
#include <list>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};std::list<int> mylist(vec.begin(), vec.end()); // 使用vector的迭代器范围构造链表std::cout << "mylist contains:";for (int& x : mylist) std::cout << ' ' << x;std::cout << '\n';return 0;
}
//输出:mylist contains: 1 2 3 4 5

2.1.4拷贝构造函数

list (const list& x);
list (const list& x, const allocator_type& alloc);

创建一个与现有链表 x 相同的新链表。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> original = {1, 2, 3, 4, 5};std::list<int> copy(original); // 使用拷贝构造函数std::cout << "copy contains:";for (int& x : copy) std::cout << ' ' << x;std::cout << '\n';return 0;
}
//输出:copy contains: 1 2 3 4 5

2.1.5初始化列表构造函数 

list (initializer_list<value_type> il, const allocator_type& alloc = allocator_type());

使用初始化列表 il 构造链表。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30, 40, 50}; // 使用初始化列表构造链表std::cout << "mylist contains:";for (int& x : mylist) std::cout << ' ' << x;std::cout << '\n';return 0;
}
//输出:mylist contains: 10 20 30 40 50

2.2迭代器

2.2.1 begin()

begin() 返回指向容器中第一个元素的迭代器。

2.2.2 end()

end() 返回指向容器末尾后一个位置的迭代器,这个位置不包含任何元素。

2.2.3 cbegin()

cbegin() 返回指向容器中第一个元素的常量迭代器,不能用于修改元素。

2.2.4 cend()

cend() 返回指向容器末尾后一个位置的常量迭代器,这个位置不包含任何元素,不能用于修改元素。

2.2.5 rbegin()

rbegin() 返回指向容器中最后一个元素的反向迭代器。

2.2.6 rend()

rend() 返回指向容器第一个元素前一个位置的反向迭代器,这个位置不包含任何元素。

2.2.7 crbegin()

crbegin() 返回指向容器中最后一个元素的常量反向迭代器,不能用于修改元素。

2.2.8 crend()

crend() 返回指向容器第一个元素前一个位置的常量反向迭代器,这个位置不包含任何元素,不能用于修改元素。

下面是一个示例代码,展示了这些迭代器的使用:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30, 40, 50};// 使用 begin() 和 end() 迭代器std::cout << "Elements using begin() and end(): ";for (auto it = mylist.begin(); it != mylist.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 cbegin() 和 cend() 迭代器std::cout << "Elements using cbegin() and cend(): ";for (auto it = mylist.cbegin(); it != mylist.cend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 rbegin() 和 rend() 迭代器std::cout << "Elements using rbegin() and rend(): ";for (auto it = mylist.rbegin(); it != mylist.rend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 crbegin() 和 crend() 迭代器std::cout << "Elements using crbegin() and crend(): ";for (auto it = mylist.crbegin(); it != mylist.crend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;return 0;
}

代码解析

  • begin() 和 end():用于从前向后遍历并访问列表中的元素。
  • cbegin() 和 cend():常量迭代器,用于从前向后遍历但不允许修改元素。
  • rbegin() 和 rend():反向迭代器,用于从后向前遍历并访问列表中的元素。
  • crbegin() 和 crend():常量反向迭代器,用于从后向前遍历但不允许修改元素。

2.3容量获取及元素访问函数

在使用 C++ 的 list 类时,容量获取和元素访问函数是非常重要的,它们可以帮助我们了解和操作容器中的元素。主要包括以下几个函数。

2.3.1 empty()

empty() 函数用于检查容器是否为空。如果容器中没有元素,则返回 true,否则返回 false

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist;if (mylist.empty()) {std::cout << "The list is empty." << std::endl;} else {std::cout << "The list is not empty." << std::endl;}mylist.push_back(10);if (mylist.empty()) {std::cout << "The list is empty." << std::endl;} else {std::cout << "The list is not empty." << std::endl;}return 0;
}
//输出:
//The list is empty.
//The list is not empty.

2.3.2 size()

size() 函数返回容器中元素的数量。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30, 40, 50};std::cout << "The size of the list is: " << mylist.size() << std::endl;mylist.push_back(60);std::cout << "After adding an element, the size of the list is: " << mylist.size() << std::endl;return 0;
}
//输出:
//The size of the list is: 5
//After adding an element, the size of the list is: 6

2.3.3 front()

front() 函数返回容器中第一个元素的引用。注意,front() 不能用于空容器,否则行为是未定义的。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30};std::cout << "The first element is: " << mylist.front() << std::endl;mylist.front() = 100; // 修改第一个元素的值std::cout << "After modification, the first element is: " << mylist.front() << std::endl;return 0;
}
//输出:
//The first element is: 10
//After modification, the first element is: 100

2.3.4 back()

back() 函数返回容器中最后一个元素的引用。注意,back() 不能用于空容器,否则行为是未定义的。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30};std::cout << "The last element is: " << mylist.back() << std::endl;mylist.back() = 300; // 修改最后一个元素的值std::cout << "After modification, the last element is: " << mylist.back() << std::endl;return 0;
}
//输出:
//The last element is: 30
//After modification, the last element is: 300

2.4修改函数

C++ list 类提供了多种修改容器内容的函数。这些函数允许我们在列表中插入、删除和替换元素。下面我们将详细介绍以下修改函数,并提供示例代码展示它们的使用:

2.4.1 assign

assign 函数用于替换当前容器内容,接受多个重载版本,可以使用指定的元素、范围或初始化列表。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist;mylist.assign(5, 100); // 使用5个100替换当前内容std::cout << "List after assign(5, 100): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;std::list<int> another_list = {1, 2, 3};mylist.assign(another_list.begin(), another_list.end()); // 使用另一个list的范围替换当前内容std::cout << "List after assign from another list: ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;mylist.assign({10, 20, 30}); // 使用初始化列表替换当前内容std::cout << "List after assign({10, 20, 30}): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List after assign(5, 100): 100 100 100 100 100 
// List after assign from another list: 1 2 3 
// List after assign({10, 20, 30}): 10 20 30 

2.4.2 push_frontpop_front

  • push_front 函数在容器开头插入一个元素。
  • pop_front 函数移除容器开头的元素。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {20, 30};mylist.push_front(10); // 在开头插入元素10std::cout << "List after push_front(10): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;mylist.pop_front(); // 移除开头的元素std::cout << "List after pop_front(): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List after push_front(10): 10 20 30 
// List after pop_front(): 20 30 

 2.4.3 push_backpop_back

  • push_back 函数在容器末尾插入一个元素。
  • pop_back 函数移除容器末尾的元素。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20};mylist.push_back(30); // 在末尾插入元素30std::cout << "List after push_back(30): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;mylist.pop_back(); // 移除末尾的元素std::cout << "List after pop_back(): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List after push_back(30): 10 20 30 
// List after pop_back(): 10 20 

 2.4.4 insert

insert 函数用于在指定位置插入一个或多个元素,可以通过单个元素、初始化列表或迭代器范围进行插入。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30};auto it = mylist.begin();++it; // 指向第二个元素mylist.insert(it, 15); // 在第二个元素前插入15std::cout << "List after insert(15): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;mylist.insert(it, 2, 25); // 在第二个元素前插入两个25std::cout << "List after insert(2, 25): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;std::list<int> another_list = {1, 2, 3};mylist.insert(it, another_list.begin(), another_list.end()); // 在第二个元素前插入另一个list的范围std::cout << "List after insert from another list: ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List after insert(15): 10 15 20 30 
// List after insert(2, 25): 10 25 25 15 20 30 
// List after insert from another list: 10 1 2 3 25 25 15 20 30 

2.4.5 erase

erase 函数用于移除一个或多个元素,可以通过单个迭代器或迭代器范围进行删除。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30, 40, 50};auto it = mylist.begin();++it; // 指向第二个元素mylist.erase(it); // 移除第二个元素std::cout << "List after erase second element: ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;auto it1 = mylist.begin();auto it2 = mylist.end();--it2; // 指向最后一个元素mylist.erase(it1, it2); // 移除范围 [it1, it2)std::cout << "List after erase range [begin, end-1): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List after erase second element: 10 30 40 50 
// List after erase range [begin, end-1): 50 

2.4.6 swap

swap 函数用于交换两个容器的内容。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> list1 = {1, 2, 3};std::list<int> list2 = {10, 20, 30};list1.swap(list2); // 交换list1和list2的内容std::cout << "List1 after swap: ";for (const int& x : list1) {std::cout << x << " ";}std::cout << std::endl;std::cout << "List2 after swap: ";for (const int& x : list2) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List1 after swap: 10 20 30 
// List2 after swap: 1 2 3 

2.4.7 clear

clear 函数用于移除容器中的所有元素,使其变为空容器。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 30};mylist.clear(); // 清空list中的所有元素std::cout << "List after clear: ";if (mylist.empty()) {std::cout << "The list is empty." << std::endl;} else {for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;}return 0;
}
// 输出:
// List after clear: The list is empty.

2.5其他功能函数

除了基本的修改函数,C++ 的 list 类还提供了一些其他功能函数,这些函数可以更高效地操作链表中的元素。

2.5.1 splice

splice 函数用于将另一个 list 中的元素移到当前 list 的指定位置。splice 函数有三个重载版本:

  1. splice(iterator position, list& x):将 x 的所有元素移动到当前 listposition 之前的位置。
  2. splice(iterator position, list& x, iterator i):将 x 中的元素 i 移动到当前 listposition 之前的位置。
  3. splice(iterator position, list& x, iterator first, iterator last):将 x[first, last) 范围内的元素移动到当前 listposition 之前的位置。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> list1 = {1, 2, 3, 4, 5};std::list<int> list2 = {10, 20, 30};auto it = list1.begin();++it; // 指向第二个元素// 将list2的所有元素移动到list1中第二个元素之前list1.splice(it, list2);std::cout << "List1 after splice all elements from List2: ";for (const int& x : list1) {std::cout << x << " ";}std::cout << std::endl;std::cout << "List2 after splice all elements to List1: ";for (const int& x : list2) {std::cout << x << " ";}std::cout << std::endl;// 初始化list2list2 = {10, 20, 30};it = list1.begin();++it; // 指向第二个元素auto it2 = list2.begin();++it2; // 指向list2中的第二个元素// 将list2中的第二个元素移动到list1中第二个元素之前list1.splice(it, list2, it2);std::cout << "List1 after splice one element from List2: ";for (const int& x : list1) {std::cout << x << " ";}std::cout << std::endl;std::cout << "List2 after splice one element to List1: ";for (const int& x : list2) {std::cout << x << " ";}std::cout << std::endl;// 初始化list2list2 = {10, 20, 30};it = list1.begin();++it; // 指向第二个元素// 将list2中的第一个和第二个元素移动到list1中第二个元素之前list1.splice(it, list2, list2.begin(), ++(++list2.begin()));std::cout << "List1 after splice range from List2: ";for (const int& x : list1) {std::cout << x << " ";}std::cout << std::endl;std::cout << "List2 after splice range to List1: ";for (const int& x : list2) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List1 after splice all elements from List2: 1 10 20 30 2 3 4 5
// List2 after splice all elements to List1:
// List1 after splice one element from List2: 1 20 10 20 30 2 3 4 5
// List2 after splice one element to List1: 10 30
// List1 after splice range from List2: 1 10 20 20 10 20 30 2 3 4 5
// List2 after splice range to List1: 30

2.5.2 unique

unique 函数用于移除容器中相邻的重复元素,只保留一个。它有两个重载版本:

  1. unique():移除相邻重复的元素。
  2. unique(BinaryPredicate pred):使用自定义的二元谓词函数来判断元素是否相邻重复。

示例代码:

#include <iostream>
#include <list>int main() {std::list<int> mylist = {10, 20, 20, 30, 30, 30, 40, 40, 50};mylist.unique(); // 移除相邻重复的元素std::cout << "List after unique(): ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;mylist = {10, 20, 21, 30, 31, 32, 40, 41, 50};// 自定义二元谓词函数,移除相邻差值小于等于1的元素mylist.unique([](int a, int b) { return std::abs(a - b) <= 1; });std::cout << "List after unique with custom predicate: ";for (const int& x : mylist) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
// 输出:
// List after unique(): 10 20 30 40 50 
// List after unique with custom predicate: 10 20 30 32 40 50

3.list的模拟实现

3.1基本实现代码

#pragma once
#include <cstddef> // for size_ttemplate<class T>
struct ListNode
{T data;ListNode<T>* _next;ListNode<T>* _prev;ListNode(const T& d = T()): data(d), _next(nullptr), _prev(nullptr){}
};template<class T, class Ref, class Ptr>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* node): _node(node){}Ref operator*(){return _node->data;}Ptr operator->(){return &_node->data;}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& it) const{return _node != it._node;}bool operator==(const Self& it) const{return _node == it._node;}
};template<class T, class Ref, class Ptr>
struct ListReverseIterator
{typedef ListNode<T> Node;typedef ListReverseIterator<T, Ref, Ptr> Self;Node* _node;ListReverseIterator(Node* node): _node(node){}Ref operator*(){return _node->data;}Ptr operator->(){return &_node->data;}Self& operator++(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_prev;return tmp;}Self& operator--(){_node = _node->_next;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const Self& it) const{return _node != it._node;}bool operator==(const Self& it) const{return _node == it._node;}
};template<class T>
class list
{
public:typedef ListNode<T> Node;typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;typedef ListReverseIterator<T, T&, T*> reverse_iterator;typedef ListReverseIterator<T, const T&, const T*> const_reverse_iterator;typedef T& reference;typedef const T& const_reference;private:Node* _head;public:list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}~list(){clear();delete _head;_head = nullptr;}list(const list<T>& l){_head = new Node;_head->_next = _head;_head->_prev = _head;for (const auto& e : l){push_back(e);}}list(size_t n, const T& data = T()){_head = new Node;_head->_next = _head;_head->_prev = _head;while (n--){push_back(data);}}list<T>& operator=(const list<T>& l){if (this != &l){list<T> tmp(l);swap(tmp);}return *this;}bool empty() const{return _head->_next == _head;}size_t size() const{size_t count = 0;Node* cur = _head->_next;while (cur != _head){++count;cur = cur->_next;}return count;}reference front(){return _head->_next->data;}const_reference front() const{return _head->_next->data;}reference back(){return _head->_prev->data;}const_reference back() const{return _head->_prev->data;}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(_head->_prev);}reverse_iterator rend(){return reverse_iterator(_head);}const_reverse_iterator crbegin() const{return const_reverse_iterator(_head->_prev);}const_reverse_iterator crend() const{return const_reverse_iterator(_head);}iterator insert(iterator pos, const T& data){Node* newNode = new Node(data);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;return iterator(newNode);}void push_back(const T& data){insert(end(), data);}void push_front(const T& data){insert(begin(), data);}iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}iterator erase(iterator first, iterator last){while (first != last){first = erase(first);}return last;}void pop_front(){erase(begin());}void pop_back(){erase(--end());}void swap(list<T>& l){std::swap(_head, l._head);}void clear(){Node* cur = _head->_next;while (cur != _head){Node* next = cur->_next;delete cur;cur = next;}_head->_next = _head;_head->_prev = _head;}
};

3.2 list正/反向迭代器实现

list 容器的模拟实现中,迭代器是一个非常重要的部分。迭代器允许我们遍历、访问和修改链表中的元素。为了支持标准库中 list 的功能,我们需要实现正向迭代器和反向迭代器。下面是对正向迭代器和反向迭代器实现的详细介绍。

3.2.1正向迭代器 (ListIterator)

ListIterator 是一个模板结构体,用于表示 list 容器的正向迭代器。它支持常见的迭代器操作,如解引用、前进、后退和比较。

定义和成员

template<class T, class Ref, class Ptr>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node; // 指向当前节点的指针// 构造函数ListIterator(Node* node): _node(node){}// 解引用运算符,返回当前节点的数据Ref operator*(){return _node->data;}// 指针访问运算符,返回指向当前节点数据的指针Ptr operator->(){return &_node->data;}// 前置递增运算符,移动到下一个节点Self& operator++(){_node = _node->_next;return *this;}// 后置递增运算符,移动到下一个节点,返回旧值Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}// 前置递减运算符,移动到前一个节点Self& operator--(){_node = _node->_prev;return *this;}// 后置递减运算符,移动到前一个节点,返回旧值Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}// 不等比较运算符bool operator!=(const Self& it) const{return _node != it._node;}// 相等比较运算符bool operator==(const Self& it) const{return _node == it._node;}
};

3.2.2反向迭代器 (ListReverseIterator)

ListReverseIterator 是一个模板结构体,用于表示 list 容器的反向迭代器。反向迭代器允许我们从后向前遍历容器。

定义和成员

template<class T, class Ref, class Ptr>
struct ListReverseIterator
{typedef ListNode<T> Node;typedef ListReverseIterator<T, Ref, Ptr> Self;Node* _node; // 指向当前节点的指针// 构造函数ListReverseIterator(Node* node): _node(node){}// 解引用运算符,返回当前节点的数据Ref operator*(){return _node->data;}// 指针访问运算符,返回指向当前节点数据的指针Ptr operator->(){return &_node->data;}// 前置递增运算符,移动到前一个节点Self& operator++(){_node = _node->_prev;return *this;}// 后置递增运算符,移动到前一个节点,返回旧值Self operator++(int){Self tmp(*this);_node = _node->_prev;return tmp;}// 前置递减运算符,移动到下一个节点Self& operator--(){_node = _node->_next;return *this;}// 后置递减运算符,移动到下一个节点,返回旧值Self operator--(int){Self tmp(*this);_node = _node->_next;return tmp;}// 不等比较运算符bool operator!=(const Self& it) const{return _node != it._node;}// 相等比较运算符bool operator==(const Self& it) const{return _node == it._node;}
};

4.list与vector的比较

listvector 是 C++ 标准模板库(STL)中两个常用的序列容器,它们在内部实现、使用场景和性能方面都有显著差异。下面将详细介绍 listvector 的比较,从以下几个方面进行探讨:

4.1 内部实现

  • listlist 是一种双向链表,每个节点包含一个数据元素以及指向前一个和后一个节点的指针。它的实现基于动态分配的节点,节点通过指针相互连接。
  • vectorvector 是一种动态数组,底层实现为一个连续的内存块。它通过动态调整大小来适应插入和删除操作。

4.2 存储方式

  • list:存储的元素不连续,每个元素通过指针连接到前一个和后一个元素。由于链表节点是分散存储的,因此元素的物理位置在内存中不是连续的。
  • vector:存储的元素是连续的,这意味着 vector 的元素在内存中是紧挨着的。这样有利于缓存的利用,访问速度较快。

4.3 访问和遍历

  • list:访问任意位置的元素需要线性时间复杂度 (O(n)),因为必须从链表的头部或尾部开始遍历到目标位置。list 支持双向迭代。
  • vector:可以在常数时间内 (O(1)) 通过索引直接访问任意位置的元素。vector 支持随机访问和双向迭代。

4.4 插入和删除操作

  • list:在任意位置插入或删除元素的时间复杂度为常数时间 (O(1)),只需调整指针。然而,找到插入或删除位置可能需要线性时间。
  • vector:在末尾插入或删除元素的时间复杂度为常数时间 (O(1)),但是在中间位置插入或删除元素的时间复杂度为线性时间 (O(n)),因为需要移动其他元素以保持连续性。

4.5 内存管理

  • list:每个元素都需要额外的内存来存储前后指针,因此内存开销较大。链表节点是分散分配的,适合频繁的插入和删除操作。
  • vector:当需要增加容量时,vector 会重新分配一块更大的内存,并将现有元素复制到新位置,这可能涉及大量的元素移动。为了减少内存重新分配的频率,vector 通常会预留比实际需要更多的内存。

4.6 使用场景

  • list

    • 适用于频繁在中间插入或删除元素的场景。
    • 不需要随机访问,只需顺序访问的场景。
    • 内存管理分散,不需要连续内存的场景。
  • vector

    • 适用于需要频繁随机访问元素的场景。
    • 在末尾进行插入或删除操作较多的场景。
    • 数据量较小,不需要频繁调整大小的场景。

示例代码比较

下面的代码展示了 listvector 在插入、删除和访问操作上的不同:

#include <iostream>
#include <list>
#include <vector>int main() {// list 示例std::list<int> myList = {10, 20, 30, 40, 50};auto it = myList.begin();++it;myList.insert(it, 15); // 在第二个位置插入15myList.erase(it);      // 删除第二个位置元素std::cout << "List elements: ";for (auto elem : myList) {std::cout << elem << " ";}std::cout << std::endl;// vector 示例std::vector<int> myVector = {10, 20, 30, 40, 50};myVector.insert(myVector.begin() + 1, 15); // 在第二个位置插入15myVector.erase(myVector.begin() + 1);      // 删除第二个位置元素std::cout << "Vector elements: ";for (auto elem : myVector) {std::cout << elem << " ";}std::cout << std::endl;// 访问元素std::cout << "List element at position 2: " << *std::next(myList.begin(), 2) << std::endl;std::cout << "Vector element at position 2: " << myVector[2] << std::endl;return 0;
}
// 输出:
// List elements: 10 15 30 40 50
// Vector elements: 10 20 30 40 50
// List element at position 2: 30
// Vector element at position 2: 30

5.结语

通过这篇文章,我们深入探讨了 C++ 标准模板库中的 list 类,包括其构造函数、迭代器、容量获取与元素访问函数、修改函数及其他功能函数。我们还实现了一个简单的 list 模拟,并详细比较了 listvector 的异同。希望通过这些内容,读者能够更全面地理解 list 的使用场景和实现原理,从而在实际编程中做出更加合理的数据结构选择,提高代码的效率和性能。

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

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

相关文章

PyMySQL连接池

背景 在用python写后端服务时候&#xff0c;需要与mysql数据库进行一些数据查询或者插入更新等操作。启动服务后接口运行一切正常&#xff0c; 隔了第二天去看服务日志就会报错&#xff0c;问题如下&#xff1a; pymysql.err.OperationalError: (2006, "MySQL server ha…

JavaScript-内存分配

内存空间 内存分为栈和堆 栈&#xff1a;由操作系统自动释放存放的变量值和函数值等。简单数据类型存放在栈中 栈会由低到高先入后出 堆&#xff1a;存储引用类型 &#xff08;对象&#xff09; 对象会先将数据存放在堆里面&#xff0c;堆的地址放在栈里面

数字孪生智慧车站:全方位可视化管理平台

运用图扑数字孪生技术&#xff0c;智慧车站可视化管理平台实时模拟并监控车站运行状态&#xff0c;通过整合即时数据与历史数据&#xff0c;提供精准分析和预测。该平台支持乘客流量管理、设备运行监控、安全预警等多项功能&#xff0c;提高车站运营效率与安全性。直观的可视化…

这个橙子真的香!老司机徒手把玩香橙派Kunpeng Pro事后回忆录

说&#xff01;你是哪个门派&#xff1f; 香橙&#xff0c;芸香科柑橘属小乔木。枝通常有粗长刺&#xff0c;新梢及嫩叶柄常被疏短毛。叶厚纸质&#xff0c;翼叶倒卵状椭圆形&#xff0c;顶部圆或钝。。。 咦&#xff1f;小李&#xff1f;我们不是搞IT的嘛&#xff0c;怎么会有…

(函数)求一元二次方程的根(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> # include <math.h>//声明函数&#xff1b; //判断条件等于0时&#xff1b; void zeor(double a, double b);//判断条件大于0时&#xff1b; void bigzeo…

浅谈 parallelStream和Stream 源码及其应用场景

上篇讲述了list.forEach()和list.stream().forEach() 异同点 谈到了并行流的概念&#xff0c;本篇则从源码出发&#xff0c;了解一下其原理。 一、流的初始操作流程 jdk8中 将Collection中加入了转换流的概念。 default Stream<E> stream() {return StreamSupport.str…

第十三章 进程与线程

第十三章 进程与线程 程序与进程的概念 程序&#xff1a; 英文单词为Program&#xff0c;是指一系列有序指令的集合&#xff0c;使用编程语言所编写&#xff0c;用于实现一定的功能。 进程&#xff1a; 进程则是指启动后的程序&#xff0c;系统会为进程分配内存空间。 函数式…

【PingPong_注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

奶奶也能看懂的耦合协调度分析

不会计算&#xff1f;跟着文献学起来~ 案例数据连接&#xff08;复制链接后粘贴到浏览器中&#xff09;&#xff1a; 耦合协调度数据​spssau.com/spssaudata.html?shareDataF363000CD033FF15E557BB75B9B0D412 假如你有这样一组数据&#xff1a; 如何进行计算分析耦合协调度…

内网安全之证书模版的管理

证书模板 Certificate templates 是 CA 证书颁发机构的一个组成部分&#xff0c;是证书策略中的重要元素&#xff0c;是用于证书注册、使用和管理的一组规则和格式。当 CA 收到对证书的请求时&#xff0c;必须对该请求应用一组规则和设置&#xff0c;以执行所请求的功能&#x…

前端知识1-4:性能优化进阶

性能优化进阶 Navigation Timing API navigationStart / end 表示从上一个文档卸载结束时 > 如果没有上一个文档&#xff0c;这个值和fetchStart相等 unloadEventStart / end 标识前一个网页unload的时间点 redirectStart / end 第一个http重定向发生和结束的时间 fetch…

Hadoop3:HDFS中DataNode与NameNode的工作流程

一、DataNode中的数据情况 数据位置 /opt/module/hadoop-3.1.3/data/dfs/data/current/BP-823420375-192.168.31.102-1714395693863/current/finalized/subdir0/subdir0块信息 每个块信息&#xff0c;由两个文件保存&#xff0c;xxx.meta保存的是数据长度、校验和、时间戳&am…

芝加哥大学最新研究:GPT-4与财务预测,重塑财务分析的未来

最近&#xff0c;芝加哥大学的研究团队发表了一篇突破性的研究&#xff0c;展示了大型语言模型&#xff08;LLM&#xff09;&#xff0c;特别是 OpenAI 开发的 GPT-4&#xff0c;如何在财务报表分析领域取得了与专业分析师相匹配甚至超越的表现。这项研究不仅凸显了人工智能在高…

GDPU Java 天码行空13

&#xff08;一&#xff09;实验目的 1、掌握JAVA中与网络程序开发相关的知识点&#xff1b; 2、理解并掌握网络编程开发思想及方法&#xff1b; 3、熟悉项目开发的分包方法和依据&#xff1b; 4、实现聊天室中客服端和服务器端的实现方法&#xff1b; 5、熟悉多线程程序开发方…

Kinetix5700罗克韦尔AB伺服驱动器维修2198-D020-ERS3

Allen-Bradley罗克韦尔运动控制/伺服驱动器维修Kinetix 5700/Kinetix 6000/Kinetix 5500等系列电机驱动器/运动控制系统维修。 AB驱动器的控制接口有两种类型&#xff1a; 类型1&#xff1a;脉冲接口 类型2&#xff1a;模拟量接口 大部分小型PLC和伺服驱动器的链接方式都是开…

通过vlan实现同一网段下的网络隔离

现有两个电脑通过交换机直接连接在一起 pc1&#xff1a; pc2&#xff1a; 正常状态下是可以ping成功的 现在先进入交换机命令行界面&#xff0c;创建两个vlan <Huawei>system-view Enter system view, return user view with CtrlZ. [Huawei]vlan 10 [Huawei-vlan10…

2024年西安交通大学程序设计校赛

A题 签到题 代码如下 //A #include<iostream> #include<algorithm> #define int long long #define endl \n #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); using namespace std; signed main() {IOSint a,b,c,d;cin>>a>>b>>c…

二叉树介绍及堆

文章目录 树 概念及结构 二叉树 概念及结构 特殊的二叉树 完全二叉树 满二叉树 性质 储存 顺序存储 链式储存 堆 概念及结构 小堆 大堆 建堆 向上调整建堆 向下调整建堆 TOPK问题 法一&#xff1a; 法二&#xff1a; 树 概念及结构 树是一种非线性的数据…

解决word里加入mathtype公式后行间距变大

1.布局>页面设置>文档网格&#xff0c;网格栏选为无网格 2.固定间距

探索标准差与方差的奥秘

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、标准差与方差的基础理解 代码案例 二、标准差与方差的计算方法 方差的计算 标准差的…