【C++】——list的介绍和模拟实现

news2024/11/27 22:27:49
P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。
P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。

  

在这里插入图片描述

                                           博主主页:Yan. yan.
                                              C语言专栏
                                            数据结构专栏
                                         力扣牛客经典题目专栏
                                                     C++专栏

文章目录

  • 一、 list的介绍和使用
    • 1.1、list的介绍
    • 1.2、list的使用
      • 1.2.1、list的构造
      • 1.2.2、list iterator的使用
      • 1.2.3、list capacity(容量相关)
      • 1.2.4、list element access(元素访问)
      • 1.2.5、list modifiers(链表修改)
      • 1.2.6、list operation(对链表的一些操作)
  • 二、list的模拟实现
    • 2.1、list的节点
    • 2.2、list的成员变量
    • 2.3、list的迭代器
      • 2.3.1、普通迭代器
      • 2.3.2、const迭代器
    • 2.4、list的成员函数
      • 2.4.1、构造函数
      • 2.4.2、拷贝构造函数
      • 2.4.3、赋值运算符重载
      • 2.4.4、push_back
      • 2.4.5、迭代器相关
      • 2.4.6、 insert
      • 2.4.7、erase
      • 2.4.8、 push_front
      • 2.4.10、pop_front
      • 2.4.11、 size
      • 2.4.12、clear
      • 2.4.13、析构函数

一、 list的介绍和使用

1.1、list的介绍

  • list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  • list 的底层是双向链表结构,双向链表中的每个元素存储在互不相关的独立节点中,在节点中通过指针指向的前一个元素和后一个元素。

1.2、list的使用

  list的文本介绍
  list在实际中非常重要,在实际中我们熟悉常用的接口就可以,下面列出了需要我们重点掌握的接口。

1.2.1、list的构造

构造函数接口说明
list()list 的默认构造,构造空的 list
list(size_type n, const value_type& val = value_type())构造的 list 中包含 n 个值为 val 的元素
list(const list& x)拷贝构造函数
list(InputIterator first, InputIterator last)用[first,last)区间中的元素构造 list
void TestList1()
{
    list<int> l1;                         // 构造空的l1
    list<int> l2(4, 100);                 // l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                    // 用l3拷贝构造l4

    // 以数组为迭代器区间构造l5
    int array[] = { 16,2,77,29 };
    list<int> l5(array, array + sizeof(array) / sizeof(int));

    // 列表格式初始化C++11
    list<int> l6{ 1,2,3,4,5 };

    // 用迭代器方式打印l5中的元素
    list<int>::iterator it = l5.begin();
    while (it != l5.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // C++11范围for的方式遍历
    for (auto& e : l5)
        cout << e << " ";

    cout << endl;
}

1.2.2、list iterator的使用

函数声明接口说明
begin() + end()返回第一个元素的迭代器 + 返回最后一个元素下一个位置的迭代器
rebegin() + rend()返回第一个元素的 reverse_iterator,即 end 位置,返回最后一个一个元素下一个位置的 reverse_iterator,即 begin 位置

注意: begin 与 end 为正向迭代器,对迭代器执行 ++ 操作,迭代器向后移动。rbegin 与 rend 为反向迭代器,对迭代器执行 ++ 操作,迭代器向前移动。由于 list 的底层物理空间并不连续,所以 list 的迭代器不再是原生指针,并且 list 的迭代器没有对 + 和 - 进行重载,只重载了 ++ 和 – ,因为空间不连续,重载 + 会比较复杂。即 l.begin() + 5 是不被允许的。

void PrintList(const list<int>& l)
{
    // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
    for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
    {
        cout << *it << " ";
        // *it = 10; 编译不通过
    }

    cout << endl;
}

void TestList2()
{
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    // 使用正向迭代器正向list中的元素
    // list<int>::iterator it = l.begin();   // C++98中语法
    auto it = l.begin();                     // C++11之后推荐写法
    while (it != l.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 使用反向迭代器逆向打印list中的元素
    // list<int>::reverse_iterator rit = l.rbegin();
    auto rit = l.rbegin();
    while (rit != l.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
}

1.2.3、list capacity(容量相关)

函数声明接口说明
empty检测 list 是否为空,是返回 true,否则返回 false
size返回 list 中有效节点个数

1.2.4、list element access(元素访问)

函数声明接口说明
front返回 list 的第一个节点中值的引用
back返回 list 的最后一个节点中值的引用

1.2.5、list modifiers(链表修改)

函数声明接口说明
push_front在 list 的第一个节点前插入值为 val 的节点
pop_front删除 list 中第一个节点
push_back在 list 尾部插入一个值为 val 的节点
pop_back删除 list 中最后一个节点
insert在 list 的 position 位置中插入一个值为 val 的节点
erase删除 list position 位置的节点
swap交换两个 list 的节点
clear清空 list 中的有效元素

1.2.6、list operation(对链表的一些操作)

函数声明接口说明
reverse对链表进行逆置
sort对链表中的元素进行排序(稳定排序)
merge对两个有序的链表进行归并,得到一个有序的链表
unique对链表中的元素去重
remove删除具有特定值的节点
splice将 A 链表中的节点转移到 B 链表

二、list的模拟实现

2.1、list的节点

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

	ListNode(const T& val = T())
	{
		_next = nullptr;
		_prev = nullptr;
		_val = val;
	}
};

2.2、list的成员变量

class list
{
	typedef ListNode<T> Node;
public:
	//一些成员函数
private:
	Node* _head;
}

2.3、list的迭代器

  list 的迭代器不能再使用原生指针,如果 list 的迭代器使用原生指针的话,那对迭代器解引用得到的是一个节点,而我们希望对迭代器解引用可以得到节点里面存储的元素,并且 list 在底层的物理空间并不连续,如果使用原生指针作为 list 的迭代器,那对迭代器执行 ++ 操作,并不会让迭代器指向下一个节点。因此我们需要对 list 的迭代器进行封装,然后将一些运算符进行重载,以实现迭代器本该有的效果。

2.3.1、普通迭代器

template<class T>
struct _list_iterator
{
	typedef ListNode<T> Node;

	Node* _node;

	_list_iterator(Node* val)
	{
		_node = val;
	}

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

	T* operator-> ()//迭代器通过->应该指向节点中的元素,因此返回的是一个T类型的地址
	{
		return &(_node->_val);
	}

	bool operator!= (const _list_iterator<T>& right)
	{
		return _node != right._node;
	}

	_list_iterator<T> operator++()
	{
		_node = _node->_next;

		return *this;
	}

	_list_iterator<T> operator++(int)
	{
		_list_iterator<T> tmp(this->_node);

		_node = _node->_next;

		return tmp;
	}
};

  这里的类名不能直接叫 iterator,因为每种容器的迭代器底层实现可能都有所不同,即可能会为每一种容器都单独实现一个迭代器类,如果都直接使用 iterator,会导致命名冲突。其次,迭代器类不需要我们自己写析构函数、拷贝构造函数、赋值运算符重载函数,直接使用默认生成的就可以,言外之意就是这里使用浅拷贝即可,因为迭代器只是一种工具,它不需要对资源进行释放清理,资源释放清理工作是在容器类中实现的,浅拷贝的问题就出在会对同一块空间释放两次,而迭代器无需对空间进行释放,所以浅拷贝是满足我们需求的。

2.3.2、const迭代器

  上面我们实现了普通迭代器,那 const 迭代器该如何实现呢?直接在容器类里面写上一句 typedef const _list_iterator const_iterator 可以嘛?答案是不可以,const 迭代器本质是限制迭代器指向的内容不能修改,而 const 迭代器自身可以修改,它可以指向其他节点。前面这种写法,const 限制的就是迭代器本身,会让迭代器无法实现 ++ 等操作。那如何控制迭代指向的内容不能修改呢?可以通过控制 operator* 的返回值来实现。但是仅仅只有返回值类型不同,是无法构成函数重载的。那要怎样才能在一个类里面实现两个 operator* 让他俩一个返回普通的 T&,一个返回 const T& 呢?一般人可能想着那就再单独写一个 _list_const_iterator 的类,这样也行,就是会比较冗余,我们可以通过在普通迭代器的基础上,再传递一个模板参数,让编译器来帮们生成呀。除此之外, operator->也需要实现 const 版本,因此还需要第三个模板参数。

template<class T,class Ref, class Ptr>
struct _list_iterator
{
	typedef ListNode<T> Node;
	typedef _list_iterator<T, Ref, Ptr> self;

	Node* _node;

	_list_iterator(Node* val)
	{
		_node = val;
	}

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

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

	bool operator!= (const self& right) const
	{
		return _node != right._node;
	}
	bool operator== (const self& right) const
	{
		return _node == right._node;
	}


	self operator++()
	{
		_node = _node->_next;

		return *this;
	}

	self operator++(int)
	{
		self tmp(this->_node);

		_node = _node->_next;

		return tmp;
	}

	self operator--()
	{
		_node = _node->_prev;

		return *this;
	}

	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;

		return tmp;
	}
};

//operator->的使用场景
struct A
{
	A(int a = 0, int b = 0)
	{
		_a = a;
		_b = b;
	}

	int _a;
	int _b;
};

void Textlist3()
{
	wcy::list<A> l;
	l.push_back(A(1, 2));
	l.push_back(A(3, 4));
	l.push_back(A(5, 6));
	l.push_back(A(7, 8));

	wcy::list<A>::iterator it = l.begin();
	while (it != l.end())
	{
		cout << it->_a << ',' << it->_b << " ";
		cout << endl;
		it++;
	}
}

2.4、list的成员函数

2.4.1、构造函数

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

2.4.2、拷贝构造函数

list(const list& ll)
//list(const list<T>& ll)
{
	_head = new Node;
	_head->_prev = _head;
	_head->_next = _head;

	for (auto& e : ll)
	{
		push_back(e);
	}
}

2.4.3、赋值运算符重载

void swap(list<T> l2)
{
	std::swap(_head, l2._head);
}

list& operator=(const list ll)
//list<T>& operator=(const list<T> ll)
{
	//现代写法
	swap(ll);

	return *this;
}

2.4.4、push_back

void push_back(const T& val)
{
	//先找尾
	Node* tail = _head;
	while (tail->_next != _head)
	{
		tail = tail->_next;
	}

	//插入元素
	Node* newnode = new Node(val);
	tail->_next = newnode;
	newnode->_prev = tail;

	newnode->_next = _head;
	_head->_prev = newnode;
}

2.4.5、迭代器相关

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

iterator end()
{
	return _head;
}

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

const_iterator end() const
{
	return _head;
}

2.4.6、 insert

iterator insert(iterator pos, const T& val)
{
	//找到 pos 位置的前一个位置
	Node* cur = pos._node;
	Node* prev = cur->_prev;

	//插入元素
	Node* newnode = new Node(val);
	prev->_next = newnode;
	newnode->_prev = prev;

	newnode->_next = cur;
	cur->_prev = newnode;

	return newnode;
}

2.4.7、erase

iterator erase(iterator pos)
{
	assert(pos != end());
	Node* cur = pos._node;//保存当前节点
	Node* prev = cur->_prev;//保存前一个节点
	Node* next = cur->_next;//保存后一个节点
	
	prev->_next = next;
	next->_prev = prev;

	delete cur;
	cur = nullptr;

	return next;
}

2.4.8、 push_front

void pop_back()
{
	erase(--end());
}

2.4.10、pop_front

void pop_front()
{
	erase(begin());
}

2.4.11、 size

size_t size()
{
	size_t sz = 0;
	iterator it = begin();

	while (it != end())
	{
		it++;
		sz++;
	}

	return sz;
}

2.4.12、clear

void clear()
{
	iterator it = begin();

	while (it != end())
	{
		it = erase(it);
	}
}

2.4.13、析构函数

~list()
{
	clear();

	delete _head;
	_head = nullptr;
}

clear 和 析构函数的主要区别在于是否释放头节点。

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

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

相关文章

亚马逊云乱扣费,被不知不觉扣钱真的好气呀

之前申请了亚马逊云12个月免费&#xff0c;一直也没用&#xff0c;也没登录&#xff0c;今天突然看到银行扣费信息&#xff0c;扣了我21美元&#xff0c;已经扣了我几个月了&#xff0c;登录亚马逊后台&#xff0c;实例为0的情况下&#xff0c;每个月都能扣21美元&#xff0c;国…

【HTML+CSS】留言板plus实现全过程

创建一个具有动态留言的简约风格留言板 在本教程中&#xff0c;我们将学习如何创建一个简约风格的留言板&#xff0c;它具备动态留言显示和一些基本动画效果。这个留言板将使用HTML和CSS构建&#xff0c;最终实现一个既美观又实用的界面。 准备工作 首先&#xff0c;确保你的…

UNRAID使用rclone挂在alist网盘

UNRAID使用rclone挂在alist网盘 需求&#xff1a;考虑异地备份&#xff0c;将部分重要的资料上传至网盘&#xff0c;保证nas中的资料安全。 考虑&#xff1a;当然网盘备份存在安全性问题&#xff0c;后续也可以通过加密的方式进行上传&#xff0c;不过这是后话&#xff0c;有精…

Python常见问题解答:从基础到进阶

Python常见问题解答&#xff1a;从基础到进阶 Python 是一种简单易学、功能强大的编程语言&#xff0c;广泛应用于数据分析、Web 开发、自动化脚本、人工智能等领域。即便如此&#xff0c;Python 开发者在编写代码的过程中&#xff0c;常常会遇到各种各样的问题。本文将从基础…

java集合 -- 面试

Java集合框架体系 ArrayList底层实现是数组 LinkedList底层实现是双向链表 HashMap的底层实现使用了众多数据结构&#xff0c;包含了数组、链表、散列表、红黑树等 List ps : 数据结构 -- 数组 ArrayList源码分析 ArrayList底层的实现原理是什么? ArrayList list new…

HKMG工艺为什么要用金属栅极?

知识星球里的学员问&#xff1a;在HKMG工艺中&#xff0c;会用到HfO2等作为栅介质层&#xff0c;为什么不能再用多晶硅做栅极&#xff0c;而是改为金属栅极&#xff1f; 什么是HKMG工艺&#xff1f; HKMG&#xff08;High-K Metal Gate &#xff09;&#xff0c;是45nm&#…

《深度学习》OpenCV 背景建模 原理及案例解析

目录 一、背景建模 1、什么是背景建模 2、背景建模的方法 1&#xff09;帧差法(backgroundSubtractor) 2&#xff09;基于K近邻的背景/前景分割算法BackgroundSubtractorKNN 3&#xff09;基于高斯混合的背景/前景分割算法BackgroundSubtractorMOG2 3、步骤 1&#xff09;初…

利士策分享,年前如何合理规划,轻松搞点小钱?

利士策分享&#xff0c;年前如何合理规划&#xff0c;轻松搞点小钱&#xff1f; 随着春节的日益临近&#xff0c;不少人开始为过年期间的开销而犯愁。 如何在年前合理规划&#xff0c;轻松搞点小钱&#xff0c;成了大家热议的话题。 别担心&#xff0c;这里有几个既实用又不伤…

华为OD机试 - 分班问题(Java 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

华为OD机试 - 密室逃生游戏(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

杀疯啦-yolov11+deepsort的目标跟踪实现

目录 yolov11介绍——实时端到端物体检测 概述 主要特征 支持的任务和模式 性能指标 总结 deepsort介绍&#xff1a; yolov11结合deepsort实现目标跟踪 效果展示 训练与预测 UI设计 其他功能展示 完整代码实现UI界面 yolov11介绍——实时端到端物体检测 概述 YOLO…

IDEA 使用技巧与插件推荐

目录 前言1. IDEA 使用技巧1.1 快捷键优化与应用1.2 高效调试与日志分析1.3 代码模板与片段的自定义 2. 插件推荐2.1 MyBatisX2.2 Lombok2.3 CheckStyle-IDEA2.4 Key Promoter X2.5 GitToolBox2.6 Rainbow Brackets 3. IDEA 性能优化3.1 内存与堆栈设置3.2 禁用不必要的插件3.3…

论文翻译 | Language Models are Few-Shot Learners 语言模型是少样本学习者(中)

3 结果 在图3.1中&#xff0c;我们展示了第2节描述的8个模型的训练曲线。对于这个图表&#xff0c;我们还包括了6个额外的超小型模型&#xff0c;参数数量少至100,000。正如[KMH20]中观察到的&#xff0c;当有效利用训练计算时&#xff0c;语言建模性能遵循幂律。在将这个趋势再…

FreeRTOS篇15:中断管理

一.中断优先级 任何中断的优先级都大于任务&#xff01; 在我们的操作系统&#xff0c;中断同样是具有优先级的&#xff0c;并且我们也可以设置它的优先级&#xff0c;但是他的优先 级并不是从 015 &#xff0c;默认情况下它是从 515 &#xff0c;0~4 这 5 个中断优先级不是 F…

【逐行注释】PF(Particle filter,粒子滤波)的MATLAB代码(附源代码)

文章目录 程序设计1. 介绍2. 系统模型3. 算法步骤 源代码运行结果 程序设计 1. 介绍 粒子滤波是一种用于动态系统状态估计的先进方法&#xff0c;广泛应用于机器人定位、目标跟踪和金融预测等领域。该算法通过一组粒子及其权重来表示系统状态的概率分布&#xff0c;能够有效处…

JavaSE——面向对象8:Object类详解(==与equals的区别、hashCode、toString方法)

目录 一、与equals()的区别 (一)是一个比较运算符 (二)equals是Object类中的方法&#xff0c;只能判断引用类型 (三)equals方法重写练习 1.练习1 2.练习2 3.练习3 二、hashCode方法 三、toString方法 1.默认返回&#xff1a;全类名(包名类名)哈希值的十六进制 (1)不…

VS编译器实用调试技巧

一.什么是bug bug本意是"昆虫"或"虫子&#xff3d;&#xff0c;现在一般是指在电脑系统或程序中&#xff0c;隐藏着的一些未被发现的缺陷或问题&#xff0c;简称程序漏洞。“Bug"的创始人格蕾丝&#xff0e;赫柏&#xff08;Grace Murray Hopper)&#xff…

算法笔记(七)——哈希表

文章目录 两数之和判定是否互为字符重排存在重复元素存在重复元素 II字母异位词分组 哈希表&#xff1a;一种存储数据的容器&#xff1b; 可以快速查找某个元素&#xff0c;时间复杂度O(1)&#xff1b; 当频繁查找某一个数时&#xff0c;我们可以使用哈希表 创建一个容器&#…

堆的代码和基础知识

堆的向上和向下调整-CSDN博客 1.堆的基础知识 2.堆的代码 Heap.h #pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> #include<time.h>// typedef int HPDataType; typedef struct Heap {HPDataTy…

电场磁场,能量密度和平均值(定义式是用实数场计算的,不能直接将定义式里面修改为复数场)

能量密度的定义式&#xff0c;都是实数场 平均能量密度&#xff0c;里面的是复数表示的场。具体推导类似坡印廷矢量 、