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

【C++】10. list

文章目录

  • 一、list的介绍及使用
    • 1、list的介绍
    • 2、list对象的常用接口
      • 1)constructor
      • 2)Iterators
      • 3)Capacity
      • 4)Element access
      • 4)Modifiers
      • 5)Operations
      • 6)list的排序问题
      • 7)迭代器失效问题
  • 二、list的模拟实现
    • 1、模拟实现list
      • 1)list.h
      • 2)Test.cpp
    • 2、list与vector的对比

一、list的介绍及使用

1、list的介绍

什么是list?根据之前学过的数据结构知识,在这里可以将list认为是带头双向循环链表。
list的文档介绍
使用STL的三个境界:能用,明理,能扩展 ,下面学习list,我们也是按照这个方法去学习。

2、list对象的常用接口

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

1)constructor

构造函数(constructor)接口说明
list (size_type n, const value_type& val =value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list

示例如下:

#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt1;//默认构造list<int> lt2(10, 1);//构造n个vallist<int> lt3(5, 1);lt3=lt2;//赋值list<int> lt4(++lt2.begin(), --lt2.end());//迭代器区间构造//遍历list<int>::iterator it = lt2.begin();while (it != lt2.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt4){cout << e << " ";}cout << endl;return 0;
}

运行结果:
在这里插入图片描述

除此之外,C++11以后还支持一种初始化列表的方式来进行初始化,使用起来更加的简便,同样这种初始化生成的对象也支持各种接口。
如下:

#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt{ 1,2,3,4,5 };lt.push_back(6);for (auto e : lt){cout << e << " ";}cout << endl;return 0;
}

运行结果:
在这里插入图片描述

2)Iterators

这里可以暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

函数声明接口说明
begin +end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin+ rend返回第一个元素的reverse_iterator(即end位置)+返回最后一个元素下一个位置的reverse_iterator(即begin位置)

结构如图所示:

示例如下:

#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);//正向遍历auto it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;//反向遍历auto rit = lt.rbegin();while (rit != lt.rend()){cout << *rit << " ";++rit;}cout << endl;return 0;
}

运行结果:
在这里插入图片描述

注意:

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

3)Capacity

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

示例如下:

#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt(5, 1);cout << lt.empty() << endl;cout << lt.size() << endl;return 0;
}

运行结果:
在这里插入图片描述

4)Element access

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

示例如下:

#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);cout << lt.front() << endl;cout << lt.back() << endl;return 0;
}

运行结果:
在这里插入图片描述

4)Modifiers

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在 iterator position 位置中插入值为val的元素
erase删除 iterator position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

示例如下:

#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt;lt.push_front(1);//头插lt.push_front(2);lt.push_front(3);lt.push_front(4);lt.push_back(1);//尾插lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.pop_front();//头删lt.pop_back();//尾删list<int> lt1(5, 1);//第3个数据前插入数据auto it = lt1.begin();int k = 2;while (k--){it++;}lt1.insert(it, 10);//删除指定的数据int x = 0;cin >> x;it = find(lt.begin(), lt.end(), x);if (it != lt.end()){lt.erase(it);}list<int> lt2(5, 1);list<int> lt3(5, 2);lt2.swap(lt3);//交换数据list<int> lt4(5, 1);lt4.clear();//清空数据return 0;
}

5)Operations

函数声明接口说明
splice将元素从一个链表转移到另一个链表
remove删除具有特定值的元素
unique删除重复值
merge合并已排序的链表
sort对容器中的元素进行排序

示例如下:

void test1()
{list<int> lt{1, 8, 6, 2, 5, 4};//升序lt.sort();for (auto e : lt){cout << e << " ";}cout << endl;//降序lt.sort(greater<int>());for (auto e : lt){cout << e << " ";}cout << endl;list<double> first{ 3.1, 2,2, 2.9 }, second{ 3.7, 7.1, 1.4 };first.sort();second.sort();first.merge(second);//合并链表for (auto e : first){cout << e << " ";}cout << endl;for (auto e : second){cout << e << " ";}cout << endl;
}

运行结果:
在这里插入图片描述

void test2()
{list<int> lt{ 1,20,3,5,5,4,5,6 };//先排序再去重效率更高lt.sort();lt.unique();//去重lt.remove(20);//删除指定值for (auto e : lt){cout << e << " ";}cout << endl;
}

运行结果:
在这里插入图片描述

void test3()
{list<int> mylist1{ 1,2,3,4 }, mylist2{ 10,20,30 };mylist1.splice(++mylist1.begin(), mylist2);//转移链表for (auto e : mylist1){cout << e << " ";}cout << endl;//调整当前链表节点的顺序list<int> lt{ 1,2,3,4,5,6 };for (auto e : lt){cout << e << " ";}cout << endl;int x = 0;cin >> x;auto it = find(lt.begin(), lt.end(), x);if (it != lt.end()){lt.splice(lt.begin(), lt, it, lt.end());// it~lt.end()是要转移的节点}for (auto e : lt){cout << e << " ";}cout << endl;
}

运行结果:
在这里插入图片描述

6)list的排序问题

对于链表的排序而言,若直接对链表排序,那么效率是很低的,一般都是将其拷贝给vector进行排序,排序好后再将其拷贝回链表。我们来测试一下这种排序方式和直接对链表排序所消耗的时间对比。

void test_op2()
{srand(time(0));const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; i++){auto e = rand() + i;lt1.push_back(e);lt2.push_back(e);}int begin1 = clock();//拷贝给vectorvector<int> v(lt2.begin(), lt2.end());//对vector排序sort(v.begin(), v.end());//拷贝回lt2lt2.assign(v.begin(), v.end());int end1 = clock();int begin2 = clock();//直接对链表lt1排序lt1.sort();int end2 = clock();printf("list copy vector sort copy list sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}

运行结果:

可以看到采用第一种排序的方式效率大大提高了。

7)迭代器失效问题

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

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

二、list的模拟实现

1、模拟实现list

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

1)list.h

#pragma once
#include<assert.h>
#include<iostream>
#include<algorithm>
#include<list>
#include<string>
#include<vector>
using namespace std;namespace zsy
{//节点类template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()):_data(data),_next(nullptr),_prev(nullptr){}};//迭代器类  ->迭代器的接口template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;//Ref:引用类型 T&/const T&; Ptr:指针类型 T*/const T*Node* _node;list_iterator(Node* node):_node(node){}Ref& operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){_node = _node->_next;//++return *this;//返回++后的迭代器}Self& operator--(){_node = _node->_prev;return *this;}//后置++ Self operator++(int){Self tmp(*this);//记录++前_node = _node->_next;//++return tmp;//返回++前的迭代器}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._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;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){//空初始化(只有哨兵位)_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}//构造:初始化列表list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}//拷贝构造 lt2(lt1)list(const list<T>& 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);}//赋值 l3=l2(两个已存在的对象)list<T>& operator=(list<T> lt){swap(lt);return *this;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}void push_back(const T& x){/* Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;++_size; */insert(end(), x);}void push_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return next;}void pop_back(){erase(end());}void pop_front(){erase(begin());}size_t size() const{return _size;}bool empty() const{return _size == 0;}private:Node* _head;//哨兵位size_t _size;};struct AA{int _a1 = 1;int _a2 = 2;};//打印容器template<class Container>void print_container(const Container& con){//typename Container::const_iterator it = con.begin();auto it = con.begin();while (it != con.end()){cout << *it << " ";++it;}cout << endl;}}

2)Test.cpp

#include"list.h"namespace zsy
{void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.insert(lt.end(), 5);lt.erase(lt.begin());auto it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;cout << lt.size() << endl;cout << lt.empty() << endl;}void test2(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);auto it1 = ++lt1.begin();auto it2 = lt1.begin()++;auto it3 = --lt1.end();auto it4 = lt1.end()--;cout << *it1 << endl;cout << *it2 << endl;cout << *it3 << endl;cout << *it4 << endl;print_container(lt1);}void test3(){list<AA> lta;//结构体类型AAlta.push_back(AA());lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator ita = lta.begin();while (ita != lta.end()){cout << (*ita)._a1 << " " << (*ita)._a2 << endl;//-> (为了可读性省略了一个->)//cout << ita.operator->()->_a1 << " " << ita.operator->()->_a2 << endl;cout << ita->_a1 << " " << ita->_a2 << endl;++ita;}cout << endl;}void test4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);//insert以后迭代器不失效list<int>::iterator it = lt.begin();lt.insert(it, 10);*it += 20;print_container(lt);//erase以后迭代器失效//删除偶数it = lt.begin();while (it != lt.end()){if (*it % 2 == 0){it = lt.erase(it);}else{++it;}}print_container(lt);}void test5(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);//拷贝构造print_container(lt1);print_container(lt2);list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt3 = lt1;//赋值print_container(lt1);print_container(lt3);}void test6(){//初始化列表list<int> lt1({ 1,2,3,4 });//直接构造list<int> lt2{ 5,6,7,8 };//隐式类型转换print_container(lt1);print_container(lt2);}
}int main()
{//zsy::test1();//zsy::test2();//zsy::test3();//zsy::test4();//zsy::test5();zsy::test6();return 0;
}

2、list与vector的对比

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

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问
http://www.dtcms.com/a/349007.html

相关文章:

  • BUCK电路的环路补偿
  • JDK版本报错
  • 在PC机上使用虚幻引擎5(UE5)开发第一款游戏的完整入门指南
  • 门面设计模式
  • Python核心技术开发指南(012)——浮点数
  • 如何捕获组件的异常情况
  • 一个简单的html音乐播放器
  • 阿里发布Qoder:颠覆软件开发体验的AI编程平台
  • 前端应用容器化,基于Docker多阶段构建的最佳实践
  • More Effective C++ 条款05: 谨慎定义类型转换函数
  • Java 泛型的“擦除”与“保留”:一次完整的编译与反编译实验
  • Docker中Dify镜像由Windows系统迁移到Linux系统的方法
  • 【计算机408数据结构】第二章:基本数据结构之线性表
  • Leetcode 3660. Jump Game IX
  • 新的 Gmail 网络钓鱼攻击利用 AI 提示注入来逃避检测
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十四)垂直滚动条
  • 【URP】[投影Projector]解析与应用
  • 【cs336学习笔记】[第6课]内核优化与Triton框架应用
  • 如何在算力时代乘风破浪?
  • 深度学习中的模型量化及实现示例
  • 【RAGFlow代码详解-4】数据存储层
  • MySQL学习记录-基础知识及SQL语句
  • 【零代码】OpenCV C# 快速开发框架演示
  • 在 Docker 容器中查看 Python 版本
  • C语言第十二章自定义类型:结构体
  • LangChain RAG系统开发基础学习之文档切分
  • Python核心技术开发指南(016)——表达式
  • 多线程——认识Thread类和创建线程
  • 【记录】Docker|Docker镜像拉取超时的问题、推荐的解决办法及安全校验
  • FPGA时序分析(四)