C++入门之stl六大组件--List源码深度剖析及模拟实现

news2024/12/27 11:53:57

文章目录

前言

一、List源码阅读

二、List常用接口模拟实现

1.定义一个list节点

2.实现一个迭代器

2.2const迭代器

3.定义一个链表,以及实现链表的常用接口

三、List和Vector

总结


前言

本文中出现的模拟实现经过本地vs测试无误,文件已上传gitee,地址:list: 模仿实现stl的list - Gitee.com


一、List源码阅读

首先我们阅读源码,阅读源码我按照如下方式:先找到单个节点的定义,再找到list里面的主要成员函数。

list_node链表节点的定义,如下:有三个成员,prev,next指针,数据域。

实现链表的接口:增删改查,回想我们用C语言实现的顺序表的时候,使用指针去实现。使用C++的时候,模拟实现string,也是使用迭代器。迭代器就是模拟指针的行为,但是链表直接使用指针++,不一定能访问到下一个节点,所以我们要封装一下原生指针--迭代器,去实现对list的访问。

阅读源码,_list_iterator这个迭代器,有三个模板参数,猜测T应该是类型,Ref是引用,Ptr是指针,为什么有三个模板参数?后续再分析。这个迭代器里实现了一个原生迭代器,一个const迭代器,const调用const对象,然后还有一个self,暂时不明白什么意思。继续往下。

 阅读到这里,重定义了一些参数。注意这里:

  • typedef _list_node<T> * link_type;  
  • link_type node;
  • _list_iterator(link type x):node(x){}

和C语言一样,这里定义了一个节点的指针,作为实现迭代器的最小单元

继续往下阅读,这里有一些运算符重载的函数,比较节点是否相等,以及引用,注意这里引用的返回值使用了reference,上面看出来将Ref重定义成了reference,所以这里可以猜测,ref就是返回引用的值

 这里实现了运算符重载函数,使用迭代器,去访问前后的节点。注意这里的返回值是self,前面读到self就是_list_iterator的一个模板参数。对比之前实现日期类的时候,日期类++,返回的就是一个日期类对象。这里使用迭代器进行++,猜测这里返回的就是一个迭代器。

继续查看迭代器的使用:在list的成员函数中,查看到begin:指向头节点的下一个,end指向头节点。rbegin指向头节点,rend指向头节点的下一个。

判断链表是否为空:判断头节点的下一个是否指向头节点

计算链表的size:根据begin,end去计算

 以及链表中最重要的插入insert,通过迭代器找到pos的位置,根据T去构建一个Node,再进行插入。头插尾插都可以复用Insert。头插头删的时候先保留现在的指针指向情况,再进行修改,修改完了之后再删除这个节点

 往下阅读,到了list的主体部分,有两个模板参数。alloc暂时不明白是什么

 这里实现了一种创建节点的成员函数,create_node:通过T类型的参数x创建一个节点.

empty_initialize 空的初始化,只创建一个节点

二、List常用接口模拟实现

通过上面阅读源码,我们来模仿实现list的一些常用接口。

1.定义一个list节点

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

2.实现一个迭代器

 迭代器要么就是原生指针,要么就是自定义类型对原生指针的封装,模拟指针的行为。

list用一个节点的指针,去构造一个迭代器

template<class T>
struct _list_iterator
{
    typedef list_node<T> node;
    
    typedef _list_iterator<T> self;

    //定义一个指针,指向链表
    node * _node;

    //构造函数 用一个节点指针去构造迭代器
    _list_iterator(node * n)
        :_node(n)
    {
    }

    //实现迭代器的功能
    //指针++向后遍历,就如日期类,返回的还是一个日期类对象;迭代器++,返回一个迭代器对象
     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;
    }

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

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

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

    

2.2const迭代器

假设我们传了一个const对象,

  • void print_list(const list<int>&lt)。
  • 对象的类型是一个const list<int> * ,调用不了普通迭代器,是一个经典的权限放大,所以我们要对this加一个const。此时变成了const* _head,指针本身不能改变,但是指向的内容可以改变。即我们还是可以对对象进行改变。
  • 在stl库中,它使用const修饰*this,返回值也是一个const_iterator。但是,对于所有的成员函数都使用const修饰*this和返回值类型很冗余,所以我们这里增加了一个模板参数class Ref。
	template<class T>
	struct __list_const_iterator
	{
		typedef list_node<T> node;
		typedef __list_const_iterator<T> self;
		node* _node;

		__list_const_iterator(node* n)
			:_node(n)
		{}

        //保护返回的值不能被修改
		const T& operator*()
		{
			return _node->_data;
		}

        //... 其他成员函数都相同
	};
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 _ndoe->data;
    }
    
    //注意这里 如果我定义了一个AA类型的链表,通过迭代器去访问,指针类型为AA*
    // 现在要访问它的成员 可以这样: *(it)._a1; 
    //也可以it->->a1 一个是运算符重载调用,it是自定义类型,无法直接使用箭头,it-> 就相当于运算符重载operator-> AA*
    //一个是找成员 
    //这里为了增强可读性 省略了一个箭头 it->_a1;
    Ptr operator->()
    {
        return &_node->data;
    }
    
    self& operator--()
    {
    }
    //其余运算符操作类似
}

template<class T>
class list
{
    //注意这里模板参数 调用普通迭代器 T&传给ref, 调用const迭代器 const T& 传给ref 
    typedef _list_iterator<T,T&,T*> iterator;
    typedef _list_iterator<T,const T&, const T*> const_iterator;

    iterator begin()
    {
        return iterator(_head->_next);
    }

    const iterator begin()
    {
        return const_iterator(_head->_next);
    }
    //..其余类似
}

3.定义一个链表,以及实现链表的常用接口

template<class T>
struct _list
{
    typedef list_node<T> node;
    typedef _list_iterator<T> iterator;

    //list的成员
    node* _head;
    
    //list的构造 初始只有一个头节点
    list()
    {
        _head = new node;
        _head->_next = _head;
        _head->_prev = _head;
    }

    //list的一些成员函数 

    //通过迭代器定位begin和end

      iterator begin()
      {
        return iterator(_head->_next);
      }
        
      iterator end()
      {
            return iterator(_head);
       }
    //通过迭代器对指定pos位置进行增删改查
   void  insert(iterator pos, const T& x)
    {
        node new_node = new node(x);
        //iterator cur = pos;
        //这里是要通过pos的指针,找到这个节点 对pos进行解引用
        node * cur = pos._node;
        node * prev = cur->_prev;
      
        prev->_next = new_node;
        new_node->_prev = prev;
        new_node->_next = cur;
        cur->_prev = new_node;
    }

    void push_back()
    {
        insert(end(),x);
    }
    
    void push_front()
    {
        insert(begin(),x);
    }
   void 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;
    }

    void pop_back()
    {
        erase(end());
    }
    
    void pop_front()
    {
        erase(begin());
    }
    
    //打印
    
    void print_list(const list<T> & lt)
    {
        iterator it = lt.begin();
        while(it != lt.end())
        {
            cout<<*it;
            ++it;
        }

        cout<<endl;
    }

}

三、List和Vector对比

vector与list都是stl中非常重要的序列容器,由于两个容器的底层结构不同,导致特性以及应用场景不同

vectorlist
底层结构动态顺序表,一段连续的空间带头节点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除在任意位置插入删除元素效率较低,时间复杂度O(N),插入可能需要扩容,开辟新空间,拷贝元素,释放旧空间任意位置插入和删除效率高,不需要搬移元素。时间复杂度O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生指针对原生指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能导致扩容,导致原来迭代器失效,删除时,也可能失效。需要重新给迭代器赋值插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,因为那个节点已经被删除。其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

总结

本文主要对stl源码中list内容进行阅读,并模拟实现。技术有限,如有错误请指正。

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

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

相关文章

AI大模型来袭,智能客服变天?

配图来自Canva可画 自ChatGPT爆火“出圈”之后&#xff0c;国内外就掀起了一波AI大模型风潮。越来越多的企业都开始布局AI大模型领域&#xff0c;其中不少企业已经推出了自家的AI大模型产品&#xff0c;试图在这股AI浪潮中拔得头筹。而在众多AI大模型中&#xff0c;既有通用大…

Python爬虫如何更换ip防封

作为一名长期扎根在爬虫行业动态ip解决方案的技术员&#xff0c;我发现很多人常常在使用Python爬虫时遇到一个困扰&#xff0c;那就是如何更换IP地址。别担心&#xff0c;今天我就来教你如何在Python爬虫中更换IP&#xff0c;让你的爬虫不再受到IP封锁的困扰。废话不多说&#…

LVS负载均衡(DR)

文章目录 LVS-DR模式配置原理注DR配置添加VIP下载ipvsadm在DR上管理LVS Real-Server RS配置绑定VIP到环回网卡添加访问VIP的路由配置ARP抑制测试&#xff1a; LVS-DR模式配置 原理 当客户端发起请求后由DR处理&#xff0c;通过算法将流量转发至Real-Server中的某一个处理。然后…

8.16CAS

1.CAS 2. 原子类的原理 3.原子类的使用 import java.util.concurrent.atomic.AtomicInteger;public class Test {public static AtomicInteger atomicInteger new AtomicInteger();public static void main(String[] args) throws InterruptedException {System.out.println(…

Python读取及生成pb文件,pb与jsonStr互转,pb与dictJson互转,打包.exe/.sh并转换,很完美跨平台

Python读取及生成pb文件&#xff0c;pb与jsonStr互转&#xff0c;pb与dictJson互转&#xff0c;打包.exe/.sh并转换&#xff0c;很完美跨平台 1. 效果图2. 命令行&#xff1a;proto文件转.class&#xff08;绝对路径或相对路径&#xff09;3. 序列化、反序列化api4. pb转json&a…

搭建MyBatis开发环境

hi,大家好,今天来学习一下MyBatis的相关知识 文章目录 &#x1f9ca;1.MyBatis定义&#x1f9ca;2.为什么要学习MyBatis&#x1f9ca;3.搭建MyBatis开发环境&#x1f350;3.1前置工作--创建数据库和表&#x1f350;3.2在新项目中添加MyBatis的框架&#x1f350;3.3设置MyBatis…

Leetcode31 下一个排列

解题思路&#xff1a; 算法过程的第二步&#xff0c;可以变为将[j,end]排序&#xff0c;然后从[j,end)和i进行比较&#xff0c;在区间j,end区间第一个大于nums[i]后&#xff0c;交换即可 public void nextPermutation(int[] nums) {int len nums.length - 1;for(int i len;i…

小程序的 weiui的使用以及引入

https://wechat-miniprogram.github.io/weui/docs/quickstart.html 网址 1.点进去&#xff0c;在app.json里面配置 在你需要的 页面的 json里面配置&#xff0c;按需引入 然后看文档&#xff0c;再在你的 wxml里面使用就好了

使用DMA传输实现单片机高效串口转发——以STM32系列为例

使用DMA传输实现单片机高效串口转发——以STM32系列为例 DateAuthorVersionNote2023.08.06Dog TaoV1.01. 完成了文档的撰写。 文章目录 使用DMA传输实现单片机高效串口转发——以STM32系列为例应用场景实现流程源码示例串口与中断配置DMA外设配置DMA发送数据函数串口中断服务函…

Java中String方法魔性学习

这里写目录标题 先进行专栏介绍String详解常用构造方法代码演示常用成员方法代码示例总结 先进行专栏介绍 本专栏是自己学Java的旅途&#xff0c;纯手敲的代码&#xff0c;自己跟着黑马课程学习的&#xff0c;并加入一些自己的理解&#xff0c;对代码和笔记 进行适当修改。希望…

【redis】SpringBoot集成redis

目录 1.添加redis依赖2.配置redis3.操作redis3.1 操作string 3.1 操作其它数据类型 4. Spring-Session基于Redis解决共享Session问题4.1 问题提出 4.1 添加依赖 4.2 修改配置4.3 存储和读取 1.添加redis依赖 方法①&#xff1a; <dependency><groupId>org.springf…

ChatGPT已闯入学术界,Elsevier推出AI工具

2022年11月&#xff0c;OpenAI公司发布了ChatGPT&#xff0c;这是迄今为止人工智能在现实世界中最重要的应用之一。 当前&#xff0c;互联网搜索引擎中出现了越来越多的人工智能&#xff08;AI&#xff09;聊天机器人&#xff0c;例如谷歌的Bard和微软的Bing&#xff0c;看起来…

微信小游戏流量主结算财务信息填写指引

微信小游戏个人开发者: 流量主结算财务信息填写指南 一,登录公众平台二,补充财务信息三,补充信息指引四,提交审核五,绑定通知对于微信小游戏个人开发者来说,流量主结算财务信息的填写是非常重要的一步。正确填写可以保证收入的及时结算,而填写不当则可能会导致收入无法到…

wxRibbonBar 常用三种控件Button,DropdownButton,HybridButton

这三种控件的效果如下所示&#xff1a; 点击下拉的效果&#xff1a; 这一部分可以设置wxITEM_CHECK&#xff0c;wxITEM_RADIO等效果 但我们可能更关注实现实例&#xff1a; &#xff08;1&#xff09;MyFrame.h #pragma once #include <wx/wx.h> #include "wx/wx…

vue2-diff算法

1、diff算法是什么&#xff1f; diff算法是一种通过同层的树节点进行比较的高效算法。 其有两个特点&#xff1a; 比较只会在同层级进行&#xff0c;不会跨层级进行。 在diff比较的过程中&#xff0c;循环从两边向中间比较。 diff算法在很多场景中都有应用&#xff0c;在vue中&…

(学习笔记-进程管理)进程

进程 我们编写的代码只是一个存储在硬盘的静态文件&#xff0c;通过编译后会生成二进制可执行文件&#xff0c;当我们运行这个可执行文件后&#xff0c;它会被装载到内存中&#xff0c;接着CPU会执行程序中的每一条指令&#xff0c;那么这个运行中的程序就被称为进程。 现在我…

怎么加密文件夹才更安全?安全文件夹加密软件推荐

文件夹加密可以让其中数据更加安全&#xff0c;但并非所有加密方式都能够提高极高的安全强度。那么&#xff0c;怎么加密文件夹才更安全呢&#xff1f;下面我们就来了解一下那些安全的文件夹加密软件。 文件夹加密超级大师 如果要评选最安全的文件夹加密软件&#xff0c;那么文…

python GUI nicegui初识一(登录界面创建)

最近尝试了python的nicegui库&#xff0c;虽然可能也有一些不足&#xff0c;但个人感觉对于想要开发不过对ui设计感到很麻烦的人来说是很友好的了&#xff0c;毕竟nicegui可以利用TailwindCSS和Quasar进行ui开发&#xff0c;并且也支持定制自己的css样式。 这里记录一下自己利…

spring security + oauth2 使用RedisTokenStore 以json格式存储

1.项目架构 2.自己对 TokenStore 的 redis实现 package com.enterprise.auth.config;import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis…

leetcode刷题:1657. 确定两个字符串是否接近、1004. 最大连续1的个数 III

leetcode刷题:1657. 确定两个字符串是否接近、1004. 最大连续1的个数 III 1. 前言2. 1657. 确定两个字符串是否接近3. 1004. 最大连续1的个数 III4. 总结 1. 前言 上述两个题目位于leetcode75中&#xff0c;难度为中等&#xff0c;虽然对于大佬而言&#xff0c;可能很简单&…