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

news2024/10/5 21:14:31

                                                

🔥个人主页:北辰水墨

🔥专栏: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 100

	second.assign(first.begin(), first.end()); // a copy of first

	int myints[] = { 1776,7,4 };
	first.assign(myints, myints + 3);            // assigning from array
	for (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 5

    it = 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 90
  it1 = 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 cur
	prev->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);  //交换头节点和_size
	return *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 cur
			prev->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 cur
			prev->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.coloradmin.cn/o/1660851.html

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

相关文章

winform植物大战僵尸

winform植物大战僵尸 植物大战僵尸源码 半成品 需要的拿去学习 登陆注册选择关卡 向日葵 豌豆射手 双枪豌豆射手 项目获取&#xff1a; 项目获取&#xff1a;typora: typora/img (gitee.com) 备用项目获取链接1&#xff1a;yifeiyixiang/kamo: 源码下载 (github.com) 备用…

Python图形复刻——绘制母亲节花束

各位小伙伴&#xff0c;好久不见&#xff0c;今天学习用Python绘制花束。 有一种爱&#xff0c;不求回报&#xff0c;有一种情&#xff0c;无私奉献&#xff0c;这就是母爱。祝天下妈妈节日快乐&#xff0c;幸福永远&#xff01; 图形展示&#xff1a; 代码展示&#xff1a; …

一定行:从零起步进入Java世界

郑重声明&#xff1a;本篇博客唯一目的就是带你从零起步&#xff0c;成功编写并运行你的第一个Java应用。 零&#xff1a;先给祖师爷来上柱香 Java之父简介 英文名&#xff1a;James Gosling中文名&#xff1a;詹姆斯高斯林祖籍&#xff1a;加拿大出生年&#xff1a;1955照片…

INS 论文分享:一种用于交通流预测的多通道时空Transformer模型

本文主要介绍了我们在长期交通流预测方面的最新研究成果&#xff0c;该成果已发表在信息学领域的顶级期刊《Information Sciences》上&#xff0c;论文题目为《A Multi-Channel Spatial-Temporal Transformer Model for Traffic Flow Forecasting》。该论文的第一作者及通讯作者…

Android项目转为鸿蒙,真就这么简单?

最近做了一个有关Android转换成鸿蒙的项目。经不少开发者的反馈&#xff1b;许多公司的业务都增加了鸿蒙板块。 对此想分享一下这个项目转换的流程结构&#xff0c;希望能够给大家在工作中带来一些帮助。转换流程示意图如下&#xff1a; 下面我就给大家介绍&#xff0c;Android…

Android 屏幕适配全攻略(上)-掌握屏幕单位,应对千变万化的设备

本文从 Android 开发中常见的长度单位 px、dp、sp 入手&#xff0c;详细介绍了它们的特点及转换关系。 接着深入探讨了屏幕尺寸、分辨率、像素密度等重要的屏幕指标&#xff0c;帮助读者全面理解它们之间的联系。最后&#xff0c;通过实例代码演示了如何在代码中进行单位转换&…

UE4\UE5 调试源代码流程(重点讲不去Github装源代码情况)

UE4\UE5 调试源代码流程 前言&#xff1a; 很多写UE C代码的小伙伴&#xff0c;肯定发现了&#xff0c;在虚幻源代码里面是没办法打断点进行调试的&#xff0c;就算走Debug调试流程&#xff0c;也依旧不能正常打断点调试&#xff0c;今天我们来分享一下不装Github源代码情况下…

Python语言基础学习(上)

目录 一、常量和表达式 二、变量和类型 2.1 认识变量 2.2 定义变量 2.3 变量类型 1、整数 int 2、浮点数&#xff08;小数&#xff09;float 3、字符串 str 4、布尔类型 2.4 类型转换 三、注释 3.1 单行注释 3.2 文档注释&#xff08;或者多行注释&#xff09; …

五金建材微信小程序商城系统开发搭建指南

如今&#xff0c;随着移动互联网的发展&#xff0c;小程序成为了商家们开拓新市场、增加收益的重要途径。特别是对于五金店这类实体店铺来说&#xff0c;通过小程序开设线上商城&#xff0c;不仅可以提升品牌影响力&#xff0c;还能够实现线上线下的无缝对接&#xff0c;为店家…

二、SPI协议

文章目录 总述1.SPI接口2. SPI工作模式3. SPI通信时序4. SPI协议 对比 UART协议&#xff08;上一篇文章刚介绍过uart协议&#xff0c;这里来对比一下&#xff09; 总述 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种高速的、全双工、同步的串行通信总线&…

2024智能投影仪怎么选?大眼橙C1D,高清高亮高性价比

在这个科技飞速发展的时代&#xff0c;家庭智能化已经成为一种趋势。大眼橙C1D&#xff0c;2024年最新上市的一款智能投影仪&#xff0c;正以其独特的魅力&#xff0c;引领着智能家居的新潮流。 一、外观设计&#xff1a;简约而不简单 大眼橙C1D的外观设计采用了简约风格&…

pdffactory pro8.0虚拟打印机(附注册码)

PdfFactory pro是一款非常受欢迎的PDF虚拟打印机&#xff0c;可以帮助用户将你的其他文档保存为PDF格式。请为用户提供打印/发送/加密等多种实用功能&#xff0c;以及一套完善的PDF打印方案。 使用说明 下载pdfFactory Pro压缩包&#xff0c;解压后&#xff0c;双击exe文件&am…

2024数维杯A题可运行思路代码文章成品

为了能够精确地确定飞行器在三维空间中的位置&#xff0c;理论上至少需要从三个不同位置的发射源接收TOA数据。下面是使用TOA数据确定位置所需的计算基础和原理&#xff1a; 单个TOA数据&#xff1a; 单个TOA测量可以确定接收器与发射源之间的距离&#xff0c;这在三维空间中形…

error C2039: “NotifySeverity“: 不是 “osg“ 的成员 问题分析

程序从osg3.6.5Qt5.9osgearth2.10环境中移植到osg3.7.0Qt5.15.2osgearth3.3环境中&#xff0c;出现了无尽的错误。 有些错误很莫名奇妙&#xff0c;比如下述错误&#xff1a; D:\OsgEarth3.3\include\osgEarth\Notify(34,53): error C2039: "NotifySeverity": 不是 &…

Vue3专栏项目 -- 二、自定义From组件(下)

需求分析&#xff1a; 现在我们还需要一个整体的表单在单击某个按钮的时候可以循环的验证每个input的值&#xff0c;最后我们还需要有一个事件可以得到最后验证的结果&#xff0c;从而进行下一步的操作 如下&#xff0c;我们应该有一个form表单包裹着全部的input表单&#xf…

分布式模式让业务更高效、更安全、更稳定

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;分布式模…

ICode国际青少年编程竞赛- Python-4级训练场-while语句入门

ICode国际青少年编程竞赛- Python-4级训练场-while语句入门 1、 while Flyer.disappear():wait() Dev.step(2)2、 Dev.step(1) while Flyer.disappear():wait() Dev.step(5)3、 while Flyer[0].disappear():wait() Dev.step(3) Dev.step(-1) while Flyer[0].disappear():…

VM虚假机联网(无代码,超简单)NAT模式

1、左边顶上编辑里面最下面找到虚拟网络编辑器2.启用管理员特权3.重新创建一个NAT模式的网络&#xff08;名称随便一个&#xff09; 4.打开这两个设置里面的东西进行拍照并记住IP区间和网关&#xff0c;等下要用&#xff1b; 5.打开虚拟机&#xff0c;右上角&#xff0c;下标点…

万物生长大会 | 创邻科技再登杭州准独角兽榜单

近日&#xff0c;由民建中央、中国科协指导&#xff0c;民建浙江省委会、中国投资发展促进会联合办的第八届万物生长大会在杭州举办。 在这场创新创业领域一年一度的盛会上&#xff0c;杭州市创业投资协会联合微链共同发布《2024杭州独角兽&准独角兽企业榜单》。榜单显示&…

怎么用照片制作gif动图?一个网站在线做

在数字图像处理中&#xff0c;动态图片是我们日常生活中不可缺少的一部分。Gif动图以为器画面展示的形式&#xff0c;文件的体积以及兼容性而备受喜爱。通过使用多张照片制作gif动画的操作&#xff0c;可以让我们制作出生地有趣的gif动态效果&#xff0c;能够更好更快的传达信息…