C++初阶—list深度解剖及模拟实现

news2024/11/28 4:47:09

 

目录

➡️0. 前言

😊1.简易框架实现

🐔1. list和__list_node分析实现

🐔2. 无参构造

😊2.迭代器实现

🐔1. list普通迭代器面临问题及解决方案

🐔2. __list_node\iterator\list三类分析

🐔3. list中const迭代器面临问题及解决方案

🐔4. list中模板参数为自定义类型迭代器优化

🐔5. list迭代器的完整实现

😊3.其他成员属性的实现

🐔1. push_back()的实现

🐔2. 迭代器区间构造实现

🐔3. 拷贝构造及赋值的现代化方法实现(深浅拷贝参考vector)

🐔4. insert()的实现

🐔5. erase()的实现

🐔6. 析构的实现


➡️0. 前言

list 容器,又称双向链表容器,即该容器的底层是以带头双向循环链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。

list链表是序列容器,允许在序列内的任何位置进行常量时间的插入和删除操作,以及两个方向的迭代。

本篇文章暂时不讲解拟制迭代器,将后续进行讲解!

list单独提供了merge归并排序是通过与前面元素的链接和后面元素的链接的每个元素的关联在内部保持的。它们与forward_list非常相似:主要区别在于forward_list对象是单链表,因此它们只能向前迭代,以换取更小和更高效。与其他基本标准序列容器(array、vector和deque)相比,列表在容器内的任何位置插入、提取和移动元素(迭代器已经获得)方面通常表现更好,因此在大量使用列表的算法(如排序算法)中也表现更好。与其他序列容器相比,列表和forward_lists的主要缺点是它们无法通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从所有已知位置(如开始或结束)迭代到该位置,这需要在这些位置之间的距离上花费线性时间。它们还会消耗一些额外的内存来保存与每个元素相关联的链接信息(对于包含小型元素的大型列表来说,这可能是一个重要因素)。

😊1.简易框架实现

🐔1. list和__list_node分析实现

 list容器成员是一个结构体__list_node变量的指针,指向链表的头节点,而结构体__list_node便是链表的结点指向堆区的结构体,包含了模板数据类型的数据,以及前后指针,指向下一个节点!

C++一般很少采用内部类的方式实现,因此再实现list容器时,需要先实现struct __list_node结构体,以及list类,结构体内所有成员变量及成员属性皆为public,便于在list类中访问节点数据

namespace Thb {
    template<class T> struct __list_node {
        T _data;
        __list_node<T>* _next;
        __list_node<T>* _prev;
    };

    template<class T> class list {
        typedef __list_node<T> list_node;
    public:

    private:
        list_node* _head;
    };
}

🐔2. 无参构造

list为双向带头循环链表,因此在实例化list时,需要先给定头节点,且头节点的_next及_prev都指向自己!而给定头节点,需要 new 一个__list_node对象,因此对两个类都提供无参构造!

1、__list_node的无参构造,采用缺省参数构造,如果有数据,直接将数据初始化

        list_node(const T& x = T()) 
            :_data(x)
            ,_next(nullptr)
            ,_prev(nullptr)
        {
        }

2、list初始化,开辟头节点

    public:
        typedef __list_node<T> list_node;
        void empty_init() {
            _head = new list_node;
            _head->_next = _head;
            _head->_prev = _head;
        }
    private:
        list()
        {
            empty_init();
        }

😊2.迭代器实现

🐔1. list普通迭代器面临问题及解决方案

面临问题:

由于list和string、vector存储方式不同,list不是连续的物理空间,因此在实现迭代器时,++并不会跳转到下一个节点的位置,同时解引用操作,也不会获取到数据。

解决方案:

为了实现list非连续物理空间的容器,可以对其迭代器实现为一个对象,调用begin,end等成员方法时,返回一个迭代器对象,而对其所属域的迭代器对象内的操作符及运算符进行重写

    template<class T>
    struct __list_iterator {
            typedef __list_node<T> list_node;
            typedef __list_iterator<T> iterator;

            list_node* _node;

            __list_iterator(list_node* node)
                :_node(node)
            {}
            bool operator!=(const iterator& it) const {
                return _node != it._node;
            }

            iterator& operator++() {
                _node = _node->_next;
                return *this;
            }
            iterator operator++(int) {
                iterator temp(*this);
                _node = _node->_next;
                return *this;
            }

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

上述迭代器类的大致实现,对迭代器的普遍用法 != 、前置 ++ 、--、后置 ++、--、以及解引用 * 访问数据进行了实现,虽然可以解决普通迭代的问题,但仍然具有很多方面的不足。

🐔2. __list_node\iterator\list三类分析

三个类是如何协调调用?为何iterator默认函数满足使用?

  1. 可通过代码观察迭代器未在堆区开辟空间,同时使用了list的节点进行构造,因此不需要采用delete析构节点,默认即可,否则源数据空间将会遭到破坏!
  2. 传递的节点皆是通过指针传递,因此可以访问到底层的数据!
  3. 尽管iterator可以访问底层数据,但是节点全部都被封装在list里,没有list传递节点,iterator是无法直接访问底层数据的,因此可以体现c++的封装特性!
  4. list迭代器并不涉及深拷贝问题,而只是获取一个节点的指针,重写++、--等便捷访问前后节点数据,因此使用默认赋值,及默认拷贝通过list传递地址即可!

🐔3. list中const迭代器面临问题及解决方案

面临问题:

虽然上述解决了普通迭代器所面临的问题,但是对于const对象,无法解决一个类模板所实现的迭代器对象,仅仅根据返回值的不同,无法构成函数重载!!!

 解决方法:

  • 第一种解决方案:重新实现一个对象__const_iterator,复写所有方法,返回const T&(pass原因:不符合高内聚,低耦合。重复代码太多,且累赘!)
  • 第二种解决方案:此时就轮到类模板参数发挥作用了,C++模板可以提供多个模板参数根据参数不同,模板实例化对象便是不同类型对象,而上述方法类模板参数为一个,因此每次使用list在使用iterator实例化的对象都是同类型对象,无法区分!

虽然上述方法,可以很好实现解耦,但是类模板还是不够完善,仍然面临的一些问题!

🐔4. list中模板参数为自定义类型迭代器优化

当list模板参数为自定义类型时,如下代码:

class Point {
public:
	Point() {

	}
	Point(int x, int y) :_x(x), _y(y) {
	}
	int getx() {
		return _x;
	}
	int gety() {
		return _y;
	}
private:
	int _x;
	int _y;
};

void testPoint() {
	Thb::list<Point> ls;
	ls.push_back(Point(1, 2));
	ls.push_back(Point(2, 3));
	ls.push_back(Point(3, 4));
	ls.push_back(Point(4, 5));
	auto it = ls.begin();
	while (it != ls.end())
	{
		cout << "ponit:" << (*it).getx() << "/" << (*it).gety() << endl;
		it++;
	}
}

此时迭代器要想调用类模板对象public方法和属性,只能先解引用获取到对象,再通过 . 的方式才能进行调用,不便于操作!

因此迭代器需要实现->可以直接进行调用,而如何重写 -> ,就需要分析c++特性,c++对于重写operator->时有特殊的规定:语法为了可读性,编译器对->进行特殊处理,重写时返回值再次使用->,即省略了一个->,相当于返回模板类型对象的地址!

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

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



/



void testPoint() {
	Thb::list<Point> ls;
	ls.push_back(Point(1, 2));
	ls.push_back(Point(2, 3));
	ls.push_back(Point(3, 4));
	ls.push_back(Point(4, 5));
	auto it = ls.begin();
	while (it != ls.end())
	{
		cout << "ponit:" << it->getx() << "/" << t->getx() << endl;
		it++;
	}
}

分析代码先通过重写的 this -> operator*(); 获取node节点的_data,便是获取模板Point实例化对象,再通过取地址&Point,获取实例化数据对象的地址,此时返回point实例对象的地址,便可以再次通过->访问类成员方法,在实际测试中可看出c++省略了一个->。

可以通过迭代器的完善观察,解引用 * 和 -> 访问操作符皆涉及到然会模板数据,当返回类型为 const T* 或者 const T& 时,便说明其是一个 const_iterator ,因此需要增加两个类模板参数!!!

🐔5. list迭代器的完整实现

新增Ref(reference引用),Ptr(pointer指针)两个模板参数,并对其成员函数返回值做出更改,对应模板传类型,而list中

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

展示了类模板传递不同,所实例化不同类型的迭代器对象,进行解耦!

    template<class T, class Ref, class Ptr>
    struct __list_iterator {
            typedef __list_node<T> list_node;
            typedef __list_iterator<T, Ref, Ptr> iterator;

            list_node* _node;

            __list_iterator(list_node* node)
                :_node(node)
            {}
            bool operator!=(const iterator& it) const {
                return _node != it._node;
            }
            bool operator==(const iterator& it) const {
                return _node == it._node;
            }

            iterator& operator++() {
                _node = _node->_next;
                return *this;
            }
            iterator operator++(int) {
                iterator temp(*this);
                _node = _node->_next;
                return *this;
            }

            iterator& operator--() {
                _node = _node->_prev;
                return *this;
            }
            iterator operator--(int) {
                iterator temp(*this);
                _node = _node->_prev;
                return *this;
            }

            //T* operator->() {
            Ptr operator->() const {
                return &(operator*());
            }
            //T& operator*() const {
            Ref operator*() const {
                return _node->_data;
            }
     };

    template<class T> 
    class list {
        typedef __list_node<T> list_node;
        void empty_init() {
            _head = new list_node;
            _head->_next = _head;
            _head->_prev = _head;
        }
    public:
        typedef __list_iterator<T, T&, T*> iterator;
        typedef __list_iterator<T, const T&, const T*> const_iterator;

        iterator begin() {
            return iterator(_head->_next);
        }
        iterator end() {
            return iterator(_head);
        }
        const_iterator begin() const {
            return const_iterator(_head->_next);
        }
        const_iterator end() const {
            return const_iterator(_head);
        }
        //默认函数                                                                                                         
        list()
        {
            empty_init();
        }

    private:
        list_node* _head;
    };
}

😊3.其他成员属性的实现

🐔1. push_back()的实现

        void push_back(const T& value) {
            list_node* tail = _head->_prev;
            list_node* newnode = new list_node(value);
            newnode->_next = _head;
            newnode->_prev = tail;
            _head->_prev = newnode;
            tail->_next = newnode;
        }

🐔2. 迭代器区间构造实现

        template<class InputIterator>
        list(InputIterator first, InputIterator last) {
            empty_init();
            while (first != last) {
                push_back(*first);
                first++;
            }
        }

🐔3. 拷贝构造及赋值的现代化方法实现(深浅拷贝参考vector)

        list(const list<T>& ls) 
        {
            empty_init();
            list<T> temp(ls.begin(), ls.end());
            swap(temp);
        }
        list<T>& operator=(list<T> ls) {
            swap(ls);
            return *this;
        }
        void swap(list<T>& ls) {
            std::swap(_head, ls._head);
        }

🐔4. insert()的实现

        iterator insert(iterator pos, const T& x) {
            list_node* newnode = new list_node(x);
            list_node* pos_node = pos._node;
            newnode->_next = pos_node;
            newnode->_prev = pos_node->_prev;
            pos_node->_prev->_next = newnode;
            pos_node->_prev = newnode;
            return iterator(newnode);
        }

🐔5. erase()的实现

        iterator erase(iterator pos) {
            assert(pos != end());
            list_node* dele = pos._node;
            list_node* next = dele->_next;
            next->_prev = dele->_prev;
            dele->_prev->_next = next;
            delete dele;
            dele = nullptr;
            return iterator(next);
        }

🐔6. 析构的实现

        void clear() {
            iterator it = begin();
            while (it != end()) {
                it = erase(it);
            }
        }
        ~list() {
            clear();
            delete _head;
        }

析构函数复用了clear,clear删除除哨兵位头节点的所有节点!而clear通过复用erase保证删除节点时,其逻辑关系仍然稳定不变!同时使用迭代器,begin记录了头节点的下一个位置,erase返回删除数据的下一个数据的位置的迭代器,进行迭代器it的更新!

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

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

相关文章

内网安全:Cobalt Strike 与 MSF 联动( 会话 相互转移 )

内网安全&#xff1a;Cobalt Strike 与 MSF 联动&#xff08; 会话 相互转移 &#xff09; 在渗透中&#xff0c;有时候 Cobalt Strike 会话可能会受限制&#xff0c;所以我们需要把 Cobalt Strike 会话转移到 MSF 上进行后面的渗透。也有的时候会话在 MSF 上&#xff0c;但是…

大数据学习归纳

本文初衷是为了学习归纳&#xff0c;若有错误&#xff0c;请指出。 修改记录 时间内容2020年4月10日第一次发布2020年4月16日添加MaxCompute SQL部分2020年9月14日新增数仓部分笔记 大数据架构 基础知识题 大数据组件概念 集群&#xff1a;多个人做同样的事 分布式&#xff1a;…

Visual Studio Community 2022 + Win10 编译 OpenCPN 5.9.0 记录

前言 前两天尝试用vs2017编译OpenCPN5.0.0&#xff0c;前后折腾了两三天总算编译成功了。官网给出的编译过程比较简单&#xff0c;我在实际编译过程中遇上了很多很多的问题&#xff0c;最多的就是缺少库&#xff0c;好在最后编译通过了。 后来浏览OpenCPN官网的时候发现发布了…

【Spring】开发框架Spring核心技术含Resource接口详细讲解

前言 Spring 是 Java EE 编程领域的一款轻量级的开源框架&#xff0c;由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立&#xff0c;它的目标就是要简化 Java 企业级应用程序的开发难度和周期。 Spring 自诞生以来备受青睐&#xff0c;一直被广大开发人员作为 Java…

测试工程师常用的10个python库,面试必备哦

目录 前言 1、os库 2、sys库 3、time库 4、selenium库 5、unittest库 6、pytest库 7、email库 8、appium库 9、pymsql库 10、requests库 总结&#xff1a; 前言 今天给各位小伙伴带来的是测试工程师常用的10个python库&#xff0c;相信有些小伙伴肯定知道一些库&am…

aclocal-1.14 is missing on your system

在编译 bluez 的时候出现如下错误&#xff1a; 没有找到 aclocal-1.14&#xff0c; 但是有 aclocal-1.13 版本的&#xff0c;那最直接的方法就是修改 Makefile了&#xff0c;搜索出来 Makefile 指定了 aclocal-1.14&#xff0c;修改成 aclocal-1.13 即可。修改完还会有如下的错…

数据科学导论

《数据科学导论》 重点归纳 第1~4章 数据科学研究的问题边角广泛&#xff0c;只要是和数据收集、清洗整理、分析和挖掘有关的问题都是数据科学要研究的问题&#xff1b;数据科学的主要方法&#xff1a;有监督学习、无监督学习、半监督学习&#xff1b;有监督学习中&#xff…

初见PlayWright

PlayWright特色 跨浏览器&#xff1a;PlayWright支持所有现代的浏览器渲染引擎&#xff0c;包括Chromium、WebKit、Firefox&#xff0c;这意味着它可以驱动像Chrome、Edge、Firefox、Safari等主流浏览器跨平台&#xff1a;基于浏览器的特性&#xff0c;可以在Windows、Linux和…

卡方检验笔记

文章目录 一、定义二、用途三、公式四、案例4.1 手工统计4.2 python统计4.3 SPSS统计 一、定义 卡方检验属于非参数检验&#xff0c;由于非参检验不存在具体参数和总体正态分布的假设&#xff0c;所以有时被称为自由分布检验。原假设 H 0 H_{0} H0​&#xff1a;观察频数与期望…

大龄、零基础,想转行做网络安全。怎样比较可行?一般人我还是劝你算了吧

昨晚上真的给我气孕了。 对于一直以来对网络安全兴趣很大&#xff0c;想以此作为以后的职业方向的人群。 不用担心&#xff0c;你可以选择兼顾工作和学习&#xff0c;以步步为营的方式尝试转行到网络安全领域。 那么&#xff0c;网络安全到底要学些什么呢&#xff1f; &…

怎么快速给需要的网路标记颜色?

引入 我们在走线的时候&#xff0c;需要知道那些类型的线需要先走&#xff0c;接下来又要走那些类型的线&#xff0c;然后依次走完&#xff0c;如果在团队中&#xff0c;这一类型的线分配给这个人走&#xff0c;哪一类型的线有分配给那个人走。而在不管是那单个人&#xff0c;还…

效果图渲染的几大实用技巧

效果图渲染是建筑、室内、景观、产品设计等行业中非常重要的一环。一个高质量的效果图可以让客户更好地了解和感受设计方案&#xff0c;提高设计师的竞争力。但是渲染效果的好坏和速度都取决于设计师的技巧和工具。本文将介绍几大实用技巧&#xff0c;帮助设计师更好地进行效果…

ASEMI代理光宝IGBT驱动器LTV-155E规格,LTV-155E封装

编辑-Z LTV-155E参数描述&#xff1a; 型号&#xff1a;LTV-155E 储存温度Tstg&#xff1a;-55~125℃ 工作温度Topr&#xff1a;-40~105℃ 输出IC结温度TJ&#xff1a;125℃ 总输出电源电压(VCC –VEE)&#xff1a;35V 平均正向输入电流IF&#xff1a;25mA 反向输入电压…

对vite的理解

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步…

强化学习笔记-13 Policy Gradient Methods

强化学习算法主要在于学习最优的决策&#xff0c;到目前为止&#xff0c;我们所讨论的决策选择都是通过价值预估函数来间接选择的。本节讨论的是通过一个参数化决策模型来直接根据状态选择动作&#xff0c;而不是根据价值预估函数来间接选择。 我们可以定义如下Policy Gradien…

软件测试外包干了3年,感觉废了..

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了3年的功能测试&…

官方都不告诉你的 Windows ISO 下载方式

目录 一、背景二、下载 一、背景 我们都知道&#xff0c;在日常生活中&#xff0c;经常会遇到各种操作系统的安装&#xff0c;如 Windows、Linux 等&#xff0c;一般都会通过 ISO 来安装。而在很多第三方地址中下载的 ISO 镜像通常会捆绑一些流氓软件&#xff0c;很是难受。那…

Java 中的异常处理

认识异常 程序中可能会有很多意想不到的问题的出现&#xff0c;这些问题中&#xff0c;有些是在编写阶段时就无法编译通过&#xff0c;比如写代码时变量名写错&#xff0c;出现语法错误 java.lang.Error: Unresolved compilation problem ……&#xff1b;有些是在程序运行的时…

从零开始Vue项目中使用MapboxGL开发三维地图教程(四)改变鼠标style、地图置于单击feature中心、量测距离和polgon面积和中心点坐标

文章目录 1、飞行平移到鼠标点击图层属性的地图中心位置2、当鼠标光标进入“圆”图层中的某个要素时&#xff0c;将其更改为指针3、量测距离4、量测area面积和中心点坐标 1、飞行平移到鼠标点击图层属性的地图中心位置 //鼠标点击事件map.on("click", "iconImag…

基于Amazon SageMaker平台部署Stable Diffusion模型实现——图片识别

序言&#xff1a; 当谈到机器学习和人工智能的开发和部署时&#xff0c;Amazon SageMaker是一个非常强大和全面的平台。作为一项托管式的机器学习服务&#xff0c;Amazon SageMaker提供了一套完整的工具和功能&#xff0c;帮助开发者轻松构建、训练和部署机器学习模型。 首先&…