当前位置: 首页 > news >正文

C++ -- STL-- List

       //////     欢迎来到 aramae 的博客,愿 Bug 远离,好运常伴!  //////

博主的Gitee地址:阿拉美 (aramae) - Gitee.com

时代不会辜负长期主义者,愿每一个努力的人都能达到理想的彼岸。

  • 1. list的介绍及使用
  • 2. list的深度剖析及模拟实现
  • 3. listvector的对比   
引言: 本章学习STL中的List容器,包括 list 的介绍及使用,深度剖析及模拟实现并补充和 vector的差异。

1. list的介绍及使用

1.1 list的介绍

  • 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  • 2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
  • 3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  • 4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。
  • 5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)

https://cplusplus.com/reference/list/list/?kw=listhttps://cplusplus.com/reference/list/list/?kw=list


1.2 list的使用 

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展 的能力。以下为list中一些常见的重要接口

1.2.1 list的构造

 list的构造使用代码演示:
#include <iostream>
#include <list>
#include <vector>
using namespace std;// 打印 list 内容的函数
template <typename T>
void printList(const list<T>& lst, const string& desc) {cout << desc << ": ";for (const auto& elem : lst) {cout << elem << " ";}cout << endl;
}int main() {// 1. 默认构造函数 list()list<int> lst1;lst1.push_back(1);lst1.push_back(2);printList(lst1, "1. 默认构造后添加元素");// 2. 构造包含 n 个 val 的 list:list (size_type n, const value_type& val)list<int> lst2(5, 10);  // 构造包含 5 个 10 的 listprintList(lst2, "2. 构造 5 个 10 的 list");// 3. 拷贝构造函数 list (const list& x)list<int> lst3(lst2);  // 用 lst2 拷贝构造 lst3printList(lst3, "3. 拷贝 lst2 构造 lst3");// 4. 范围构造函数 list (InputIterator first, InputIterator last)vector<int> vec = {100, 200, 300};list<int> lst4(vec.begin(), vec.end());  // 用 vector 的 [begin, end) 范围构造printList(lst4, "4. 用 vector 范围构造 lst4");// 也可以用 list 自身的迭代器范围构造list<int> lst5(lst4.begin(), lst4.end());printList(lst5, "4. 用 lst4 范围构造 lst5");return 0;
}

1.2.2 list iterator的使用

 将迭代器理解成一个指针,该指针指向list中的某个节点

【注意】
1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

 list的迭代器使用代码演示:
#include <iostream>
#include <list>
#include <string>
using namespace std;// 打印 list 内容(普通迭代器遍历)
template <typename T>
void printListNormal(const list<T>& lst, const string& desc) {cout << desc << ": ";// begin 返回指向第一个元素的迭代器,end 返回指向最后一个元素下一个位置的迭代器for (auto it = lst.begin(); it != lst.end(); ++it) {cout << *it << " ";}cout << endl;
}// 打印 list 内容(反向迭代器遍历)
template <typename T>
void printListReverse(const list<T>& lst, const string& desc) {cout << desc << ": ";// rbegin 返回指向最后一个元素的反向迭代器(对应普通迭代器的 end 前一个位置)// rend 返回指向第一个元素前一个位置的反向迭代器(对应普通迭代器的 begin 位置)for (auto it = lst.rbegin(); it != lst.rend(); ++it) {cout << *it << " ";}cout << endl;
}// 演示普通迭代器修改元素(需要非 const list)
template <typename T>
void modifyWithIterator(list<T>& lst) {cout << "尝试用普通迭代器修改元素:" << endl;for (auto it = lst.begin(); it != lst.end(); ++it) {// 通过解引用迭代器修改元素值(需确保 list 非 const)*it *= 2;  }
}int main() {list<int> myList = {1, 2, 3, 4, 5};// 1. 普通迭代器遍历(begin + end)printListNormal(myList, "普通迭代器遍历初始 list");// 2. 反向迭代器遍历(rbegin + rend)printListReverse(myList, "反向迭代器遍历初始 list");// 3. 用普通迭代器修改元素modifyWithIterator(myList);printListNormal(myList, "修改后的 list(普通迭代器遍历)");printListReverse(myList, "修改后的 list(反向迭代器遍历)");// 4. 结合 const list 演示(只能读,不能通过迭代器修改)const list<int> constList = {10, 20, 30};cout << "const list 遍历(普通迭代器):";for (auto it = constList.begin(); it != constList.end(); ++it) {cout << *it << " ";}cout << endl;return 0;
}

1.2.3 list capacity

  • empty:判断 list 是否为空,返回 bool 类型结果(空为 true,非空为 false )。
  • size:获取 list 中有效元素(节点)的数量,返回 size_type 类型值 。
代码演示:
#include <iostream>
#include <list>int main() {// 创建一个空的 liststd::list<int> myList;// 使用 empty() 检查是否为空std::cout << "myList 是否为空? " << (myList.empty() ? "是" : "否") << std::endl;// 使用 size() 获取元素数量std::cout << "myList 的元素数量: " << myList.size() << std::endl;// 添加元素myList.push_back(10);myList.push_back(20);myList.push_back(30);// 再次检查状态std::cout << "\n添加元素后:" << std::endl;std::cout << "myList 是否为空? " << (myList.empty() ? "是" : "否") << std::endl;std::cout << "myList 的元素数量: " << myList.size() << std::endl;// 清空 listmyList.clear();// 最后一次检查std::cout << "\n清空后:" << std::endl;std::cout << "myList 是否为空? " << (myList.empty() ? "是" : "否") << std::endl;std::cout << "myList 的元素数量: " << myList.size() << std::endl;return 0;
}

 1.2.4 list element access

  • front():返回第一个节点的值的引用,可直接读取或修改(需确保 list 非空,否则行为未定义)。
  • back():返回最后一个节点的值的引用,同理需确保 list 非空。
 代码演示:
#include <iostream>
#include <list>
using namespace std;int main() {// 1. 初始化一个非空 listlist<int> myList = {10, 20, 30};// 2. front:访问并输出第一个节点的值cout << "第一个节点的值(front):" << myList.front() << endl;// 3. back:访问并输出最后一个节点的值cout << "最后一个节点的值(back):" << myList.back() << endl;// 4. 通过 front/back 修改首尾元素(利用引用特性)myList.front() = 100;  // 修改第一个节点为 100myList.back() = 300;   // 修改最后一个节点为 300cout << "修改后 list 的元素:";for (int num : myList) {cout << num << " ";}cout << endl;// 5. 测试空 list(危险操作,实际开发需避免)list<int> emptyList;// 以下两行代码会导致未定义行为(空容器访问 front/back)// cout << emptyList.front(); // cout << emptyList.back(); // 正确做法:先判断 empty()if (!emptyList.empty()) {cout << emptyList.front();} else {cout << "list 为空,无法访问 front/back" << endl;}return 0;
}

1.2.5 list modifiers

1.push_front / pop_front

  • push_front 在链表头部快速插入元素(双向链表结构保证 O(1) 时间复杂度)。
  • pop_front 删除头部元素,同样是 O(1) 复杂度。

2. push_back / pop_back

  • push_back 在链表尾部插入元素,O(1) 时间。
  • pop_back 删除尾部元素,O(1) 时间。

3.insert

  • 需要传入迭代器指定位置,在该位置前插入新元素。
  • 由于是链表结构,插入操作仅需调整指针,O(1) 时间(找到位置的遍历是 O(n),但插入本身是 O(1))。

4. erase

  • 传入迭代器指定要删除的位置,删除该元素并返回下一个位置的迭代器(示例中简化处理,直接移动迭代器)。
  • 同样利用链表结构,删除操作 O(1) 时间(遍历找位置是 O(n),删除本身 O(1))。

5. swap

  • 交换两个 list 的内部数据,时间复杂度 O(1)(仅交换链表头指针等少量数据)。

6. clear

  • 清空链表所有元素,释放内存,所有迭代器失效。
list的插入和删除使用代码演示 :
#include <iostream>
#include <list>
using namespace std;// 打印 list 内容的函数
template <typename T>
void printList(const list<T>& lst, const string& desc) {cout << desc << ": ";for (const auto& elem : lst) {cout << elem << " ";}cout << endl;
}int main() {list<int> myList = {10, 20, 30};// 1. push_front:在首元素前插入myList.push_front(5);printList(myList, "push_front(5) 后"); // 输出: 5 10 20 30 // 2. pop_front:删除第一个元素myList.pop_front();printList(myList, "pop_front() 后");    // 输出: 10 20 30 // 3. push_back:在尾部插入myList.push_back(40);printList(myList, "push_back(40) 后");  // 输出: 10 20 30 40 // 4. pop_back:删除最后一个元素myList.pop_back();printList(myList, "pop_back() 后");     // 输出: 10 20 30 // 5. insert:在指定位置插入auto it = myList.begin();advance(it, 1); // 迭代器移动到第 2 个元素位置(值为 20)myList.insert(it, 15); printList(myList, "insert 后");         // 输出: 10 15 20 30 // 6. erase:删除指定位置元素it = myList.begin();advance(it, 2); // 迭代器移动到第 3 个元素位置(值为 20)myList.erase(it); printList(myList, "erase 后");          // 输出: 10 15 30 // 7. swap:交换两个 list 的元素list<int> anotherList = {100, 200};myList.swap(anotherList);printList(myList, "swap 后 myList");    // 输出: 100 200 printList(anotherList, "swap 后 anotherList"); // 输出: 10 15 30 // 8. clear:清空 listmyList.clear();printList(myList, "clear 后 myList");   // 输出: (空)return 0;
}

1.2.6 list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

下面是几个 std::list 迭代器失效的典型例子及分析:

例子 1:删除操作导致迭代器失效

#include <iostream>
#include <list>int main() {std::list<int> myList = {1, 2, 3, 4, 5};auto it = myList.begin();std::advance(it, 2); // 让 it 指向值为 3 的元素// 错误做法:删除元素后继续使用原迭代器myList.erase(it); // 下面这行代码会导致未定义行为,因为 it 已经失效// std::cout << *it << std::endl; // 正确做法:使用 erase 的返回值更新迭代器auto itCorrect = myList.begin();std::advance(itCorrect, 2); itCorrect = myList.erase(itCorrect);if (itCorrect != myList.end()) {std::cout << "正确处理后下一个元素的值: " << *itCorrect << std::endl;}return 0;
}

解释:在 std::list 中调用 erase 删除元素时,指向被删除元素的迭代器会失效。正确的做法是使用 erase 函数返回的迭代器(指向下一个有效元素)来更新原来的迭代器。

例子 2:清空容器导致迭代器失

#include <iostream>
#include <list>int main() {std::list<int> myList = {1, 2, 3};auto it = myList.begin();myList.clear();// 以下操作会导致未定义行为,因为 myList 已经清空,所有迭代器都失效了// std::cout << *it << std::endl; return 0;
}

解释:当调用 clear 函数清空 std::list 时,所有指向该容器元素的迭代器都会失效,此时再使用这些迭代器访问元素就会产生未定义行为。

例子 3:交换容器导致迭代器失效(相对原容器)

#include <iostream>
#include <list>int main() {std::list<int> list1 = {1, 2, 3};std::list<int> list2 = {4, 5, 6};auto it = list1.begin();list1.swap(list2);// 此时 it 仍然指向原来 list1 中的某个元素,但该元素现在属于 list2 了// 以下操作虽然不会崩溃,但不符合预期,因为 it 不再是 list1 的有效迭代器// std::cout << *it << std::endl; return 0;
}

解释:调用 swap 函数交换两个 std::list 时,迭代器所指向的元素虽然没有被销毁,但所属的容器发生了变化,对于原容器来说,原来的迭代器就失效了。

通过这些例子可以看出,在对 std::list 进行删除、清空、交换等操作时,需要特别注意迭代器的有效性,以避免出现未定义行为。

void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值l.erase(it); ++it;}
}
// 改正
void TestListIterator()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++); // it = l.erase(it);}
}

2. list的模拟实现

2.1 模拟实现list

要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,通过上面的学习,这些内容已基本掌握,现在我们来模拟实现list。


list.h
#include<assert.h>namespace aramae
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;list_node(const T& val = T()):_next(nullptr), _prev(nullptr), _val(val){}};// typedef __list_iterator<T, T&, T*> iterator;// typedef __list_iterator<T, const T&, const T*> const_iterator;template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){return &_node->_val;}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>struct __list_const_iterator{typedef list_node<T> Node;Node* _node;__list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_val;}__list_const_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_const_iterator<T> operator++(int){__list_const_iterator<T> tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const __list_const_iterator<T>& it){return _node != it._node;}bool operator==(const __list_const_iterator<T>& it){return _node == it._node;}};*/template<class T>class list{typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;// 如何设计const迭代器?iterator begin(){//return _head->_next;return iterator(_head->_next);}iterator end(){return _head;//return iterator(_head);}const_iterator begin() const{//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{return _head;//return const_iterator(_head);}void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}list(){empty_init();}// lt2(lt1)list(const list<T>& lt)//list(const list& lt){empty_init();for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt)//list& operator=(list lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}// pos位置之前插入iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;}size_t size(){/*size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;*/return _size;}private:Node* _head;size_t _size;};void Print(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){// (*it) += 1;cout << *it << " ";++it;}cout << endl;}void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){(*it) += 1;cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;Print(lt);}struct A{A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;};void test_list2(){list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;++it;}cout << endl;}void test_list3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_front(5);lt.push_front(6);lt.push_front(7);lt.push_front(8);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt){cout << e << " ";}cout << endl;lt.clear();lt.push_back(10);lt.push_back(20);lt.push_back(30);lt.push_back(40);for (auto e : lt){cout << e << " ";}cout << endl;cout << lt.size() << endl;}void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt1(lt);for (auto e : lt1){cout << e << " ";}cout << endl;list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);lt2.push_back(40);for (auto e : lt2){cout << e << " ";}cout << endl;lt1 = lt2;for (auto e : lt1){cout << e << " ";}cout << endl;}
}

2.2 list的反向迭代器

通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。

template<class Iterator>
class ReverseListIterator
{// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态成员变量// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;
public://////////////////////////////////////////////// 构造ReverseListIterator(Iterator it): _it(it){}//////////////////////////////////////////////// 具有指针类似行为Ref operator*(){Iterator temp(_it);--temp;return *temp;}Ptr operator->(){ return &(operator*());}//////////////////////////////////////////////// 迭代器支持移动Self& operator++(){--_it;return *this;}Self operator++(int){Self temp(*this);--_it;return temp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self temp(*this);++_it;return temp;}//////////////////////////////////////////////// 迭代器支持比较bool operator!=(const Self& l)const{ return _it != l._it;}bool operator==(const Self& l)const{ return _it != l._it;}Iterator _it;
};

3. list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不 同,其主要不同如下:

#include <iostream>
#include <list>
#include <vector>int main() {// list 示例std::list<int> myList;myList.push_back(1);  // 在尾部插入元素myList.push_front(0); // 在头部插入元素// 遍历 liststd::cout << "List elements: ";for (const auto& element : myList) {std::cout << element << " ";}std::cout << std::endl;// vector 示例std::vector<int> myVector;myVector.push_back(10); // 在尾部插入元素myVector.push_back(20);// 通过下标访问 vector 元素std::cout << "Vector element at index 0: " << myVector[0] << std::endl;// 遍历 vectorstd::cout << "Vector elements: ";for (const auto& element : myVector) {std::cout << element << " ";}std::cout << std::endl;return 0;
}


结语:感谢相遇

/// 高山仰止,景行行止。虽不能至,心向往之 ///

http://www.dtcms.com/a/280061.html

相关文章:

  • 分布式通信框架 - JGroups
  • 从零开始的云计算生活——第三十二天,四面楚歌,HAProxy负载均衡
  • 数据怎么分层?从ODS、DW、ADS三大层一一拆解!
  • 智慧园区:激活城市活力的数字化引擎
  • 【colab 使用uv创建一个新的python版本运行】
  • mac上的app如何自动分类
  • 22-C#的委托简单使用-2
  • 自增主键为什么不是连续的?
  • 基于多智能体强化学习的医疗检索增强生成系统研究—MMOA-RAG架构设计与实现
  • Uboot源码超详细分析(2)
  • 力扣25.7.15每日一题——有效单词
  • 对于编写PID过程中的问题
  • TCP可靠性设计的核心机制与底层逻辑
  • TDengine GREATEST 和 LEAST 函数用户手册
  • 26.将 Python 列表拆分为多个小块
  • SSM框架学习DI入门——day2
  • 跨平台移动开发技术深度分析:uni-app、React Native与Flutter的迁移成本、性能、场景与前景
  • IOS 18下openURL 失效问题
  • Flutter瀑布流布局深度实践:打造高性能动态图片墙
  • LVS(Linux Virtual Server)详细笔记(理论篇)
  • JavaScript进阶篇——第三章 箭头函数核心
  • 【问题排查流程总结】tmd2635模块开发中断异常,排查心得
  • python技巧:使用pyvisa控制仪器;安装NI-VISA等visa库;导入pyvisa并创建资源管理器;打开和使用仪器
  • 【 Cache 写策略学习笔记】
  • 编程项目选择思考点以及项目包装的关键点
  • linux系统------LVS+KeepAlived+Nginx高可用方案
  • 优雅的Java:01.数据更新如何更优雅
  • Rocky Linux 9 源码包安装php8
  • 基于按键开源MultiButton框架深入理解代码框架(一)(指针的深入理解与应用)
  • 开源AI Agent开发平台Dify源码剖析系列(二)