【C++】List模拟实现过程中值得注意的点

news2025/1/16 12:42:41

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.List迭代器

2.适配器

3.迭代器失效

4.模拟实现源码


前言

本篇文章旨在记录博主在模拟实现vector容器中遇到的一些问题,都是一些需要注意的细节问题,希望与大家共勉。


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。

=========================================================================

GITEE相关代码:🌟fanfei_c的仓库🌟

=========================================================================


1.List迭代器

在之前模拟实现『 String和Vector』时,我们都采用的『 原生指针』实现迭代器,那么到List这里还可以么?

这我们就要搞清楚『 迭代器存在的意义』。

我们希望可以通过迭代器来实现对某一容器的遍历访问,例如对迭代器++或--;

迭代器:让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。

之前的String和Vector我们可以简单的利用原生指针实现迭代器是因为String和Vector底层的物理空间是连续的,所以++和--当然可以访问到对应的对象。

可到了List中,List的底层是带头双向循环链表,链表的物理空间并『 不连续』,所以我们需要对迭代器进行一定的『 封装』,让迭代器『 符合统一的迭代器使用规则』。

如何封装呢?

提示:比如重载各种运算符,++运算符的底层可以为链表的p=p->next;

注意:end迭代器是最后一个元素的下一个位置,所以end一般指向的是『 头节点』。 

  • 头节点不存储数据。

 

// List的节点类
template<class T>
struct ListNode
{
    ListNode<T>* _next;
    ListNode<T>* _prev;
    T _data;
    ListNode(const T& val = T())
        :_next(nullptr)
        , _prev(nullptr)
        , _data(val)
    {}
};
//List的迭代器类
template<class T, class Ref, class Ptr>
class __list_iterator
{
public:
    typedef ListNode<T> Node;
    typedef __list_iterator<T, Ref, Ptr> Self;
    Node* _node;
    __list_iterator(Node* x)
        :_node(x)
    {}

    Ref operator*()
    {
        return _node->_data;
    }
    Ptr operator->()
    {
        return &_node->_data;
    }
    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& s)
    {
        return _node != s._node;
    }
    bool operator==(const Self& s)
    {
        return _node == s._node;
    }
};

2.适配器

我们知道迭代器一般需要两个形式,一种为非const迭代器,一种为const迭代器用来满足不同的使用场景,但你有没有发现上面的代码,似乎只有一种形式???

其实并不是,如果我们按照以前的写法,应为:

//非const迭代器类
template<class T>
class __list_iterator
{
public:
    typedef ListNode<T> Node;
    typedef __list_iterator<T> Self;
    Node* _node;
    __list_iterator(Node* x)
        :_node(x)
    {}

    T& operator*()
    {
        return _node->_data;
    }
    T* operator->()
    {
        return &_node->_data;
    }
    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& s)
    {
        return _node != s._node;
    }
    bool operator==(const Self& s)
    {
        return _node == s._node;
    }
};
//const迭代器类
template<class T>
class __list_const_iterator
{
public:
	typedef ListNode<T> Node;
	typedef __list_const_iterator<T> self;
	Node* _node;
	__list_const_iterator(Node* x)
		:_node(x)
	{}

    const T& operator*()
    {
        return _node->_data;
    }
    const T* operator->()
    {
        return &_node->_data;
    }
	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& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

但这样看起来代码非常冗余,那么有没有什么方式来简化代码呢?

其实模板的用法不仅有vector<int>这类,还可以这么使用:


所以,编译器可以通过模板自动推演出当前使用场景为const迭代器还是非const迭代器。

这种方式可以唤作为『 适配器』,当然这部分后面还会有涉及。


3.迭代器失效

在之前学习vector时我们发现了迭代器失效的问题,对于vector来讲迭代器失效往往发生在扩容之后,那么对于List来讲,是不需要扩容的,那么List会不会发生迭代器失效的问题呢?

List可能会在『 erase』操作之后,迭代器失效。

我们来分析一下:

vector迭代器失效的原因是因为扩容导致迭代器使用前后底层指针位置发生了改变,换句话说就是开辟了新的空间,指针指向了新的地址,而迭代器没有更新从而导致迭代器失效。

而List并不会扩容,也不会开辟新的连续的空间,所以对于插入来说无从谈起迭代器失效,而erase是会导致迭代器失效的,因为删除了该位置的对象,迭代器底层指针成为野指针,但并不会影响其他迭代器,只需要重新赋值即可,比如设计为删除当前迭代器位置对象,并指向下一位置:

// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
    assert(pos != end());//list是带头双向循环链表,当pos是end()位置时,证明没有可删除的节点了

    Node* cur = pos._node;
    Node* prev = cur->_prev;
    Node* next = cur->_next;
    prev->_next = next;
    next->_prev = prev;

    delete cur;
    return next;
}

4.->操作符的重写

->操作符的重写是一个特例,大家只需要记住即可,因为这是STL设计者故意设计为这样的。

Ptr operator->()//为什么返回的是地址??
{
    return &_node->_data;
}

那放在实际的场景中我们使用一下看看: 

struct AA
{
    int _a1;
    int _a2;

    AA(int a1 = 1, int a2 = 1)
        :_a1(a1)
        , _a2(a2)
    {}
};

void test_list5()
{
    list<AA> lt;
    AA aa1;
    lt.push_back(aa1);

    lt.push_back(AA());

    AA aa2(2, 2);
    lt.push_back(aa2);

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

    list<AA>::iterator it = lt.begin();
    while (it != lt.end())
    {
        cout << it->_a1 << ":" << it->_a2 << endl;
        //这里打印的是_a2的内容并不是_a2的地址
        ++it;
    }
    cout << endl;
}

正常来讲这里本来是应该有两个->的,第一个箭头是it->去调用重载的operator->返回AA* 的指针,第二个箭头是AA* 的指针去访问对象当中的成员变量_a2。

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

但是为了程序可读性,所以编译器做了特殊识别处理,省略了一个箭头。 


5.模拟实现源码

#pragma once

#include<iostream>
#include<assert.h>

namespace F
{
    // List的节点类
    template<class T>
    struct ListNode
    {
        ListNode<T>* _next;
        ListNode<T>* _prev;
        T _data;
        ListNode(const T& val = T())
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(val)
        {}
    };


    //List的迭代器类
    template<class T, class Ref, class Ptr>
    class __list_iterator
    {
    public:
        typedef ListNode<T> Node;
        typedef __list_iterator<T, Ref, Ptr> Self;
        Node* _node;
        __list_iterator(Node* x)
            :_node(x)
        {}

        Ref operator*()
        {
            return _node->_data;
        }
        Ptr operator->()
        {
            return &_node->_data;
        }
        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& s)
        {
            return _node != s._node;
        }
        bool operator==(const Self& s)
        {
            return _node == s._node;
        }
    };

    //list类
    template<class T>
    class list
    {
        typedef ListNode<T> Node;
    public:
        typedef __list_iterator<T, T&, T*> iterator;
        typedef __list_iterator<T, const T&, const T*> const_iterator;
    public:
        ///
        // List的构造
        void empty_init()
        {
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }
        list()
        {
            empty_init();
        }
        list(int n, const T& value = T())
        {
            empty_init();
            while (n--)
            {
                push_back(value);
            }
        }

        list(const list<T>& l)
        {
            empty_init();
            for (const auto& e : l)
            {
                push_back(e);
            }
        }
        list<T>& operator=(list<T> l)
        {
            swap(l);
            return *this;
        }

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


        ///
        // List Iterator
        iterator begin()
        {
            return _head->_next;
        }
        iterator end()
        {
            return _head;
        }
        const_iterator begin() const
        {
            return _head->_next;
        }
        const_iterator end() const
        {
            return _head;
        }

        ///
        // List Capacity
        size_t size()const
        {
            size_t count = 0;
            const_iterator it = begin();
            while (it != end())
            {
                ++count;
                ++it;
            }
            return count;
        }
        bool empty()const
        {
            return _head->_next == _head;
        }


        
        // List Access
        T& front()
        {
            return *begin();
        }
        const T& front()const
        {
            return *begin();
        }
        T& back()
        {
            return *(--end());
        }
        const T& back()const
        {
            return *(--end());
        }


        
        // List Modify
        void push_back(const T& val) { insert(end(), val); }    
        void pop_back() { erase(--end()); }
        void push_front(const T& val) { insert(begin(), val); }
        void pop_front() { erase(begin()); }
        // 在pos位置前插入值为val的节点
        iterator insert(iterator pos, const T& val)
        {
            Node* cur = pos._node;
            Node* prev = cur->_prev;
            Node* newnode = new Node(val);

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

            //return iterator(newnode);
            return newnode;//单参数的构造函数支持隐式类型转换
        }
        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos)
        {
            assert(pos != end());//list是带头双向循环链表,当pos是end()位置时,证明没有可删除的节点了

            Node* cur = pos._node;
            Node* prev = cur->_prev;
            Node* next = cur->_next;
            prev->_next = next;
            next->_prev = prev;

            delete cur;
            return next;
        }
        void clear()
        {
            iterator it = begin();
            while (it != end())
            {
                it = erase(it);
            }
        }
        void swap(list<T>& l)
        {
            std::swap(_head, l._head);
        }
    private:
        Node* _head;
    };
};

模拟实现的主要目的在于学习优秀的代码,比如这里适配器的使用、优秀的框架设计。

注意为了迭代器统一使用:

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

这样我们就可以不需要知道迭代器的底层是如何实现的,只需要调用迭代器的接口就可以了,从而实现了通用的目的。


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

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

相关文章

0004.电脑开机提示按F1

常用的电脑主板不知道什么原因&#xff0c;莫名其妙的启动不了了。尝试了很多方法&#xff0c;没有奏效。没有办法我就只能把硬盘拆了下来&#xff0c;装到了另一台电脑上面。但是开机以后却提示F1&#xff0c;如下图&#xff1a; 根据上面的提示&#xff0c;应该是驱动有问题…

力扣645.错误的集合

一点一点地刷&#xff0c;慢慢攻克力扣&#xff01;&#xff01; 王子公主请看题 集合 s 包含从 1 到 n 的整数。不幸的是&#xff0c;因为数据错误&#xff0c;导致集合里面某一个数字复制了成了集合里面的另外一个数字的值&#xff0c;导致集合 丢失了一个数字 并且 有一个数…

基于springboot+vue仓库管理系统

摘要 本文介绍了一种基于Spring Boot和Vue的现代化仓库管理系统的设计与实现。仓库管理是企业运营中至关重要的一环&#xff0c;它涉及到货物的进出、库存的管理以及订单的处理等方面。为了提高仓库管理的效率和精确度&#xff0c;我们设计了这个集成了前后端技术的系统。在系统…

IntelliJ IDEA 常用快捷键一览表(通用型,提高编写速度,类结构、查找和查看源码,替换与关闭,调整格式)

文章目录 IntelliJ IDEA 常用快捷键一览表1-IDEA的日常快捷键第1组&#xff1a;通用型第2组&#xff1a;提高编写速度&#xff08;上&#xff09;第3组&#xff1a;提高编写速度&#xff08;下&#xff09;第4组&#xff1a;类结构、查找和查看源码第5组&#xff1a;查找、替换…

数据操作——缺失值处理

缺失值处理 缺失值的处理思路 如果想探究如何处理无效值, 首先要知道无效值从哪来, 从而分析可能产生的无效值有哪些类型, 在分别去看如何处理无效值 什么是缺失值 一个值本身的含义是这个值不存在则称之为缺失值, 也就是说这个值本身代表着缺失, 或者这个值本身无意义, 比如…

Spring成长之路—Spring MVC

在分享SpringMVC之前&#xff0c;我们先对MVC有个基本的了解。MVC(Model-View-Controller)指的是一种软件思想&#xff0c;它将软件分为三层&#xff1a;模型层、视图层、控制层 模型层即Model&#xff1a;负责处理具体的业务和封装实体类&#xff0c;我们所知的service层、poj…

智慧文旅一机游:科技与文化的完美结合,引领智慧文旅新潮流,智慧旅游未来已来

一、科技与文化的完美结合&#xff1a;智慧文旅一机游的核心理念 智慧文旅一机游&#xff0c;是科技与文化相融合的产物&#xff0c;它不仅代表着旅游行业的创新与发展&#xff0c;更是一种文化与科技完美结合的生活方式。一机游的核心理念在于通过先进的科技手段&#xff0c;提…

HttpServletRequest getServerPort()、getLocalPort() 、getRemotePort() 区别

getRemotePort() 、getServerPort()、getLocalPort() request.getServerPort()、request.getLocalPort() 和 request.getRemotePort() 这三个方法都是获取与HTTP请求相关的端口信息的 客户端(如浏览器)通过某个随机分配的网络连接端口(7070) 向服务器发送HTTP请求( http://exam…

Leetcode刷题笔记题解(C++):LCR 174. 寻找二叉搜索树中的目标节点

思路&#xff1a;二叉搜索树的中序遍历是有序的从大到小的&#xff0c;故得出中序遍历的结果&#xff0c;即要第cnt大的数为倒数第cnt的数 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeN…

嵌入式学习-网络编程-Day6

嵌入式学习-网络编程-Day6 一、思维导图 二、作业 1.基于UDP的网络聊天室&#xff08;2024.1.21号前上交&#xff09; 项目需求&#xff1a; 1.如果有用户登录&#xff0c;其他用户可以收到这个人的登录信息 2.如果有人发送信息&#xff0c;其他用户可以收到这个人的群聊信息…

读书笔记之《万物起源》:宇宙与人类的极简史

《万物起源&#xff1a;从宇宙大爆炸到文明的兴起》讲述了从大爆炸直到今日&#xff0c;约140亿年间所有重大事物的起源&#xff0c;依次覆盖了量子力学&#xff0c;天体物理学&#xff0c;化学&#xff0c;行星科学&#xff0c;地质学&#xff0c;生物学和人类历史等等学科。 …

Spring第七天(AOP)

简介 AOP(Aspect Oriented Programing)面向切面编程&#xff0c;一种编程范式&#xff0c;指导开发者如何组织程序结构 作用 在不惊动原始设计的基础上为其进行功能增强 Spring理念&#xff1a;无入侵式/无侵入式 基本概念 连接点(JoinPoint) : 程序执行过程中的任意位置&a…

Linux:软硬链接的概念与应用

文章目录 软链接和硬链接软链接的应用场景硬链接的应用场景当前目录和上级目录软硬链接目录和文件的问题 总结 本篇要探讨的主题是关于软硬链接的概念 在Linux系统链接文件中有两种&#xff0c;一种是硬链接&#xff0c;一种是软链接&#xff0c;那么本篇就基于上述的两种链接…

IDEA的database使用

一、数据据库 在使用database之前&#xff0c;首先你的电脑要安装好了数据库并且启动。 MySQL卸载手册 链接&#xff1a;https://pan.baidu.com/doc/share/AVXW5SG6T76puBOWnPegmw-602323264797863 提取码&#xff1a;hlgf MySQL安装图解 链接&#xff1a;https://pan.baidu.…

2024年,给程序员的六点建议

作为程序员&#xff0c;持续进步和发展是至关重要的。除了技术能力的提升&#xff0c;还有一些关键的行为和思维方式可以帮助工程师在职业生涯中取得更大的成功。本文将提供六个重要的建议&#xff0c;这些建议将帮助程序员在职业生涯中迈出成功的步伐。 走出舒适区 走出舒适区…

IEEE-2024年第五届人工智能、机器人及控制国际会议(AIRC 2024)

IEEE--2024年第五届人工智能、机器人及控制国际会议(AIRC 2024) 会议时间: 2024年4月22-24日 会议地点: 埃及开罗 埃及英国大学 会议网址:AIRC 2024 | Artificial Intelligence, Robotics and Controlhttps://www.airc.org/ 埃及开罗 埃及英国大学 会议组织单位&#xff1a; 征…

关于安装Dubbo+zookeeper过程中遇到的许多问题

在学习dubbozookeeper时安装启动dubbo-admin出现的一些问题&#xff1a; 首先我是跟着狂神安装zookeeper&#xff0c;基本下来没什么问题。然后就是安装dubbo-admin&#xff0c;狂神安装的是dubbo-admin-master&#xff0c;但是现在github上已经没有这个版本了&#xff0c;只能…

[小程序]Http网络请求

一、数据请求限制 出于安全性(bushi)考虑&#xff0c;小程序请求的数据接口必须具备以下两个条件&#xff1a; ①只能请求Https类型 ②必须将接口域名添加到信任列表中 1.配置request合法域名 配置步骤如下&#xff1a;小程序管理后台->开发->开发设置->服务器域名-&g…

“揭秘Maven:如何成为大数据项目的管理能手?“

介绍&#xff1a;Maven是一个项目管理和构建自动化工具&#xff0c;广泛应用于Java项目中。具体来说&#xff1a;项目对象模型&#xff08;POM&#xff09;&#xff1a;Maven通过一个名为POM的模型来描述项目信息&#xff0c;包括项目的坐标、依赖关系、插件目标等。这个模型通…

从零开始的OpenGL光栅化渲染器构建5-阴影

前言 阴影是光线被阻挡的结果&#xff1b;当一个光源的光线由于其他物体的阻挡不能够达到一个物体的表面的时候&#xff0c;那么这个物体就在阴影中了。阴影能够使场景看起来真实得多&#xff0c;并且可以让观察者获得物体之间的空间位置关系。 直接阴影 阴影映射(Shadow Ma…