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

list模拟实现

本文中我们将来模拟实现一下STL中的list,在STL中使用的是带头节点的双向链表结构。

list的节点结构体和构造函数

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

	list_node(const T& x = T()) // 用来插入数据的时候进行构造初始化,这里初始化需要使用的是T()匿名对象,不能使用0这种内置类型
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
	{}
};

list的构造函数

list() // 创建一个带有头结点的链表,表头表位都指向自己
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
}

尾插、头插、尾删、头删这几个方法都与我们之前数据结构中编写的双向带头循环链表相类似,这里就不过多赘述。

我们这里主要介绍的是迭代器相关内容。

迭代器

在之前的string与vector这两个类中我们迭代器使用的都是指针,因为在这两个结构中所占有的空间都是连续的,通过指针的++就可以获取到下一个元素的地址。但是在这里由于list所占有的空间是分开的那么就会有一个问题:加入我们将跟之前一样将指针++的话,得到的不知道是哪里的地址,无法获得我们想要的下一个数据的地址。这里就需要将节点的指针进行封装,然后就可以在这个封住的类中重载++方法。

迭代器的构造函数

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* n)
		:_node(n)
	{}

    Ref operator*()
    {
	    return _node->_data;
    }
    Ptr operator->()
    {
	    return &_node->_data;
    }
    self& operator++()
    {
	    _node = _node->_next;
	    return *this;
    }
    bool operator==(const self& s)
    {
	    return _node == s._node;
    }

通过将节点的指针进行封装后,重新给这个类重载++让迭代器的++能够指向下一个数据,通过这种方式我们可以控制各种操作。

然后就可以在list类中进行迭代器的处理:

// 迭代器浅拷贝不会失效的原因是没有编写析构函数,不需要释放节点
iterator begin()
{
	//iterator it(_head->_next);
	//return it;
	return iterator(_head->_next); // 这里返回时可以直接使用匿名对象进行返回,这里使用的是默认生成的浅拷贝,可以传给使用者begin的数据且不会让使用这对数据进行修改。
}

iterator end()
{
	//iterator it(_head);
	//return it;
	return iterator(_head);
}

接下来就是const迭代器的问题,有了上述基本的迭代器,我们就试想将const迭代器直接仿照普通迭代器,写一份const迭代器,这样可以通过编译且能够运行。但是可以发现,这样操作之后代码的迭代器部分有大量重复的代码,非常的不好。因此我们想要通过在模板里放入新的模板参数来解决控制const迭代器的返回值的问题。通过模板参数使T&和const T&进行复用。

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;
    typedef const __list_iterator<T> const_iterator; // 这里我们不能写成这个样子,因为这样就会让迭代器本身无法改变,而我们需要让迭代器进行++操作
}

可以发现在模板参数中还有一个T*参数这个是为了解决重载 -> 的问题:

class AA
{
public:
	AA(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{}
	int _a1;
	int _a2;
};

void test_list2()
{
	list<AA> l1;
	l1.push_back(AA(1, 1));
	l1.push_back(AA(2, 2));
	l1.push_back(AA(3, 3));
	
	// AA* ptr
	list<AA>::iterator it = l1.begin();
	while (it != l1.end())
	{
		//cout << (*it)._a1 << ":" << (*it)._a2 << endl;
        // 会发现这里调用感觉缺少了一个->		
		cout << it->_a1 << ":" << it->_a2 << endl; // 本来应该是->->但是编译器优化了,为了增加可读性,省略了一个->
		cout << it.operator->()->_a1 << ":" << it.operator->()->_a1 << endl;
		++it;
	}
	cout << endl;

list迭代器失效的问题

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

list的拷贝构造和赋值重载

template <class Iterator>
list(Iterator first, Iterator last)
{
	empty_init();

	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

void swap(list<T>& tmp)
{
	empty_init(); // 这里需要注意头地址是否有意义的问题

	std::swap(_head, tmp._head);
}

list(const list<T>& lt)
{
	list<T> tmp(lt.begin(), lt.end());
	swap(tmp);
}

// 这里不能使用&,会对原数据进行修改,这是本不需要的
list<T>& operator=(list<T> lt) // 这里的swap需要对内部的头指针进行修改所以不能使用const修饰
// 这里借用了拷贝构造在lt这里发生了拷贝构造,然后让lt与需要被赋值的进行交换,得到我们想要的结果
{
	swap(lt);
	
	return *this;
}

http://www.kler.cn/a/6641.html

相关文章:

  • 【AI学习】Huggingface复刻Test-time Compute Scaling技术
  • 两点间最短距离 - Dijkstra
  • 用SparkSQL和PySpark完成按时间字段顺序将字符串字段中的值组合在一起分组显示
  • 使用 datamodel-code-generator 从 MySQL 生成 Python 模型
  • BPMN与一般的流程图区别在那里?
  • SQL语句整理五-StarRocks
  • 00后面试华为软件测试工程师,竭尽全力拿到15K。。。。。
  • 解析安装程序使用指南
  • 华为OD机试-最优资源分配-2022Q4 A卷-Py/Java/JS
  • 美团暑期实习
  • 【python设计模式】10、组合模式
  • 从大厂到创业公司,管理上需要怎样转变?
  • ChatGPT背后的技术和多模态异构数据处理的未来展望——我与一位资深工程师的走心探讨
  • 获取元素通常使用的两种方式
  • 【华为机试真题详解JAVA实现】—坐标移动
  • 携手共进 智享未来丨美格智能2023年代理商合作伙伴大会成功举办
  • MAC干净卸载IDEA
  • 阿里9年测试经验告诉你:作为一名年薪40w自动化测试需要具备那些能力
  • PLC - 笔记
  • 嵌入式--C++程序入门
  • 2022国赛22:免密码ssh登录到其他Linux主机
  • 机械硬盘的工作原理
  • Idea 插件、快捷键
  • O2O、C2C、B2B、B2C是什么意思
  • IDE装上ChatGPT,一天开发一个系统
  • 【python设计模式】18、仲裁者模式