【C++从0到王者】第十五站:list源码分析及手把手教你写一个list

文章目录

  • 一、list源码分析
    • 1.分析构造函数
    • 2.分析尾插等
  • 二、手把手教你写一个list
    • 1.结点声明
    • 2.list类的成员变量
    • 3.list类的默认构造函数
    • 4.list类的尾插
    • 5.结点的默认构造函数
    • 6.list类的迭代器
    • 7.设计const迭代器
    • 8.list的insert、erase等接口
    • 9.size
    • 10.list的clear
    • 11.list的析构函数
    • 12.list拷贝构造函数
    • 13.赋值运算符重载
    • 14. 测试代码
  • 三、模拟list类的全部代码

一、list源码分析

1.分析构造函数

list的分析与vector的分析思路是一样的,我们一开始最先看到的就是这个结点的结构体,在这里我们可以注意到这是一个双向链表。有一个前驱指针,一个后继指针。然后在有一个存储数据的空间
在这里插入图片描述

其次我们还会注意到,它的迭代器是一个自定义类型,而非原生指针。这与vector是不同的,至于迭代器为什么要这么设计,我们暂时还看不懂,那么我们就往下继续看,先把大结构给研究出来
在这里插入图片描述

那么我们继续找成员变量,在这里我们就找到了成员变量,但是这个类型我们很明显看不懂,于是我们速览定义去查看
在这里插入图片描述

可以看到这个实际上还是一个指针。但是这个指针我们还是看不懂,于是我们继续去速览定义,于是就找到了这里
在这里插入图片描述

这里我们可以注意到这个结点的类型其实就是一个类模板,这个模板正好就是我们一开始就看到的用结构体定义的一个结点。于是我们就清楚了,这个成员变量实际就是一个指针,这个指针指向一个结点。这样一想象,就有点类似于我们在c语言使用链表时候的头节点了。

那么接下来,我们应该分析一下构造函数是什么样子的。
不难注意到,就在成员变量的下方,正好就是一个无参的构造函数。也就是默认构造函数
在这里插入图片描述

但是在这里它又封装了一层函数,于是乎我们继续深入查看

如下所示,我们看到了具体的函数内容,从名字上来看,get_node 不出意外就是开空间的。也就是得到一个结点,然后返回这个结点指针。这样一来,我们的成员变量就获取的实际的一个结点,然后让它的next和prev都指向自己,这样一来这个结点形成一个自循环。现在就能看出来这是一个带头双向循环链表了
在这里插入图片描述

那么这个得到结点的函数内部究竟是什么,我们还可以继续深入查看一下
在这里插入图片描述

如上图所示,这里的allocated设计到空间配置器。这里我们就先不做了解了。后序在详细介绍

我们不妨顺着这个思路往下继续理解,下面刚好有一个put_node,这个函数其实就是释放结点的。

在后面还有这样一个函数,这个函数是creat_node不难理解,这个就是获取一个结点,先给这个结点开空间,然后调用构造函数。等一系列操作。
在这里插入图片描述

既然这里涉及到一个构造,那么我们继续深入,看看这个构造里面是什么东西?这里其实我们还是有点懵的,这里其实就涉及到了C++11的内容了,我们就先不管它了,我们只需要这道能new出来空间就行了
在这里插入图片描述

2.分析尾插等

好了,构造函数分析完了,那么我们继续分析一下push_back系列的插入函数

我们不难注意到,这里的push_front和push_back都是复用了insert接口,那么我们就直接去分析一下insert接口吧
在这里插入图片描述

我们可以分析出来,它调用的是这个函数

在这里插入图片描述

在这里我们也是不难理解的,先创造一个结点,然后进行连接。现在我们也基本读懂了这个大体的框架了

这里还需要注意的是:由于一开始的结点里面的指针都是空指针,导致后面需要经历很多的强制类型转化。其实这里大可不必,我们如果一开始就定义好指针的类型自然是最好的。

二、手把手教你写一个list

1.结点声明

如下所示,是我们的结点的定义,对于这里的定义,我们可能刚看到的时候会比较陌生感,又有一丝熟悉感,这是正常的。多写写就熟悉了。我们现在来深入了解一下这个结点是如何进行声明的,我们这里和c语言的链表采用同样的方法,使用一个结构体,但是这里的结构体已经非比结构体了。因为C++对结构体进行了升级,这里应该是一个类,使用struct的话会是类成员变量默认为公有的成员变量,方便类外的变量可以随时访问。

其次,C++中对结点进行定义的时候可以只写类名,这与class是一样的。注意不要忘记带上模板参数T,因为我们写的结点也只是一个模板。因为类名不是类型,他实例化以后可以有各种各样的结点类型。

	template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;};

2.list类的成员变量

由于我们的结点是一个模板,对于它而言,它的类型就比较繁琐,我们可以在list类里面使用typedef进行一次重命名。然后再私有里面再定义一个指针,这个指针就是一个结点指针。

	template<class T>class list{typedef list_node<T> Node;public:private:Node* _head;};

3.list类的默认构造函数

如下所示,我们定义好了成员变量以后,我们就写一个默认构造函数,当我们对这个链表类进行实例化的时候,自动调用这个默认构造函数,这个默认构造函数会为成员变量的头节点指针分配实际的空间,在new Node空间的时候,会调用它Node(即list_node<T>)类的默认构造函数函数。从而成功的开辟好这块空间。

		list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}

4.list类的尾插

如下所示,我们的尾插逻辑也是比较简单的,先利用我们传过来的val去开辟一个新的结点,注意这里开辟新结点的时候使用new的话可以直接带一个括号去调用它的构造函数

		void push_back(const T& val){Node* newnode = new Node(val);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}

5.结点的默认构造函数

有了上面的分析之后,我们现在缺的就是一个结点的默认构造函数,我们直接给一个缺省值,使用T()就是一个匿名对象来初始化,对于内置类型也是同样适用的。然后我们使用初始化列表即可。

		list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}

6.list类的迭代器

首先我们思考一下,可否像vector一样直接在类里面typedef 一个迭代器?
在这里插入图片描述

答案是显然不可以的,这样大错特错。vector可以这样使用的原因是数组天生就是一个迭代器。解引用后就能找到对应的值。

而对于list,首先它就是不连续的,指针加1后,地址早已不知道跑到哪个结点去了。其次这里仅仅只是结点的指针,解引用后,找到的仅仅只是结点,我们还需要进一步解引用才能找到对应的真正的值。

在这里插入图片描述

总之直接typedef的话,会使得迭代器的++和解引用操作均失效了,这时候我们只能使用运算符重载了。才能解决这个问题。既然要运算符重载,这里我们最好再次封装一个类出来。因为如果不封装一个类出来的话,我们无法完成此处的运算符重载。

如下所示,是我们实现的iterator的类

	template<class T>struct __list_iterator{typedef list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}T& operator*(){return _node->_val;}__list_iterator<T>& operator++(){_node = _node->_next;return *this;}bool operator!=(const __list_iterator<T>& it){return _node != it._node;}};

这个类我们使用了一个结构体去封装,在这个结构体中,我们只有一个成员变量,这个成员变量是结点类的指针,用于指向某一个结点, 在我们一开始定义出迭代器的时候,我们需要先写一个构造函数,用于迭代器的初始化。即需要传一个参数node来控制。

与之对应的,我们在list中就需要写出对应的begin和end函数,来返回迭代器。

		typedef __list_iterator<T> iterator;iterator begin(){//return _head->_next //单参数的构造函数支持隐式类型转换return iterator(_head->_next);}iterator end(){return iterator(_head);}

在这里的迭代器中,首先返回值肯定是iterator,根据我们前面iterator函数的构造函数,我们可以利用匿名对象去构造一个迭代器对象。这里我们正好传一个参数,这个参数根据我们函数的特性去传递,在list类中,它的成员变量就是一个结点类指针,我们可以直接传递该节点的下一个指针,用这个指针刚好就能构造出这个迭代器类型。

现在我们已经获得了这个迭代器,这个迭代器本质就是一个类,而不是指针。由于结点是无法直接正常解引用的,那么我们就需要去在迭代器类中去重载一个*运算符,让他看上去就像一个指针一样,试想一下,我们解引用出来的结果应该是什么呢?其实就是该迭代器类型里面这个唯一的公有成员变量_node所指向的结点中的_val,这个_val就是我们所需要返回的值。这个值的类型是T,由于解引用后我们还可以去修改这个结点里面的值,所以我们还需要传引用返回

除此之外,我们还需要一个++运算符重载,在这个运算符重载中,我们也很明确,我们需要返回的类型就是迭代器类型。那么我们是如何进行++的呢?,因为我们的成员变量就是指向一个结点的指针,所以我们直接让这个结点的指针去往后移动一次即可。由于是前置++,所以我们先移动,在返回*this,因为*this就是我们的该迭代器。这里我们注意,我们可以传引用返回,也可以传值返回。因为无非就是多了一个类。如果传引用返回,另外一个类改变的时候,这个it所指向的内容也将改变。如果传值返回就不会了。

为了方便我们测试,我们还需要再写一个!=的运算符重载,这个运算符重载我们在上面也给出来了,就是简单的进行比较即可。

我们使用如下代码进行测试(这里的测试代码与list在同一个命名空间,所以不需要域作用限定符)

	void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;}

在这里插入图片描述

总而言之,list的迭代器确实比较抽象一些,这里存在三个类之间的各种纠缠。所以导致比较抽象,但是认真分析之后,还是比较容易读懂的。

那么我们再来思考一下,这里是否需要拷贝构造呢?事实上,这里我们可以不需要写拷贝构造函数,因为库里面会默认生成一个浅拷贝,而我们这里也就需要一个浅拷贝。不需要深拷贝。所以我们不需要写。

那么我们在思考一下,我们这里有很多个迭代器,迭代器里面的指针指向同一块空间,那么这里是否会产生崩溃呢?其实是不会的,因为我们就没写析构函数。那么为什么不写析构呢?其实这是因为这个结点就不是我这个迭代器去生成的,迭代器只是拿走了这个结点的地址罢了。要析构也轮不到迭代器去析构,应该让list去析构。迭代器只是借助这个结点去访问容器。迭代器只是为了访问,而不是去管理容器。

我们这里先将迭代器的基本操作写的稍微完善一些

	template<class T>struct __list_iterator{typedef list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}T& operator*(){return _node->_val;}__list_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_iterator<T> operator++(int){__list_iterator<T> tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const __list_iterator<T>& it){return _node != it._node;}bool operator==(const __list_iterator<T>& it){return _node == it._node;}};

7.设计const迭代器

我们先来看看下面这种设计方法是否可行?
在这里插入图片描述

先说结论:不行,为什么不行呢?这是因为const迭代器要求的是迭代器指向的内容不可以被修改,迭代器本身可以被修改。而这里呢?我们的对迭代器类型加上了const,我们的迭代器本身就是一个类,对一个类加上一个const,这是让这个类对象无法被修改啊。里面的成员变量都不可以被修改,而我们迭代器对象里面的指针指向的才是结点指针。我们这样一来,这个迭代器里面的指针无法移动了。也就意味着不满足我们的迭代器本身可以被修改的性质了。它就无法调用前置++,后置++了。

那么我们到底该如何控制指向的数据不可被修改呢?
答案就是在这里加上const
在这里插入图片描述

这样一来返回的就是const引用,自然就无法进行修改内容了。

这样一来,我们就有了一种实现const迭代器的想法了。我们可以拷贝一份原来的迭代器,然后改变一下类名和解引用这个成员函数的返回值即可

	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;}};

即如上代码所示,但是这样设计太过于冗余了。不过这个也是可以正常运行的,我们先补两个接口

		const_iterator begin() const{//return _head->_next //单参数的构造函数支持隐式类型转换return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}

如上所示代码中,要特别注意的是我们访问这两个接口时候使用的是list类,并且是const的对象,那么一定要加上const,否则就是权限放大了。

我们来测试一下const迭代器,我们只需要在上面的测试用例中补上一个Print接口即可

void Print(const list<int> lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << (*it) << " ";++it;}cout << endl;}

在这里插入图片描述

显然是可以的,不过还是刚才那个问题,这个迭代器太冗余了。因为仅仅就是一个返回值不一样而已,那么我们能否去改善呢?其实是可以的。我们可以通过一个类型去控制这个返回值,而要控制这个类型,就需要增加一个模板参数即可

我们可以这样做

在迭代器类中添加一个Ref参数
在这里插入图片描述

然后让*的运算符重载返回这个模板参数。

在这里插入图片描述

最后我们在list类中定义const_iterator的时候这样定义。
在这里插入图片描述

如此一来,很巧妙的解决了代码冗余的问题。虽然说从实际上来说并无太大变化。本质还是两个迭代器类。但是使我们的代码更加精简了
不过这样一来虽然list精简了,但是按照我们之前的迭代器代码,后面的大部分迭代器类型都需要大幅度改动,于是我们不妨使用typedef一下。以防后序再次修改。

如下代码所示:

	template<class T, class Ref>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref> self;Node* _node;__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_val;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const self & it){return _node != it._node;}bool operator==(const self & it){return _node == it._node;}};

当然现在还没完,还有时候,我们可能会写出这样的代码

	struct A{A(int a = 0, int b = 0):_a(a),_b(b){}int _a;int _b;};void test2(){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));lt.push_back(A(5, 5));list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a << " ";it++;}cout << endl;}

在这里我们显然发现是无法正常运行的。由于迭代器是类似于指针的操作,我们有时候就需要->操作符去解引用。所以我们就需要写一个->的运算符重载。

		T* operator->(){return &_node->_val;}

如上所示,是写在迭代器里面的operator运算符重载。

利用这个运算符重载我们就可以跑上面的代码了
在这里插入图片描述

然而当我们细心的话,我们会发现这个运算符重载是比较怪异的。哪里怪异呢?

首先我们这个运算符重载返回的是什么呢?是A*,也就是说它还需要一次->解引用才能找到真正的值。那么我们这里为什么可行呢?

严格来说,it->->_a,才是符合语法的。
因为运算符重载要求可读性,那么编译器特殊处理,省略了一个->

上面这个运算符重载仅仅只是针对于普通对象的,如果是const对象的话,那么我们只能使用跟*运算符重载一样的处理方法,多传一个参数,才可以解决这个问题。也就是我们需要三个模板参数才可以。

在这里插入图片描述
在这里插入图片描述

最终我们的迭代器代码如下所示

	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;}};

8.list的insert、erase等接口

当我们实现了迭代器以后,剩余的接口其实就很简单了,与C语言中list的实现是一模一样的。

		void push_back(const T& val){//insert(end(), val);Node* newnode = new Node(val);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;cur = nullptr;prev->_next = next;next->_prev = prev;return next;}

9.size

对于这个size,我们有两种方法,第一种如下所示,我们直接遍历统计。这样时间复杂度是O(N)

		size_t size(){size_t sz = 0;iterator it = begin();while (it != end()){it++;sz++;}return sz;}

还有一种方法是增加一个私有成员变量_size,当插入数据的时候加一,当删除数据的时候减一即可。还有构造函数我们也需要初始化一下这个_size。

这样一来,我们只需要将前面的代码都给修改一下即可。

10.list的clear

对于clear,我们直接复用前面的接口即可。

		void clear(){iterator it = begin();while (it != end()){it = erase(it);}}

11.list的析构函数

析构函数也是很简单的,析构和clear的区别就是析构会删除头节点,而clear不会删除头节点。

		~list(){clear();delete _head;_head = nullptr;}

12.list拷贝构造函数

我们这里已经涉及到资源的申请与释放了,所以我们必须得构造一个深拷贝构造函数

		list(const list<T>& lt){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto& e : lt){push_back(e);}}

当然在这里我们可能会觉得拷贝构造和默认构造有点重复了。我们可以对前面重复的部分在封装一个函数,从而简化代码

		void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;empty_init();}list(const list<T>& lt){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;empty_init();for (auto& e : lt){push_back(e);}}

13.赋值运算符重载

如下所示,这个也是比较简单,我们直接使用现代写法即可

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

这里我们还有一个东西需要注意的是
我们也许会发现,库里面的函数返回值和形参写的是类名,而不是类型
在这里插入图片描述

这个的话,是因为在类模板里面写成员函数的时候,是允许用类名代替类型的。

即我们的代码下面这些部分可以直接换为类名,但是呢,这里不建议使用,因为会降低可读性。
在这里插入图片描述

14. 测试代码

我们现在用如下的代码可以测试出以上的全部函数的使用

	void test3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;lt.push_front(6);lt.push_front(7);lt.push_front(8);lt.push_front(9);lt.push_front(10);for (auto e : lt){cout << e << " ";}cout << endl;Print(lt);cout << lt.size() << endl;lt.clear();cout << lt.size() << endl;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);it = lt.begin();it++;for (auto e : lt){cout << e << " ";}cout << endl;it = lt.insert(it, 100);for (auto e : lt){cout << e << " ";}cout << endl;it--;it = lt.insert(it, 200);for (auto e : lt){cout << e << " ";}cout << endl;++it;it = lt.insert(it, 300);for (auto e : lt){cout << e << " ";}cout << endl;--it;it = lt.erase(it);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_back();lt.pop_front();for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt2;lt2.push_back(1);lt2 = lt;for (auto e : lt2){cout << e << " ";}cout << endl;}

经测试,运行结果正常
在这里插入图片描述

三、模拟list类的全部代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<list>
#include<assert.h>using namespace std;namespace Sim
{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){}};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_const_iterator<T> const_iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;iterator begin(){//return _head->_next //单参数的构造函数支持隐式类型转换return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{//return _head->_next //单参数的构造函数支持隐式类型转换return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;empty_init();}list(const list<T>& lt){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;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){swap(lt);return *this;}void push_back(const T& val){insert(end(), val);//Node* newnode = new Node(val);//Node* tail = _head->_prev;//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;cur = nullptr;prev->_next = next;next->_prev = prev;--_size;return next;}size_t size(){//size_t sz = 0;//iterator it = begin();//while (it != end())//{//	it++;//	sz++;//}//return sz;return _size;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}private:Node* _head;size_t _size;};void Print(const list<int> lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << (*it) << " ";++it;}cout << endl;}void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;Print(lt);}struct A{A(int a = 0, int b = 0):_a(a),_b(b){}int _a;int _b;};void Print(const list<A>& lt){list<A>::const_iterator it = lt.begin();while (it != lt.end()){cout << it->_a << " ";it++;}cout << endl;}void test2(){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));lt.push_back(A(5, 5));cout << lt.size() << endl;lt.clear();cout << lt.size() << endl;list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a << " ";it++;}cout << endl;Print(lt);}void test3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;lt.push_front(6);lt.push_front(7);lt.push_front(8);lt.push_front(9);lt.push_front(10);for (auto e : lt){cout << e << " ";}cout << endl;Print(lt);cout << lt.size() << endl;lt.clear();cout << lt.size() << endl;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);it = lt.begin();it++;for (auto e : lt){cout << e << " ";}cout << endl;it = lt.insert(it, 100);for (auto e : lt){cout << e << " ";}cout << endl;it--;it = lt.insert(it, 200);for (auto e : lt){cout << e << " ";}cout << endl;++it;it = lt.insert(it, 300);for (auto e : lt){cout << e << " ";}cout << endl;--it;it = lt.erase(it);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_back();lt.pop_front();for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt2;lt2.push_back(1);lt2 = lt;for (auto e : lt2){cout << e << " ";}cout << endl;}
}

好了,本节内容就到这里了
本节内容难度稍大,希望读者可以好好阅读。有不懂的可以及时私聊博主解答疑惑

如果对你有帮助的话,不要忘记点赞加收藏哦!!!

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

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

相关文章

SQLserver数据日期数据处理方式(日期查询,日期转换字符串)

获取本月第一天 dateadd(dd,-day(getdate())1,getdate()) 获取下月第一天&#xff08;数据 当前日期减去 日数 在加一个月&#xff09; DATEADD(MM,1,dateadd(dd,-day(getdate())1,getdate())) 获取本月最后一天&#xff08;&#xff09; 将日期转换为字符串的方法两种 1.使用c…

arm neon/fpu/mfloat

neon官网介绍: Arm Neon technology is an advanced Single Instruction Multiple Data (SIMD) architecture extension for the A-profile and R-profile processors. Neon technology is a packed SIMD architecture. Neon registers are considered as vectors of elements …

huatuo(hybridclr)热更新踩坑笔记

个人尝试的结果&#xff0c;不一定为正规的操作&#xff0c;若观者有更好的方案&#xff0c;望赐教。 1.第三方框架应该放在哪里&#xff1f; 以热更框架为例&#xff0c;入口函数进入后&#xff0c;需要调用热更代码检查资源&#xff0c;更新资源&#xff0c;加载程序集。测试…

网络安全 Day25-HTML 介绍和简单基础

HTML 介绍和简单基础 1. HTML 介绍1.1 什么是HTML1.2 如何编辑HTML 2. Html 基础2.1 Html 标题2.2 HTML 段落2.3 HTML 换行2.4 HTML 水平线2.5 HTML 注释2.6 如何查看网页源代码2.7 html标签属性2.8 格式化文字2.9 HTML 实体2.10 HTML 超链接 1. HTML 介绍 1.1 什么是HTML Ht…

前沿分享-会发电的水凝胶敷料

四川大学的研究团队设计了一种新型的伤口敷料&#xff0c; 将电刺激治疗引入伤口敷料&#xff0c;达到营造湿润环境的同时利用电刺激来加速愈合的效果。 上半部分由树状纳米纤维构成&#xff0c;下半部分由双网络导电水凝胶构成&#xff0c;加入了铁离子和儿茶酚。该部分用于贴…

【FPGA IP系列】FIFO的通俗理解

FPGA厂商提供了丰富的IP核&#xff0c;基础性IP核都是可以直接免费调用的&#xff0c;比如FIFO、RAM等等。 本文主要介绍FIFO的一些基础知识&#xff0c;帮助大家能够理解FIFO的基础概念。 一、FIFO介绍 FIFO全称是First In First Out&#xff0c;即先进先出。 FIFO是一个数…

语义检索系统【四】:基于ERNIE-Gram的Pair-wise和基于RocketQA的CrossEncoder训练的单塔模型实现数据精排

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

无涯教程-Lua - 环境安装

在Windows上安装 为Windows环境开发了一个单独的名为" SciTE"的IDE,可以从https://code.google.com/p/luaforwindows/下载部分。 运行下载的可执行文件以安装Lua IDE。 由于它是一个IDE&#xff0c;因此您可以使用它来创建和构建Lua代码。 如果您有兴趣在命令行模…

微服务架构的模式介绍

1.微服务架构模式方案 用Scale Cube方法设计应用架构&#xff0c;将应用服务按功能拆分成一组相互协作的服务。每个服务负责一组特定、相关的功能。每个服务可以有自己独立的数据库&#xff0c;从而保证与其他服务解耦。 1.1 聚合器微服务设计模式 聚合器调用多个服务实现应用程…

Jmeter远程服务模式运行时引用csv文件的路径配置

问题 在使用jmeter过程中&#xff0c;本机的内存等配置不足&#xff0c;启动较多的线程时&#xff0c;可以采用分布式运行。 在分布式运行的时候&#xff0c;jmeter会自动将脚本从master主机发送到remote主机上&#xff0c;所以不需要考虑将脚本拷贝到remote主机。但是jmeter…

UPnP是什么?有什么更好的连接方案?快解析内网穿透

一、UPnP是什么 有些小伙伴对于UPnP并不了解&#xff0c;其实UPnP只是一种网络协议&#xff0c;主要作用就是简化家庭和企业网络中设备之间的连接和通信过程&#xff0c;它的主要目标是实现网络的无缝连接&#xff0c;并简化相关网络操作。 二、UPnP有什么主要作用&#xff1…

125.验证回文串

目录 一、题目 二、代码 一、题目 125. 验证回文串 - 力扣&#xff08;LeetCode&#xff09; 二、代码 class Solution { public: bool ABC(char& s) {if (s > 65 && s < 90){s 32;return true;}if (s > 97 && s < 122){return true;}if …

Chapter 10: Dictionaries | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介DictionariesDictionariesDictionary as a set of countersDictionaries and filesLooping and dictionariesAdvanced text parsingDebuggingGlossary Python for Everybody Exploring Data Using Python 3 Dr. Charles R. Severance 课程…

linux安装Tomcat及部署jpress的详细教程!!!

一、YUM在线安装 1、查看Tomcat相关安装包 [rootlocalhost ~]# yum list | grep tomcat tomcat.noarch 7.0.76-16.el7_9 updates tomcat-admin-webapps.noarch 7.0.76-16.el7_9 updates tomcat-docs…

2D视觉检测算法整理

1.ROI pooling 和 ROI align的区别 ROI pooling第一步根据候选区域找特征图的位置&#xff0c;可能不是刚好对应&#xff0c;需要一次量化&#xff0c;如上图所示&#xff0c;第二次是特征图需要转化为特定的大小&#xff0c;这时候pooling可能也不能正好整除&#xff0c;所以第…

【Linux命令200例】tee将输入内容输出到屏幕和文件

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;Linux命令大全。 &#x1f3c6;本专栏我们会通过具体的系统的命令讲解加上鲜…

C++ 类型转换 int, hex,char* float, string, wstring

日常的开发中经常会用到类型的相互转换&#xff0c;这里记录一下&#xff1b; 1, int转hex std::string IntToHex(int value) {stringstream ioss;ioss << std::hex << value;string temp;ioss >> temp;return temp;} 2, int转hex 设置宽度 std::string …

计算机毕设 深度学习猫狗分类 - python opencv cnn

文章目录 0 前言1 课题背景2 使用CNN进行猫狗分类3 数据集处理4 神经网络的编写5 Tensorflow计算图的构建6 模型的训练和测试7 预测效果8 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往…

Not allowed to load local resource: file:///D:/xxx.jpg

问题&#xff1a; Not allowed to load local resource: file:///D:/xxx.jpg 出现以上情况的原因是浏览器不允许访问本地路径&#xff0c;所以你需要配置一个虚拟的路径代替本地的路径。 解决方法&#xff1a; 思路&#xff1a;通过tomcat代理&#xff0c;在tomcat里写一个虚…

mockery 模拟

composer地址&#xff1a;mockery/mockery - Packagist github地址&#xff1a;地址 文档地址&#xff1a;Mockery — Mockery Docs 1.0-alpha documentation 根据文档介绍&#xff0c;mockery是php mock对象框架。根据js的mock框架的作用&#xff0c;估计mockery也是通过创…