【C++修炼之路】list 模拟实现

news2024/10/7 10:25:21

👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路

文章目录

  • 一、读源码
  • 二、成员
  • 三、默认成员函数
    • 1、构造
    • 2、析构
    • 3、拷贝构造
    • 4、赋值重载
  • 四、迭代器
  • 五、其他接口

如果无聊的话,就来逛逛 我的博客栈 吧! 🌹

一、读源码

list 是双向带头循环链表,不了解这个结构可以去看我的双向链表。

list 不支持 [ ] ,因为地址不连续,list 的访问通过迭代器。

迭代器:

template<class T, class Ref, class Ptr>
struct __list_iterator {
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;

  typedef bidirectional_iterator_tag iterator_category;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  link_type node;

  // ...
};

迭代器是自定义类型。

成员变量:

template <class T, class Alloc = alloc>
class list {
    // ...
protected:
  link_type node;  
}

查看 link_type 本质:

typedef list_node* link_type;
typedef __list_node<T> list_node;
// 链表节点
template <class T>
struct __list_node {
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  T data;
};

实质上就是节点类型的指针。

创建节点:

link_type create_node(const T& x) {
    link_type p = get_node();
    __STL_TRY {
        construct(&p->data, x);
    }
    __STL_UNWIND(put_node(p));
    return p;
}

get_node:

link_type get_node() { return list_node_allocator::allocate(); } // 空间配置器开辟空间

construct 实际上就是定位 new 。

list 构造:

list() { empty_initialize(); }
void empty_initialize() { 
    node = get_node();
    node->next = node;
    node->prev = node;
}

list 构造没有调用定位 new ,因为哨兵位上面并不需要存放有效数据,所以只需要开辟空间,确定链接关系;普通节点上,存放自定义类型数据,若赋值空间上存在随机值,对空间进行释放可能会导致崩溃的现象,所以对于普通节点需要调用定位 new 进行构造。

二、成员

list_node 为节点,需要经常访问,开 struct :

template<class T>
    struct list_node
    {
        list_node<T>* _next;
        list_node<T>* _prev;
        T _val;
        
        list_node(const T& val = T())
            :_next(nullptr)
                ,_prev(nullptr)
                ,_val(val)
            {}
    };

list:

template<class T>
    class list
    {
        typedef list_node<T> Node; // 给类内部用的

        // ...
        private:
        Node* _head; // 哨兵位
        size_t _size;
    };

三、默认成员函数

1、构造

给哨兵位完成初始化:

void empty_init()
{
    _head = new Node;
    _head->_prev = _head;
    _head->_next = _head;

    _size = 0;
}

list()
{
    empty_init();
}

2、析构

~list()
{
    clear();

    delete _head;
    _head = nullptr;
}

// 清除除哨兵位的所有节点
void clear()
{
    iterator it = begin();

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

    _size = 0;
}

3、拷贝构造

// list(const list& lt) // 类中类名可以充当类型
list(const list<T>& lt)
{
    // new(this)list; // 定位 new
    empty_init();

    // & 提高效率
    for (auto& e : lt)
    {
        push_back(e);
    }
}

可以使用定位 new 直接调用构造函数,也可以复用 empty_init 。赋值通过 push_back ,push_back 中会完成对自定义类型成员的深拷贝。

4、赋值重载

// void swap(list& lt)
void swap(list<T>& lt)
{
    std::swap(_head, lt._head);
    std::swap(_size, lt._size);
}

// list& operator=(const list<T>& lt)
list<T>& operator=(list<T> lt)
{
    swap(lt);
    return *this;
}

调用拷贝构造,进行交换。

四、迭代器

list 的迭代器是自定义类型,不是原生指针Node*.

若:

typedef Node* iterator;

list<int>::iterator it = lt.begin();

while (it != lt.end())
{
    (*it) += 1;
    cout << *it << " ";
    ++it;
}

那么 *it 获得的是节点类,并不是节点中存储的值。

所以,迭代器为自定义类型,其中 *, ++ 等都是通过运算符重载来完成的。

通过封装自定义类型为迭代器,利用节点指针构造迭代器,用类封装类型,统一迭代器的使用方法,来获得统一的结果。

思考,普通迭代器的模板类型构成:

我们需要如下重载符号:*, 前置++,后置++,前置--,后置--,->, !=, ==,返回类型分别为:T&, 迭代器引用,迭代器拷贝,迭代器引用,迭代器拷贝,T*, 布尔值,布尔值

-> 模拟的行为:

struct A
{
    A(int a1 = 0, int a2 = 0)
        :_a1(a1)
            , _a2(a2)
        {}

    int _a1;
    int _a2;
};

void test_list2()
{
    list<A> lt;
    lt.push_back(A(1, 1));
    lt.push_back(A(2, 2));
    lt.push_back(A(3, 3));
    lt.push_back(A(4, 4));

    list<A>::iterator it = lt.begin();
    while (it != lt.end())
    {
        //cout << (*it)._a1 << " " << (*it)._a2 << endl; // 自定义类型成员访问成员变量
        cout << it->_a1 << " " << it->_a2 << endl; // 打印自定义类型的成员变量

        ++it;
    }
    cout << endl;
}

类比 const 迭代器,const 迭代器与普通迭代器的区别无非是 * 返回的值不能修改,-> 返回的 T 类型指针指向的成员变量不能修改

所以,我们完全可以将类模板写为:

template<class T, class Ref, class Ptr> // T 为任意类型,Ref 为 T& 为数据的引用,Ptr 为 T* 为返回的 T 类型的指针

迭代器根据节点来构造,通过运算符重载来完成统一行为,写出迭代器:

template<class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef list_node<T> Node;
		typedef _list_iterator<T, Ref, Ptr> self; // 重命名避免繁琐,selft 就是迭代器本身
		Node* _node;

		_list_iterator(Node* node)
			:_node(node)
		{}

		// 返回引用的值
		Ref operator*()
		{
			return _node->_val;
		}
		
        // 编译器做了优化
        // Ptr 返回的是 T*
        // 调用时 it->_member
        // 那么此刻为 T*_member,原本为 T*->_member
        // 所以其实调用方应该写为 it->->_member,为了符合逻辑,编译器做了优化,只要写 it->_member
		Ptr operator->()
		{
			return &_node->_val;
		}

		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;
		}
		
        // it != it.end()
        // it.end() 返回的是临时对象,为常量,所以要加 const 修饰
		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}

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

迭代器的拷贝只要浅拷贝,期望拿到的就是对应节点的 begin;迭代器没有析构,析构工作交给 list 统一处理,迭代器只需要完成它的本质工作:对节点的访问。

普通迭代器和 const 迭代器只需要在 list 中 typedef :

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

注:类中的自定义类型有两方面 - 1. typedef 的内嵌类型 2. 内部类。

iterator begin()
{
    return _head->_next; // 隐式类型转换
}

iterator end()
{
    return _head;
}

const_iterator begin() const
{
    return _head->_next;
}

const_iterator end() const
{
    return _head;
}

返回时,隐式类型转换,例如 begin,实际上是 iterator(_head->_next),单参数的构造函数支持隐式类型转换。

五、其他接口

void push_back(const T& val)
{
    /*Node* newnode = new Node(val);
			Node* tail = _head->_prev;

			newnode->_prev = tail;
			tail->_next = newnode;

			newnode->_next = _head;
			_head->_prev = newnode;*/

    insert(end(), val);
}

void push_front(const T& val)
{
    insert(begin(), val);
}

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

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

// pos 位置之前插入
iterator insert(iterator pos, const T& x)
{
    Node* newnode = new Node(x);
    Node* cur = pos._node; // 迭代器访问成员变量,获取 Node*
    Node* prev = cur->_prev;

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

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

    ++_size;

    return newnode; // 插入的位置
}

// 删除 pos 位置
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;
    --_size;

    return next; // 删除后的位置
}

size_t size()
{
    return _size;
}

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

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

相关文章

Pytorch自动求导机制详解

目录 1. 自动求导 1.1 梯度计算 1.1.1 一阶导数 1.1.2 二阶导数 1.1.3 向量 1.2 线性回归实战 1. 自动求导 在深度学习中&#xff0c;我们通常需要训练一个模型来最小化损失函数。这个过程可以通过梯度下降等优化算法来实现。梯度是函数在某一点上的变化率&#xff0c;可以告…

vue代码格式化,Prettier - Code formatter格式化规则文件

vue2&#xff0c;vue3格式化代码使用方法&#xff1a; 1、新建文件名&#xff1a; .prettierrc.cjs&#xff0c;里面放上下面的代码片段&#xff0c;直接粘贴即可 2、把 .prettierrc.cjs文件放在项目的根目录中 // prettier的默认配置文件 module.exports {// 一行最多 100 …

Final Cut Pro中文新手教程 (52绿幕抠图)FCPX透明通道基础使用方法

今天小编为大家分享的是FCPX透明通道基础教程&#xff0c;究竟什么是透明通道呢&#xff1f;透明通道就是一个阿尔法(alpha)通道&#xff0c;也叫做通明阿尔法通道。只要带有alpha的图片或者视频&#xff0c;他们的背景就是透明的只会显示他们的形状和内容。这种技术经常应用在…

VLAN :虚拟局域网

目录 VLAN&#xff1a;虚拟局域网 VLAN种类&#xff1a; 接口分配链路类型 接口划分VLAN 跨网段的通讯 VLAN&#xff1a;虚拟局域网 LAN &#xff1a;局域网 MAN&#xff1a;城域网 WAN&#xff1a;广域网 1.一个VLAN相当于一个广播域 VLAN&#xff1a;通过路由器和交换机…

OpenCv之图像形态学

目录 一、形态学 二、图像全局二值化 三、自适应阈值二值化 四、腐蚀操作 五、获取形态学卷积核 六、膨胀操作 七、开运算 八、闭运算 一、形态学 定义: 指一系列处理图像形状特征的图像处理技术形态学的基本思想是利用一种特殊的结构元(本质上就是卷积核)来测量或提取输…

数据结构--图的基本操作

数据结构–图的基本操作 使用的存储模式&#xff1a; 图的基本操作&#xff1a; • Adjacent(G,x,y)&#xff1a;判断图G是否存在边<x, y>或(x, y)。 • Neighbors(G,x)&#xff1a;列出图G中与结点x邻接的边。 • InsertVertex(G,x)&#xff1a;在图G中插入顶点x。 • …

VSCode 注释后光标快速定位下一行

VSCode默认用 Ctrl / 注释一行时&#xff0c;光标停留在该行中。下面介绍如何注释后&#xff0c;光标会自动移动到下一行。 1.【View】 ->【Extensions】->【查找并安装Multi-command 扩展】 2.【File 】 -> 【Preferences 】->【Keyboard Shortcuts】&#xff08…

怎样优雅地增删查改(八):按用户关系查询

文章目录 原理实现正向用户关系反向用户关系 使用测试 用户关系&#xff08;Relation&#xff09;是描述业务系统中人员与人员之间的关系&#xff0c;如&#xff1a;签约、关注&#xff0c;或者朋友关系。 之前我们在扩展身份管理模块的时候&#xff0c;已经实现了用户关系管理…

Spark(30):Spark性能调优之常规性能调优

目录 0. 相关文章链接 1. 最优资源配置 2. RDD优化 2.1. RDD复用 2.2. RDD持久化 2.3. RDD尽可能早的 filter 操作 3. 并行度调节 4. 广播大变量 5. Kryo序列化 6. 调节本地化等待时长 0. 相关文章链接 Spark文章汇总 1. 最优资源配置 Spark 性能调优的第一步&…

9.Ceph部署

文章目录 Ceph部署前期环境准备实验部署软件安装部署Ceph集群部署mon节点部署OSD存储节点部署mgr节点开启监控模块管理pool Ceph部署 前期环境准备 主机名public网络cluster网络角色admin192.168.242.69admin(管理节点)node01192.168.242.66192.168.242.100.11mon、mgr、osdn…

【Elemnt-UI——el-popover点击出现多个弹框】

效果图 解决 :append-to-body"false"添加这个属性就可以了 <el-popoverv-model"item.contextmenuVisible"placement"bottom-end":append-to-body"false"trigger"click":visible-arrow"false"hide"item.…

[PCIE体系结构导读]PCIE总结(一)

什么是PCIE PCIe Peripheral Component Interconnect express 快速外部组件互联 高速串行计算机扩展总线标准 处理器系统的局部总线 连接外部设备 高速、低时延支持热插拔可靠扩展性好复杂度高点对点串行连接 附一个博主写的总结文章&#xff0c;非常好 《PCI EXPRESS体系结…

如何在服务器下载coco数据集

如果是需要在服务器上进行跑实验&#xff0c;可以直接通过wget下载链接 那么如何确定下载地址呢&#xff1f; 例如&#xff1a;先找到coco的数据集地址http://cocodataset.org 然后可以看到 可以下载一个 2017 Train/Val annotations[241MB]&#xff0c;在下载内容中可以看到…

跨域是怎么个事?

跨域是怎么个事&#xff1f; 【一】到底什么是跨域&#xff1f;【二】什么是浏览器的同源策略&#xff1f;【三】跨域问题有哪些解决方案&#xff1f;【四】详细说说Nginx解决跨域问题的实现过程&#xff1f;&#xff08;1&#xff09;项目准备 【一】到底什么是跨域&#xff1…

宋浩线性代数笔记(一)行列式的计算

本帖更新b站宋浩老师的线代网课笔记&#xff0c;内容较为细致详细&#xff0c;参考书用的是科学出版社的第三版&#xff0c;之后会附加同济出版社第六版的教材内容。 &#xff08;字不好看大家将就看吧QAQ&#xff09;

【论文阅读】一些多轮对话文章的体会 ACL 2023

前言 本文是对昨天看到的ACL 2023三篇多轮对话文章的分享这三个工作都是根据一些额外属性控制输出的工作&#xff0c;且评估的方面比较相似&#xff0c;可以借鉴 方法 这几篇文章都不是做general任务的&#xff0c;倾向于通过一些额外信息&#xff0c;来做specific任务 【1】…

当低代码和无代码平台可以加速应用程序现代化时

你的组织很可能正在寻求将遗留应用程序现代化、将单片应用架构拆分为服务&#xff0c;并迁移到公共或私有云基础架构。在此过程中&#xff0c;您可能还希望改善用户体验&#xff0c;创建CI/CD流水线&#xff0c;添加测试自动化&#xff0c;并实施一系列其他DevOps最佳实践。 这…

水声功率放大器的作用是什么

水声功率放大器是一种专门用于水声设备的高功率电子设备&#xff0c;主要用于提升水下信号的传输距离和保证语音清晰度。它的作用在水下通信、水下测量、海洋科学等领域都非常重要。 其主要作用有以下几个方面&#xff1a; 增强信号传输距离 水声信号在水中传播会受到各种因素的…

维安股份冲刺A股上市:计划募资约15亿元,上海科学院为实控人

7月17日&#xff0c;上海证券交易所对上海维安电子股份有限公司&#xff08;下称“维安股份”&#xff09;发出问询函。据贝多财经了解&#xff0c;维安股份于2023年6月20日递交招股书&#xff0c;准备在上海证券交易所主板上市。 本次冲刺上市&#xff0c;维安股份计划募资15.…

解决一云多芯全球命题,浪潮云海给出了解题思路

云计算&#xff0c;生而为用户简化资源的使用&#xff0c;让用户不必关注复杂的底层硬件架构&#xff0c;而是通过“服务”的方式调用资源&#xff0c;专注于自己的业务创新。 因此&#xff0c;用户要上云&#xff0c;就天然地要求云平台必须能够屏蔽底层的硬件架构&#xff0…