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

👑作者主页:@安 度 因
🏠学习社区: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; // 定位 newempty_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->_memberPtr 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.hqwc.cn/news/27970.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【学会动态规划】解码方法(4)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

跟我一起从零开始学python(九)numpy+pandas+matplotlib

前言 回顾之前讲了python语法编程 &#xff0c;必修入门基础和网络编程&#xff0c;多线程/多进程/协程等方面的内容&#xff0c;后续讲到了数据库编程篇MySQL&#xff0c;Redis&#xff0c;MongoDB篇&#xff0c;和机器学习&#xff0c;全栈开发前面没看的也不用往前翻&#…

macos使用搭建算法竞赛c/c++的g++/gcc编译环境(homebrew,含万能头,改环境变量,vscode/clion可用)

文章目录 1、homebrew安装2、安装g3、改环境变量 1、homebrew安装 我没改镜像&#xff0c;直接网上脚本一键安装的&#xff0c;具体命令忘了&#xff0c;可能是这个 反正装这个的方法很多&#xff0c;网上一搜都有。 成功装上homebrew就行。 /bin/bash -c "$(curl -fsSL…

Acwing.905 区间选点(贪心)

题目 给定N个闭区间[a,bi]&#xff0c;请你在数轴上选择尽量少的点&#xff0c;使得每个区间内至少包含一个选出的点。输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入格式 第一行包含整数N&#xff0c;表示区间数。 接下来N行&#xff0c;每行包含两个整数…

【QT】元对象系统学习笔记(一)

QT元对象系统 01、元对象系统1.1、 元对象运行原则1.2、 Q_OBJECT宏1.3、 Qt Creator启动元对象系统1.4、 命令行启动元对象&#xff08;不常用&#xff09; 02、反射机制2.1、 Qt实现反射机制2.2、 反射机制获取类中成员函数的信息2.1.1、 QMetaMethon类2.1.2、QMetaObject类 …

如何快速又高质量的输出PDF实验报告?

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 PDF文档的打印在很多应用场景中都会被使用到&#xff0c;最为常见的就是实验室信息管理…

xred病毒分析

概述 xred病毒是一种感染型病毒&#xff0c;会感染系统中特定目录下的exe和xlsx文件&#xff0c;该病毒会将自身伪装成Synaptics触摸板驱动程序&#xff0c;使用Dephi编写。 样本的基本信息 Verified: Unsigned Link date: 6:22 1992/6/20 Company: Synaptics Description: S…

华南农业大学|图像处理与分析技术综合设计|题目解答:定位数显区域并分离电表数字

设计任务 图 28 是一幅正在运行的数字电表图像&#xff08;ipa28.jpg&#xff09;&#xff0c;试采用图像处理与分析 技术&#xff0c;设计适当的算法和程序&#xff0c;找出电表的数显区域&#xff0c;计算目标区域的长宽比 和像素面积&#xff1b;并提取其中面积最大的 …

Service:微服务架构的应对之道

Service 的工作原理和 LVS、Nginx 差不多&#xff0c;Kubernetes 会给它分配一个静态 IP 地址&#xff0c;然后它再去自动管理、维护后面动态变化的 Pod 集合&#xff0c;当客户端访问 Service&#xff0c;它就根据某种策略&#xff0c;把流量转发给后面的某个 Pod。 Service 使…

Docker把公共镜像推送到harbor私服的流程(企业级)

如果构建项目时&#xff0c;使用了k8s docker Jenkins的模式。 那么我们在docker构建镜像时&#xff0c;如果需要使用了Nodejs&#xff0c;那么我们必须得从某个资源库中拉取需要的Nodejs。 在企业里&#xff0c;正常都会把自己项目涉及的库都放在harbor私服里。 下面讲一下&…

珠海电子行业实行MES系统该如何规划 mes系统供应商 先达盈致

在电子行业中&#xff0c;MES系统是提高生产效率、降低成本、优化生产计划的关键工具。但是&#xff0c;如何规划MES系统的实施才能使其大限度地提高企业的生产效率和经济效益呢&#xff1f; 首先&#xff0c;企业应该充分了解MES系统的基本概念和功能模块。MES系统中包括生产…

PowerDesigner 数据库建模使用详解

目录 一、前言 二、PowerDesigner概述 2.1 PowerDesigner核心能力 2.1.1 集成多种建模能力 2.1.2 自动生产代码能力 2.1.3 强大的逆向工程能力 2.1.4 可扩展的企业库解决方案 2.2 PowerDesigner常用的几种模型 2.2.1 概念模型 2.2.2 逻辑数据模型 2.2.3 物理模型 2.2…