【C++】list的使用与模拟实现

                                                

🔥个人主页:北辰水墨

🔥专栏:C++学习仓

Alt

本节内容我们来讲解list的使用和模拟实现。 本节难点:list迭代器的模拟实现。

一、list的介绍:

列表
列表是一种序列容器,允许在序列的任何位置进行时间复杂度为O(1)的插入和删除操作,并可在前后两个方向上进行迭代

列表容器被实现为双向链表;双向链表可以将它们包含的每个元素存储在不同且不相关的存储位置中。通过将每个元素关联到其前面的元素和后面的元素的链接来在内部保持顺序。

它们与forward_list非常相似:主要区别在于forward_list对象是单向链表,因此forward_list对象只能向前迭代,作为交换,forward_list对象相对较小和更高效。

与其他基本标准序列容器(数组,向量和双端队列)相比,列表在已获得迭代器的情况下通常在容器内的任何位置执行插入、提取和移动元素方面表现更好,因此在那些大量使用这些操作的算法(如排序算法)中也表现更好。

与其他序列容器相比,列表和forward_list的主要缺点是它们缺乏通过位置直接访问元素的能力;例如,要访问列表中的第六个元素,需要从已知位置(如开头或结尾)迭代到该位置,这在这两者之间的距离上需要线性时间。它们还会消耗一些额外的内存来保存与每个元素关联的链接信息(对于大量小尺寸元素的大型列表可能是一个重要因素)。

总结:list是一个带头双向循环列表

二、接口函数:

2.1 构造函数:

 2.1.1 Default constructor (构造一个空的 std::list):
             std::list<int> myList1;    // 创建一个空的整型链表

2.1.2 Fill constructor (构造一个有特定数量元素且每个元素都有相同初始值的 std::list):
            std::list<int> myList2(5, 10); // 创建一个有5个元素的链表,每个元素都初始化为10

2.1.3 Range constructor (从另一个迭代器定义范围的容器中构建 std::list):
            std::vector<int> myVector{1, 2, 3, 4, 5};
            std::list<int> myList3(myVector.begin(), myVector.end()); // 使用vector的范围来初始化链表

2.1.4 Copy constructor (使用另一个 std::list 来构造一个新的 std::list, 是副本):
            std::list<int> myOriginalList{1, 2, 3, 4, 5};
            std::list<int> myList4(myOriginalList); // 使用另一个list来初始化这个新的list

Fill constructor构造函数前面加explicit,表示不能隐式类型转换

2.2 迭代器

 用法保持不变:

#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);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;list<int>::reverse_iterator rit = lt.rbegin();while (rit != lt.rend()){cout << *rit << " ";rit++;}return 0;
}

2.3 容量操作: 

  • empty检测list是否为空,是返回true,否则返回false
  • size返回有效元素个数的值

2.4 元素访问:

  • front返回list的第一个节点值的引用
  • back返回list的最后一个节点值的引用

 2.5内容操作:

这里大多数函数我们在前面都讲解过,包括头插头删尾插尾删之类的。 

2.5.1 assign

清除原来的数据,再链接新数据(模拟实现原理,先clear(),再push_back() )

  size的大小会跟着改变。

// list::assign
#include <iostream>
#include <list>int main()
{std::list<int> first;std::list<int> second;first.assign(7, 100);                      // 7 ints with value 100second.assign(first.begin(), first.end()); // a copy of firstint myints[] = { 1776,7,4 };first.assign(myints, myints + 3);            // assigning from arrayfor (auto u : first){std::cout << u << " ";}std::cout << std::endl;std::cout << "Size of first: " << int(first.size()) << '\n';std::cout << std::endl;for (auto u : second){std::cout << u << " ";}std::cout << std::endl;std::cout << "Size of second: " << int(second.size()) << '\n';return 0;
}

2.5.2 

push_front   头插 

push_back   尾插

pop_front   头删

pop_back   尾删

2.5.3  insert

      1. 不会出现迭代器失效:它不像vector那样会有异地扩容,挪动数据,iterator野指针的问题。

      2. 不用it接收返回值:那么it还是指向那个节点的地址。

      3. 用it接收返回值:就指向新添加的首节点的地址

#include <iostream>
#include <list>
#include <vector>int main()
{std::list<int> mylist;std::list<int>::iterator it;// set some initial values:for (int i = 1; i <= 5; ++i) mylist.push_back(i); // 1 2 3 4 5it = mylist.begin();++it;       // it points now to number 2           ^it=mylist.insert(it,3, 10);                       // 1 10 10 10 2 3 4 5用it接收返回值                                     ^mylist.insert(it, 2, 20);                      // 1 20 20 10 10 10 2 3 4 5没有用it接收返回值,还是指向那一块空间                  ^std::cout << "mylist contains:";for (it = mylist.begin(); it != mylist.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';return 0;
}

2.5.4 erase

        在earse时会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不受影响

只需要用it接收earse函数返回值。

返回值为删除元素的下一个节点。

// erasing from list
#include <iostream>
#include <list>int main ()
{std::list<int> mylist;std::list<int>::iterator it1,it2;// set some values:for (int i=1; i<10; ++i) mylist.push_back(i*10);// 10 20 30 40 50 60 70 80 90it1 = it2 = mylist.begin(); // ^^advance (it2,6);            // ^                 ^++it1;                      //    ^              ^it1 = mylist.erase (it1);   // 10 30 40 50 60 70 80 90//    ^           ^it2 = mylist.erase (it2);   // 10 30 40 50 60 80 90//    ^           ^++it1;                      //       ^        ^--it2;                      //       ^     ^mylist.erase (it1,it2);     // 10 30 60 80 90//        ^std::cout << "mylist contains:";for (it1=mylist.begin(); it1!=mylist.end(); ++it1)std::cout << ' ' << *it1;std::cout << '\n';return 0;
}

2.5.5 clear

清除数据,保留 带头节点,size变成0 

2.6  opeations

std::list 提供了一些有用的成员函数,允许执行各种操作,如元素的合并、移除、排序和倒序。下面是这些函数的简要说明和使用示例:

splice: 将元素从一个列表转移到另一个列表,可以转移整个列表、一个单独的元素或一个元素范围。不会执行任何元素的复制或者移动操作,只是在内部节点之间调整指针。

std::list<int> list1 = {1, 2, 3};

std::list<int> list2 = {4, 5, 6};

list1.splice(list1.end(), list2); // 把list2的所有元素移动到list1的末尾 

remove: 从列表中移除所有具有特定值的元素。

 list lt={1,2,3,4,5,3,3,4};

lt.remove(3);// 移除所有值为3的元素     最后为 lt={1,2,4,5,4};

remove_if: 根据一个判断条件移除元素。

 bool ref(const int n)  {return n % 2 == 0; }    //判断条件,偶数为true

 myList.remove_if(ref);       //删除返回值为true的元素

#include <iostream>
#include <list>
bool ref(const int n)
{return n % 2 == 0; 
}int main()
{std::list<int> myList = { 1, 2, 3, 4, 5 };myList.remove_if(ref);for (auto u : myList){std::cout << u << " ";}return 0;
}

unique: 移除连续并且重复的元素,只保留唯一的元素。

必须要是有序的,才可以使用unique函数(底层原理:判断前后元素是否相同) 

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

一开始无序:不做任何处理 

merge: 合并两个已排序的列表,并确保结果列表也是排序的。

 std::list<int> list1 = {1, 3, 5};

std::list<int> list2 = {2, 4, 6};

list1.merge(list2); // 合并两个列表为1, 2, 3, 4, 5, 6

sort: 对列表中的元素进行排序。它接受一个比较函数作为参数(可选)。

 std::list<int> myList = {4, 3, 5, 2, 1};

myList.sort(); // 排序列表为1, 2, 3, 4, 5

reverse: 反转列表中元素的顺序。

 std::list<int> myList = {1, 2, 3, 4, 5};

myList.reverse(); // 反转后列表为5, 4, 3, 2, 1

三、模拟实现: 

 3.1基本框架:

namespace moon
{template<class T>struct __list_node{T _val;__list_node<T>* prev;__list_node<T>* next;__list_node(const T& x=T()):_val(x),prev(nullptr),next(nullptr){}};template<class T>class list{typedef __list_node<T> Node;public:private:Node* _head;size_t size;};
}

这段代码实现了一个简单双向链表的基础结构。让我们分两部分来解释这个代码: 

1.  namespace moon 命名空间 moon 用于封装代码,避免与其他库中的同名类型或函数冲突。在这个命名空间中,定义了模板类__list_node和模板类list。

2.  模板类__list_node :

        数据的存储是用了T模版类型,还定义了两个指针,分别指向前驱和后继。

        还有一个构造函数,用于在创建模板类__list_node的对象时进行初始化。

        值得注意的是:构造函数的缺省值是T(),调用他自身的构造,如A类,就去调用A本身的构造函数。而内置类型:在C++中也有对应的构造函数。

        是用struct来写的,目的是:允许别人访问我的成员

3.模板类list :       

  • 类型定义 typedef ListNode<T> Node 是为了简化代码,使得在类 list 中可以直接使用 Node 来指代 ListNode<T>
  • _head 是一个指向链表头部节点的指针。
  • _size 是一个 size_t 类型的值,用来存储链表的长度(即节点个数)。
  • 用class来写,目的是:封装,保护

  • 成员变量包括:指向头节点的指针 和 存储链表的长度的size

3.2 list的基本函数:

🔥list()

list()
{_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;
}

🔥尾插 push_back()

void push_back(const T& x)
{Node* NewNode = new Node(x);Node* end = _head->prev;end->next = NewNode;NewNode->prev = end;_head->prev = NewNode;NewNode->next = _head;_size++;
}

我们完成了添加元素的基本操作,那么我们如何遍历它呢?

我们需要用迭代器来遍历,这就是我们本篇文章的重点,迭代器的模拟实现。

3.3迭代器的封装和实现: 

我们来思考一下,原生的指针能不能帮助我们完成迭代器?

1. 链表是随机存储的,让原生指针++,是不会跳到下一个节点。应该对++进行运算符重载。

2. 对原生指针进行解引用,得不到我们需要的节点中的_val。

所以我们需要对迭代器进行封装。 

	template<class T>struct __list_iterator{trpedef __list_node<T> Node;//成员变量Node* _PtrNode;__list_iterator(Node* node):_PtrNode(node){}};

注意:我们的迭代器是在list类中访问的: 

list<int>::iterator it=lt.begin(); 

我们应该在list类中实现嵌套,对__list_node<T>  typedef

template<class T> class list

{

public:

        // 这是一个嵌套类型的别名定义。

        typedef __list_iterator<T> iterator;

        // ...

}; 

list<int>::iterator it = lt.begin(); 这一行涉及到了所谓的嵌套类型或者内嵌类型

        在C++中,当一个类型(比如 __list_iterator<T>)是在另一个类型的作用域内部定义的(比如 list<T>)时,这个类型被称为嵌套类型。嵌套类型通常用于与外部类型紧密相关联的概念,例如迭代器、节点或其他辅助类。

        这里的 iterator 是 list 类的嵌套类型的别名:

template<class T>
class list {
public:  typedef __list_iterator<T> iterator;
};

        所以当我们在类外部引用它时,我们需要使用类的名称(在这个例子中是 list<int>),后跟两个冒号来限定作用域,然后是别名 iterator。因此 list<int>::iterator 指的是 __list_iterator<int>,它是链接到特定的 list 实例化类型的迭代器类型

        要点是,内嵌类型通常在类定义内部,并且与该类紧密关联。当我们在类外部谈到这些类型时,需要使用类的名称来限定这些类型,就像我们引用 list<int>::iterator  一样。这种设计方式提供了良好的封装和组织结构,在集合和容器类(如 list )中是一种常见做法

list<int>::iterator it1=lt.begin();vector<int>::iterator it2=v.begin();string::iterator it3=str.begin();

目前所学的:这三者的底层完全不同,但是上层都用法相同,就可以对他们进行遍历。 

 在对应的list vector string类中都对iterator typedef了。其实调用的都是不同的迭代器。


 迭代器就是一个节点的指针,我们这个类的成员就是_PtrNode(节点指针)

	template<class T>struct __list_iterator{trpedef __list_node<T> Node;//成员变量Node* _PtrNode;__list_iterator(Node* node):_PtrNode(node){}};

迭代器与list的联系: 

我们还需要在list类中给__list_iterator类提供两个接口,lt.begin() 和lt.end()

	template<class T>class list{typedef __list_node<T> Node;typedef __list_iterator<T> iterator;public:list(){_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;}iterator begin(){return _head->next;}iterator end(){return _head;}private:Node* _head;size_t _size;};
}

分析代码:
        1. 涉及到隐式类型转换:把_head->next和_head节点指针,隐式转换成迭代器。因为迭代器的构造函数是 __list_iterator(Node* node) 单参数的构造函数。

        2. begin返回第一个数据的迭代器,end返回最后一个数据的下一个位置 

 回到最初的问题,实现迭代器的++和*等操作:

🔥operator++()和operator--()

     这里的++/--,改变指针的位置,挪到下一个/上一个节点。

前置++:

		self& operator++(){_PtrNode = _PtrNode->next;//节点的指针return  *this; // 迭代器}

后置++:

		self& operator++(int){self tmp(*this);_PtrNode=_PtrNode->next;return tmp;}

这里的浅拷贝是不会出现问题的,因为是内置类型,指针类型。就是为了创建一个新的指向那个位置的指针。 

 前置--:

		self& operator--(){_PtrNode = _PtrNode->prev;//节点的指针return  *this; // 迭代器}

后置--:

		self& operator--(int){self tmp(*this);_PtrNode=_PtrNode->prev;return tmp;}

 🔥operator*()

直接解引用不能得到我们想要的数据,所以我们需要对解引用操作符重载

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

  🔥operator!=()

bool operator!=(self& lt)  //it!=lt.end()
{return _PtrNode != lt._PtrNode;
}

3.4 list函数的完善: 

 又了迭代器,我们就可以完善list函数了。

 🔥insert()

iterator insert(iterator pos,const T& val)
{Node* newnode = new Node(val);//pos是__list_iterator类的一个实例化对象,pos._PtrNode是去取对象中的成员Node* cur = pos._PtrNode;Node* prev = cur->prev;// prev newnode curprev->next = newnode;newnode->prev = prev;cur->prev = newnode;newnode->next = cur;_size++;return newnode;
}

🔥erase()

iterator erase(iterator pos)
{Node* cur = pos._PtrNode;Node* prev = cur->prev;Node* next = cur->next;prev->next = next;next->prev = prev;delete(cur);_size--;return next;  //erase函数返回的是下一个节点的迭代器//同样,这里包括隐式类型转换
}

 🔥拷贝构造

list(const list<T>& lt)//这个函数需要先完成const迭代器才行,const迭代器后面会有模拟实现
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto e : lt)  //通过const迭代器实现{push_back(e);}
}

 🔥赋值重载

void swap(list<T>& lt)
{std::swap(_head, lt._head);std::swap(_size, lt._size);
}// lt3 = lt1
list<int>& operator=(list<int> lt)  //自定义类型传参,调用拷贝构造
{swap(lt);  //交换头节点和_sizereturn *this;  //出了函数,lt会去调用析构函数,顺便把一开始*this中的数据清除
}

1. 传参时:拷贝构造,得到lt

2. 调用自己写的swap函数,把头节点和_size交换

3. 出了函数,lt 调用析构函数

🔥头尾删插

有了insert和erase函数,我们就可以对push_back/push_front/ pop_back/pop_front进行复用

void push_back(const T& val)
{insert(end(), val);
}
void  push_front(const T& val)
{insert(begin(), val);
}
void pop_back()
{erase(--end());  //end()返回的是带头节点
}
void pop_front()
{erase(begin());
}

 🔥与_size相关的函数

//返回_size的大小
size_t size()
{return _size;
}//判断是否为空
bool empty()
{return _size == 0;
}

🔥链表销毁

void clear()
{iterator it = begin();while (it != end()){it = erase(it); //_size在erase函数中会减小}
}
~list()
{clear();delete _head;_head = nullptr;
}

 3.5 进一步完善迭代器:

对于下面的这种类

 struct A{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}};

如果list存的是上面的自定义类型呢?

插入有以下几种方法:

void test_list2()
{list<A> lt;A aa1(1, 1);A aa2 = { 1, 1 };lt.push_back(aa1);lt.push_back(aa2);lt.push_back(A(2, 2));lt.push_back({ 3, 3 });lt.push_back({ 4, 4 });  
}

上面代码使用不同方式来创建和插入 A 类型的对象到自定义的 list 容器中。下面是每种方式的详细说明以及它们所涉及的概念:

有名对象的直接插入:

A aa1(1, 1);
lt.push_back(aa1);

这里,首先创建了一个命名对象 aa1,使用了 A 的构造函数 A(int a1, int a2) 并为其提供了两个参数。然后,你将 aa1 作为参数传递给 lt.push_back() 函数。在这种情况下,aa1 是有名对象(也就是说,它有一个名称),并且 push_back 函数接受到的是 aa1 的一个副本

多参数隐式类型转换:

A aa2 = { 1, 1 };
lt.push_back(aa2);

aa2 通过列表初始化的方式被创建。这里的列表初始化允许直接用花括号 {} 来初始化对象。C++11 引入的列表初始化特性可以用来初始化任何对象,包括具有构造函数的对象。创建了 aa2 有名对象并将其插入到列表中

通过构造函数创建匿名对象并插入:

lt.push_back(A(2, 2));

在这里,没有给新创建的 A 对象一个名字,因此它是一个匿名对象(也称作临时对象)。这个匿名的 A 对象是通过调用它的构造函数来直接初始化的,并立即被传递到 push_back 函数中。

通过隐式类型转换创建匿名对象并插入:

lt.push_back({ 3, 3 });

与第三种方式类似,创隐式类型转换建了一个匿名的 A 对象,但这次是通过。初始化时没有使用相应类型的构造函数,而是依赖编译器生成的代码来创建一个具有给定初始化列表的对象,并将其传递给 push_back 函数。

在所有这些情况中,实际插入到 list 容器中的都是 A

现在我们来进行遍历:

list<A>::iterator it = lt.begin();
while (it != lt.end())
{cout<<*it<<" ";++it;
}

 A是自定义类型,不支持留插入,我们解引用得到的_data是A的对象

这里数据是公有的,解引用得到的可以通过.访问符进行访问

cout << (*it)._a1 << ":" << (*it)._a2 << endl;

这种访问方式相当于这种形式:

A* ptr = &aa1;
(*ptr)._a1;

这种指针访问行为十分复杂,我们可以重载一个函数使实现这种访问方式:

ptr->_a1;

在迭代器中重载->运算符

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

这里返回的是_data的地址

我们就可以这样访问:

cout << it->_a1 << ":" << it->_a2 << endl;

实际上它的访问方式如下:

cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

注意:

这里隐藏了一个箭头,一个是重载,一个是原生指针的访问操作

实际上,并不需要在代码中写两次箭头。这是因为在 C++ 中,operator-> 有一个特殊的规则

当重载 operator->,不会直接返回成员的值,而是应该返回一个指针,这个指针指向的对象包含我们想要访问的成员。当使用 ->运算符时,C++ 会自动和透明地调用重载的 operator-> 并继续 “链式” 访问成员,而不需要程序员显示地添加多余的箭头。

这是如何工作的:

1.  如果有一个用户自定义类型的对象(比如迭代器)it,并且我们调用 it->member,编译器会查找这个类型是否有 operator->
2.  如果这个类型有一个 operator-> 的重载,编译器就调用 it.operator->()
3.  it.operator->() 应该返回一个指向对象的指针,这个对象有一个我们尝试访问的 member 成员
4.  编译器取得这个指针,并继续访问 member

这个流程只需要一个 -> 即可。编译器在背后处理了所有操作,直到访问到一个实际对象,然后这个对象的成员可以直接通过 -> 运算符访问

因此,我们不需要手动写成 it->->member;只需写 it->member 即可。编译器会自动将其 “转换” 为 it.operator->()->member

在 ListIterator 示例里,it->_a1 意味着:

1.  调用 it.operator->() 拿到 ListNode 中 _data 成员的地址(这是一个 A 类型的对象)。
2.  使用返回的指针来访问 A 对象的 _a1 成员。

整个过程对于编程者来说是透明的,不需要编写多个 ->。这种处理方式使得重载 -> 可以更自然地使用,就像处理普通的指针一样。

const迭代器

我们上面写的迭代器对于const对象是无法编译成功的,const不能调用非const成员函数

对于const类迭代器,我们需要在list类里面重新增加重载

typedef _const__list_iterator<T> const_iterator;const_iterator begin() const
{return _head->_next;
}const_iterator end() const
{return _head;
}

这样子我们就有两个版本:iterator 和 const_iterator

    普通对象调用iterator版本     const对象调用iterator版本 

1.  这里的const版本不是对普通迭代器进行const修饰。(想象当我们使用const修饰指针,指针不能移动,指针指向的内容可以修改,不符合我们的预期)

2.  const迭代器是一个重新定义的类型,本身可以修改,指向的内容不可以修改

总结:const_interator 是一个单独的类,我们要实现的是 不能修改数据的(不能改变迭代器指向内容的值),而不是不能移动的迭代器(让迭代器++/--等等操作,这样子就需要能改变迭代器的值)。

方法一:单独再实现一个const_iterator类

	template<class T>struct _const__list_iterator{typedef __list_node<T> Node;typedef _const__list_iterator<T> self;//成员变量Node* _PtrNode;_const__list_iterator(Node* x):_PtrNode(x){}self& operator++(){_PtrNode = _PtrNode->next;//节点的指针return  *this; // 迭代器}self& operator++(int){self tmp(*this);_PtrNode = _PtrNode->next;return tmp;}self& operator--(){_PtrNode = _PtrNode->prev;//节点的指针return  *this; // 迭代器}self& operator--(int){self tmp(*this);_PtrNode = _PtrNode->prev;return tmp;}const T& operator*(){return _PtrNode->_val;}bool operator!=(const self& it)  //it!=lt.end(){return _PtrNode != it._PtrNode;}const T* operator->(){return &_PtrNode->_val;}};

 

 合并两中迭代器:

这里仅仅只是两种返回类型不同,这里我们利用模版来对这里内容进行合并 

	template<class T,class Ref,class Ptr>struct __list_iterator{typedef __list_node<T> Node;typedef __list_iterator<T,Ref,Ptr> self;//成员变量Node* _PtrNode;__list_iterator(Node* x):_PtrNode(x){}Ref operator*(){return _PtrNode->_val;}Ptr operator->(){return &_PtrNode->_val;}self& operator++(){_PtrNode = _PtrNode->next;//节点的指针return  *this; // 迭代器}self& operator++(int){self tmp(*this);_PtrNode=_PtrNode->next;return tmp;}self& operator--(){_PtrNode = _PtrNode->prev;//节点的指针return  *this; // 迭代器}self& operator--(int){self tmp(*this);_PtrNode = _PtrNode->prev;return tmp;}bool operator!=(const self& it)  //it!=lt.end(){return _PtrNode != it._PtrNode;}};

 我们只提取不同的部分,其他部分与原来相同

Ref代表引用,Ptr代表指针

让我们来看一下这个合并后的迭代器的模板参数:

  • T:列表节点存储的数据类型

  • Ref:通过迭代器访问数据时的返回类型,可以是T&或者const T&。

  • Ptr:通过迭代器访问数据的指针类型,可以是T*或者const 

这样,我们可以创建一个常量迭代器,为RefPtr参数指定常量类型,例如:

__list_iterator<T, const T&, const T*> const_iterator;

对于非常量迭代器,就简单地传递非常量类型的引用和指针: 

__list_iterator<T, T&, T*> iterator;

list类中,我们需要相应地声明两种类型的迭代器:

	template<class T>class list{public:typedef __list_node<T> Node;typedef __list_iterator<T,T&,T*> iterator;typedef __list_iterator<T,const T&,const T*> const_iterator;list(){_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;}iterator begin(){return _head->next;}iterator end(){return _head;}const_iterator begin() const{return _head->next;}const_iterator end() const{return _head;}iterator insert(iterator pos,const T& val){Node* newnode = new Node(val);Node* cur = pos._PtrNode;//pos是__list_iterator类的一个实例化对象,pos._PtrNode是去取对象中的成员Node* prev = cur->prev;// prev newnode curprev->next = newnode;newnode->prev = prev;cur->prev = newnode;newnode->next = cur;_size++;return newnode;}iterator erase(iterator pos){Node* cur = pos._PtrNode;Node* prev = cur->prev;Node* next = cur->next;prev->next = next;next->prev = prev;delete(cur);_size--;return next;}void push_back(const T& val){insert(end(), val);}void  push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}size_t size(){return _size;}bool empty(){return _size == 0;}void clear(){iterator it = begin();while (it != end()){it = erase(it); //_size在erase函数中会减小}}~list(){clear();delete _head;_head = nullptr;}private:Node* _head;size_t _size;};
}

list类中的其他成员函数像beginend需要按照是否接收常量类型来适配这两种迭代器。 

 list<int>::const_iterator it=lt.begin();

list<int>:: iterator it=lt.begin();

需要在list<int>类里面访问

通过左边的类型,推出右边是用哪一种迭代器。

 模拟实现list的源码,仅供参考,纯手打的代码,可能会有bug,还请各位大佬批评指正。

namespace moon
{template<class T>struct __list_node{T _val;__list_node<T>* prev;__list_node<T>* next;__list_node(const T& x=T()):_val(x),prev(nullptr),next(nullptr){}};template<class T,class Ref,class Ptr>struct __list_iterator{typedef __list_node<T> Node;typedef __list_iterator<T,Ref,Ptr> self;//成员变量Node* _PtrNode;__list_iterator(Node* x):_PtrNode(x){}Ref operator*(){return _PtrNode->_val;}Ptr operator->(){return &_PtrNode->_val;}self& operator++(){_PtrNode = _PtrNode->next;//节点的指针return  *this; // 迭代器}self& operator++(int){self tmp(*this);_PtrNode=_PtrNode->next;return tmp;}self& operator--(){_PtrNode = _PtrNode->prev;//节点的指针return  *this; // 迭代器}self& operator--(int){self tmp(*this);_PtrNode = _PtrNode->prev;return tmp;}bool operator!=(const self& it)  //it!=lt.end(){return _PtrNode != it._PtrNode;}};template<class T>class list{public:typedef __list_node<T> Node;typedef __list_iterator<T,T&,T*> iterator;typedef __list_iterator<T,const T&,const T*> const_iterator;list(){_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;}iterator begin(){return _head->next;}iterator end(){return _head;}const_iterator begin() const{return _head->next;}const_iterator end() const{return _head;}iterator insert(iterator pos,const T& val){Node* newnode = new Node(val);Node* cur = pos._PtrNode;//pos是__list_iterator类的一个实例化对象,pos._PtrNode是去取对象中的成员Node* prev = cur->prev;// prev newnode curprev->next = newnode;newnode->prev = prev;cur->prev = newnode;newnode->next = cur;_size++;return newnode;}iterator erase(iterator pos){Node* cur = pos._PtrNode;Node* prev = cur->prev;Node* next = cur->next;prev->next = next;next->prev = prev;delete(cur);_size--;return next;}void push_back(const T& val){insert(end(), val);}void  push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}size_t size(){return _size;}bool empty(){return _size == 0;}void clear(){iterator it = begin();while (it != end()){it = erase(it); //_size在erase函数中会减小}}~list(){clear();delete _head;_head = nullptr;}private:Node* _head;size_t _size;};
}

 本篇list的使用与模拟实现,纯干货!!!

 

 

 

 

 

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

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

相关文章

基于springboot+mybatis+vue的项目实战之页面参数传递

如图所示&#xff0c;删除操作可以用按钮实现&#xff0c;也可以用超链接来实现。 1、第一种情况&#xff0c;用按钮实现。 html页面相关&#xff1a; <button type"button" click"deleteId(peot.id)">删除</button> <script>new Vue(…

【算法与数据结构】数组

文章目录 前言数组数组的定义数组的基本操作增加元素删除元素修改元素查找元素 C STL 中的数组arrayvector Python3 中的列表访问更改元素值遍历列表检查列表中是否存在某元素增加元素删除元素拷贝列表总结 Python3 列表的常用操作 参考资料写在最后 前言 本系列专注更新基本数…

从0开始Jmeter接口测试实战

在之前的文章中给大家介绍过接口测试文档和接口测试用例示例&#xff0c;本文基于Jmeter工具给大家介绍一下如何实现接口测试用例&#xff1a;包括发起Http请求&#xff0c;绕过登陆&#xff0c;验证响应。JMeter是Apache组织开发的基于Java的压力测试工具。具有开源免费、框架…

Leetcode—2105. 给植物浇水 II【中等】

2024每日刷题&#xff08;131&#xff09; Leetcode—2105. 给植物浇水 II 实现代码 class Solution { public:int minimumRefill(vector<int>& plants, int capacityA, int capacityB) {int size plants.size();int i 0;int j size - 1;int capA capacityA;in…

【Linux】Linux安装JDK

一、卸载Linux自带的JDK #查询已有的JDK rpm -qa | grep jdk ①将查询到的JDK全部卸载掉 #直接复制一整行的JDK名称 yum -y remove java-1.7.0-openjdk-headless-1.7.0.261-2.6.22.2.el7_8.x86_64 ②卸载完第一个后再次查询 ③继续卸载&#xff0c;卸载完成后再次查询 ④查询…

Flask-大体了解介绍

初识Flask Flask是使用 Python编写的Web微框架。Web框架可以让我们不用关心底层的请求响应处理&#xff0c;更方便高效地编写Web程序。 Flask主要有两个依赖&#xff0c;一个是WSGI&#xff08;Web Server Gateway Interface&#xff0c;Web服务器网关接口&#xff09;工具集…

ICode国际青少年编程竞赛- Python-4级训练场-太阳能板1

ICode国际青少年编程竞赛- Python-4级训练场-太阳能板1 1、 Dev.step(3) Dev.turnRight() Dev.step(2) while Dev.energy < 60:wait() Dev.step(-6)2、 Dev.step(7) while Dev.energy < 90:wait() Dev.step(-1) Dev.turnRight() Dev.step(7)3、 Dev.step(4) Dev.turn…

区块链 | NFT 水印:Review on Watermarking Techniques(三)

&#x1f34d;原文&#xff1a;Review on Watermarking Techniques Aiming Authentication of Digital Image Artistic Works Minted as NFTs into Blockchains 一个 NFT 的水印认证协议 可以引入第三方实体来实现对交易的认证&#xff0c;即通过使用 R S A \mathsf{RSA} RSA…

(十)JSP教程——config对象

config对象是脚本程序配置对象&#xff0c;表示当前JSP页面的配置信息。由于JSP页面通常无需配置&#xff0c;因此该对象在JSP页面中比较少见。 config对象可以读取一些初始化参数的值&#xff0c;而这些参数一般在web.xml配置文件中可以看到&#xff0c;并通过config对象的相应…

国内护眼台灯品牌哪些实用?推荐五款物美价廉的台灯品牌

近年来&#xff0c;我们注意到儿童近视的现象呈现出增多且趋于低龄化的趋势。这一变化&#xff0c;部分原因可以归咎于孩子们越来越多地使用电子产品&#xff0c;另一部分则与他们面临的学业压力增加有关。鉴于此&#xff0c;家长们在挑选儿童学习用品时变得格外谨慎&#xff0…

Sqli-labs第五~八关(布尔盲注)

目录 首先找到他们的闭合方式 操作 总结&#xff1a; 第五关根据页面结果得知是字符型但是和前面四关还是不一样是因为页面虽然有东西。但是只有对于请求对错出现不一样页面其余的就没有了。这个时候我们用联合注入就没有用&#xff0c;因为联合注入是需要页面有回显位。如果…

鸿蒙开发接口Ability框架:【@ohos.application.Want (Want)】

Want Want模块提供系统的基本通信组件的能力。 说明&#xff1a; 本模块首批接口从API version 8 开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import Want from ohos.application.Want; 开发前请熟悉鸿蒙开发指导文档&#xff1…

nginx--rewrite

功能 Nginx服务器利用ngx_http_rewrite_module 模块解析和处理理rewrite请求&#xff0c;此功能依靠PCRE(Perl Compatible Regular Expressions)&#xff0c;因此编译之前要安装PCRE库&#xff0c;rewrite是nginx服务器的重要功能之一&#xff0c;用于实现URL的重写&#xff0…

《Video Mamba Suite》论文笔记(4)Mamba在时空建模中的作用

原文翻译 4.4 Mamba for Spatial-Temporal Modeling Tasks and datasets.最后&#xff0c;我们评估了 Mamba 的时空建模能力。与之前的小节类似&#xff0c;我们在 Epic-Kitchens-100 数据集 [13] 上评估模型在zero-shot多实例检索中的性能。 Baseline and competitor.ViViT…

【网络编程】UDP协议和TCP协议1

UDP协议格式 UDP 报文分为 UDP 报头和 UDP 数据区两部分。报头由 4 个 16 位长&#xff08;2字节&#xff09;字段组成&#xff0c;分别说明该报文的源端口、目的端口、报文长度和校验值。 UDP协议如何将报头和有效载荷分离 UDP报头是一种定长报头&#xff0c;长度为8个字节。…

QCC3071/QCC3081/QCC3083/QCC3084/QCC5171/QCC5181/QCC3091/QCC3095平台LDAC解码

QCC3071/QCC3081/QCC3083/QCC3084/QCC5171/QCC5181/QCC3091/QCC3095平台LDAC解码 LDAC Decoder Evaluation Kit for QCC5181 and QCC5171 (The 5181 Kit) 随着Qualcomm DSP向下开放&#xff0c;QCC3071/QCC3081/QCC3083/QCC3084目前可以可以实现LDAC Decoder。 QCC3071/QCC3…

【Shell脚本】Shell编程之循环语句

目录 一.循环语句 1.for语句的结构 1.1.格式 1.2.实操案例 案例1. 案例2. 案例3. 案例4. 2.while语句的结构 2.1.格式 2.2.实操案例 案例1. 案例2. 案例3. 案例4. 3.until循环命令 3.1.格式 3.2.实操案例 案例1. 二.补充 1.常用转义符 一.循环语句 1.for…

56 关于 linux 的 oom killer 机制

前言 这里主要讲的是 linux 的 oom killer 机制 在系统可用内存较少的情况下&#xff0c;内核为保证系统还能够继续运行下去&#xff0c;会选择杀掉一些进程释放掉一些内存。 通常oom_killer的触发流程是&#xff1a;进程A想要分配物理内存&#xff08;通常是读写内存&#…

笨方法自学python(九)-读写文件

读取文件 前面已经学过了 input 和 argv&#xff0c;这些是你开始学习读取文件的必备基础。你可能需要多多实验才能明白它的工作原理&#xff0c;这节练习涉及到写两个文件。一个正常的 ex15.py 文件&#xff0c;另外一个是 ex15_sample.txt&#xff0c;第二个文件并不是脚本&…

图像锐化——非锐化掩膜USM和锐化掩膜SM(附代码)

非锐化掩膜 (USM) 和锐化掩膜 (SM) 都是常用的图像锐化技术。它们都可以通过增强图像的边缘信息来提高图像的清晰度。 目录 一、非锐化掩膜USM1.1 USM原理1.2 USM实现步骤1.3 优点1.4 代码 二、锐化掩膜SM2.1 SM原理2.2 SM实现步骤2.3 优点2.4 代码 三、锐化效果四、总结4.1 效…