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

《C++ list详解》

目录

节点篇

迭代器篇

链表篇

构造函数

拷贝构造函数

赋值=重载

迭代器

析构函数

插入和删除

补充篇

迭代器失效

节点中的数据自定义类型的情况


节点篇

链表中的节点包含储存的数据前一个节点的指针后一个节点的指针。在实现节点时还要将其初始化。那么这里我们不知道模板参数T具体是什么类型,就给了一个缺省参数,使用T的构造函数初始化生成一个匿名对象作为val的默认值,使用const T&延长了匿名对象的生命周期。

特别注意一点,初始化列表的写法,第一行的:别写成了;。

//T是节点中存储的数据类型template <class T>//节点结构struct List_Node{//节点中包含的元素List_Node* _next;List_Node* _prev;T _data;//构造函数初始化节点//初始化列表:别写成;了!!!!!!!!!!!!!!!List_Node(const T& val = T()): _next(nullptr), _prev(nullptr), _data(val){}};

迭代器篇

链表中的迭代器与vector中的有所不同。

后者的迭代器只是一个单纯的指向数组内容的指针,由于数组的空间是连续的,所以可以满足迭代器的++,--,*等需求,但是这里链表的节点空间不是连续的,无法满足迭代器的基本需求,所以不能将迭代器定义为单纯的指向节点内容的指针。

于是聪明的人们用一个结构体模板来封装迭代器,并在结构体里面重载运算符,来实现迭代器的基本操作。

首先我们需要一个成员来表示此时指向的节点的指针,并用这个节点的指针来初始化该成员,这个成员的类型肯定也是节点的指针。

接下来我们就要完成一些符号的重载,来满足迭代器的操作需求。

还有一个问题就是不仅仅有普通迭代器iterator还要有const_iterator,为了简化代码,在实现时不实现两个模板结构体,就多使用了两个模板参数Ref和Ptr

比如说在重载*时,普通情况下我们拿到的是T&就是节点储存的数据的引用(为什么不直接返回T?原因是我们要修改内容,而返回T拿到的只是一个临时副本,无法修改),但是有时我们不希望他被修改就需要返回const T&,所以为了方便就使用Ref来替代,使用Ptr来替代也是同理。

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->_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& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}};

链表篇

链表的成员变量主要包含头节点的指针节点个数

构造函数

构造函数的主要目的就是创建一个头节点出来,为了简化实现不同构造函数时的代码,将构造头节点的主要过程封装在函数empty_init()中。

默认构造函数里面会new一个node大小的空间,并会调用node的默认构造函数把这个节点初始化,同时返回这个空间的地址给_head,然后再改变_next和_prev的指向,并将_size置为0。

使用花括号{}构造的构造函数List(std::initializer_list<T> il),内部同样会使用empty_init(),然后再将花括号{}中的内容尾插过来。

        void empty_init(){//初始化头节点_head = new node;_head->_next = _head;_head->_prev = _head;_size = 0;}List(){empty_init();}List(std::initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}

拷贝构造函数

1.调用empty_init()初始化头节点。

2.尾插链表内容。

        List(List<T>& lt){empty_init();for (auto& t : lt){push_back(t);}}

赋值=重载

和vector中的赋值重载类似,需要实现一个swap函数来交换链表中的内容。

当lt2给lt1赋值,首先会调用拷贝构造将lt2的内容拷贝给lt,将lt1的内容和lt交换,出了作用域lt就会销毁机lt1之前的空间就被销毁,而此时的lt1指向的就是拷贝构造出来的lt指向的空间。

		void swap(List<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt1 = lt2List<T>& operator=(List<T> lt){swap(lt);return (*this);}

迭代器

在链表中需要提供迭代器接口供外部使用。

那么如何在类中使用我们定义的迭代器结构体?如何将迭代器结构体和链表类联系起来呢?

解决方法是使用typedef将模板实例化的复杂类型嵌套定义为类的成员类型在类中有了成员类型就可以使用这个成员类型来定义变量了

		typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;

为了满足使用需求,还需要提供供外部使用的迭代器,分为普通迭代器const迭代器这里返回的是一个调用迭代器类中的构造函数的匿名对象。

本来的过程是,先在函数内构造匿名对象,然后通过拷贝构造函数将这个匿名对象拷贝到返回值位置,最后匿名对象被销毁。

编译器优化后变为,直接在返回值的内存位置构造这个匿名对象,完全跳过了临时对象的创建和拷贝步骤。

		iterator begin(){//返回的是一个匿名对象//调用构造函数return iterator(_head->_next);}const_iterator begin() const{return const_iterator(_head->_next);}iterator end() {return iterator(_head);}const_iterator end() const{return const_iterator(_head);}

析构函数

链表的各个节点不是存在于一块连续的空间, 空间没法一次性释放,必须将节点逐个释放。所以要实现一个函数clear()来释放链表中各个节点(头节点除外,因为clear()不止用在析构函数中)的空间。将头节点空间释放。

		~List(){clear();delete _head;_head = nullptr;_size = 0;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}

插入和删除

插入和删除的函数之间都有很强的复用性。实现节点的插入和删除,只需改变目标节点的_prev和_next即可,插入要创建节点空间,删除要释放节点空间。

		void push_back(const T& val){/*node* newnode = new node(val);node* tail = _head->_prev;tail->_next = newnode;newnode->_next = _head;newnode->_prev = tail;_head->_prev = newnode;_size++;*/insert(end(), val);_size++;}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void insert(iterator pos, const T& val){node* pcur = pos._node;node* prev = pcur->_prev;node* newnode = new node(val);newnode->_prev = prev;newnode->_next = pcur;prev->_next = newnode;pcur->_prev = newnode;_size++;}iterator erase(iterator pos){assert(pos != end());node* pcur = pos._node;node* prev = pcur->_prev; node* next = pcur->_next;prev->_next = next;next->_prev = prev;delete pcur;--_size;//erase删完返回下一个节点的地址return next;}

补充篇

迭代器失效

list的erase在使用过后会将pos指向的那块空间释放并置为nullptr,那删除后就无法执行迭代器的相关操作,所以要在erase函数中返回下一个节点的地址,在循环中连续使用erase时就要重新给迭代器赋值

		while (it != lt.end()){//删除偶数if (*it % 2 == 0){//迭代器失效了,重新赋值it = lt.erase(it);}else{it++;}}

节点中的数据自定义类型的情况

	struct AA{int a1;int a2;AA(int a = 0, int b = 3):a1(a), a2(b){}};void test02(){//当链表节点存储的数据是自定义类型时List<AA> lt;AA aa = { 1,2 };lt.push_back(aa);List<AA>::iterator it = lt.begin();//for (auto& e : lt)//{//	//e开始拿到的是lt的begin()迭代器,而这个迭代器呢指向的内容是一个自定义类型//	//而库中的<<只能打印内置类型,如果要打印就要在AA中重载<<//	cout << e << " ";//}while(it != lt.end()){//(*it)是一个自定义类型AA,自定义类型访问成员变量用.操作符cout << (*it).a1 << " " << (*it).a2 << " ";//自定义类型的指针可以通过->来访问成员函数和成员变量//it是一个迭代器,底层是一个指针,其初始化成lt的第一个节点的指针//那么it的内容就是一个指针,通过->可以访问节点的内容,这个内容就包含了T _data(自定义类型的对象的指针)//这个_data是一个指针变量,即AA的对象aa的地址//所以要访问aa中的a1,a2就要再次使用->//为了易读性,这里省略一个->cout << it->a1 << " " << it->a2 << " ";//本质是cout << it.operator->()->a1 << " " << it.operator->()->a2 << " ";it++;}}}

"希望这篇内容能帮你少走弯路。保持好奇,持续编码!🚀" 

 

  

http://www.xdnf.cn/news/8015.html

相关文章:

  • 金仓数据库主备切换故障解析,一次由相对路径引发的失败与切换流程解读
  • 抛弃传统P2P技术,EasyRTC音视频基于WebRTC打造教育/会议/远程巡检等场景实时通信解决方案
  • 数据库blog5_数据库软件架构介绍(以Mysql为例)
  • 大队项目流程
  • 流程引擎选型指南
  • VSCode推出开源Github Copilot:AI编程新纪元
  • 实战:Dify智能体+Java=自动化运营工具!
  • C++ 中的 **常变量** 与 **宏变量** 比较
  • 【TI MSP430与SD NAND:心电监测的长续航解决方案】
  • Mysql刷题之正则表达式专题
  • 程序编辑器快捷键总结
  • Spring Boot与Disruptor高性能队列整合指南
  • SpringAI 大模型应用开发篇-SpringAI 项目的新手入门知识
  • Vue3实现轮播表(表格滚动)
  • App Builder技术选型指南:从AI编程到小程序容器,外卖App开发实战
  • STM32 CAN CANAerospace
  • 我爱学算法之—— 二分查找(中)
  • MySQL迁移SSL报错
  • web实验(2)
  • Redis 基础知识详解
  • 【笔记】修复AttributeError: ‘super‘ object has no attribute ‘__del__‘
  • 解决Qt Creator在Ubuntu环境下运行Qt程序后,程序中无法输入中文
  • MySQL的可重复读事务隔离级别的实现原理
  • leetcode 438. 找到字符串中所有字母异位词
  • Linux `nc` 命令详细讲解
  • vue3:十四、角色权限管理-表格引入-树形表格
  • Axure系统原型设计列表版方案
  • BERT框架:自然语言处理的革命性突破
  • PostgreSQL 14 pacemaker 高可用集群
  • czml数据以及应用