当前位置: 首页 > news >正文

从零开始手写STL库:List

从零开始手写STL库–List部分

Github链接:miniSTL


文章目录

  • 从零开始手写STL库–List部分
  • List是什么?
  • List需要包含什么函数
          • 1)基础成员函数
          • 2)核心功能
          • 3)其他功能
  • 基础成员函数的编写
  • 核心功能的编写
  • 其他功能编写
  • 总结


List是什么?

std::list是基于双向链表的的数据结构,与std::vector基于数组不同,list在频繁插入和删除的场景中更适合应用。

List需要包含什么函数

应当具有:

1)基础成员函数

构造函数:初始化List头节点
析构函数:释放内存,当运行结束后要摧毁这个List防止内存泄漏
不同于Vector,List这种以链表为基础的容器一般不需要去拷贝新的List,也就不用手动构建拷贝构造函数和拷贝赋值操作符

2)核心功能

push_back/push_front:在List的末尾/头部加入新元素
pop_back/pop_front:移除List末尾/头部元素
size:获取List长度
clear:清空List
get:获取List中某个元素的值
remove:删除某个节点
find:查找某个值对应的节点
empty:检查List是否为空

3)其他功能

迭代器、重载输出符等等

基础成员函数的编写

List的成员:
List本身是链表,那么每个节点应该包括本节点的数据、指向上/下一个节点的指针,而List是描述这一系列节点构成的双向链表,那么只需要记录头节点、尾节点以及List长度即可

template<typename T>
class myList
{
private:struct Node{T data;Node * next;Node * prev;Node(const T & data_, Node * next_ = nullptr, Node * prev_ = nullptr): data(data_), next(next_), prev(prev_) {} };Node * head;Node * tail;size_t current_size;
public:};

构造函数和析构函数就是

public:myList() : head(nullptr), tail(nullptr), current_size(0) {}~myList() {clear(); }

再次提示,这里的构造函数用current_size(0) 初始化才是合规的,放在中括号内会浪费掉这个初始化进程

析构函数调用一下清空函数即可

核心功能的编写

1、push_back/push_front函数:在List的末尾/头部加入新元素
链表不像动态数组需要考虑分配问题,所以直接加就行了

但是也需要判断List为空的清空,如果为空,head/tail是没法取出next指针的,此时就会报错

所以在push的时候检查一下

(在力扣算法题中避免这种复杂操作的方法是构建一个虚拟头节点,就可以统一处理)

    void push_back(const T & value){Node * temp = new Node(value, nullptr, tail);if(tail) tail->next = temp;else head = temp;tail = temp;current_size ++;}void push_front(const T & value){Node * temp = new Node(value, head, nullptr);if(head) head->prev = temp;else tail = temp;head = temp;current_size ++;}

2、pop_back/pop_front函数:移除List末尾/头部元素
头尾节点的删除较为简单,注意一下空列表的情况特殊处理即可

    void pop_back(){if(current_size > 0){Node * temp = tail->prev;delete tail;tail = temp;if(tail) tail->next = nullptr;else head = nullptr;current_size --;}}void pop_front(){if(current_size > 0){Node * temp = head->next;delete head;head = temp;if(head) head->prev = nullptr;else tail = nullptr;current_size --;}}

这里如果删除之后发现头/尾节点是空了,说明这个List已经空了,但是另一端还没处理,所以要让另一端也为nullptr,否则有可能发生指针越界问题。

3、size函数:获取List长度

    int size(){return current_size;}

4、clear函数:清空List
不同于vector那样直接将size归零,List需要考虑节点占用的内存,所以实际上是需要循环释放的

void clear()    
{        while (head)         {             Node * temp = head;head = head->next;             delete temp;            }         tail = nullptr;         current_size = 0;              
}

5、get函数:获取List中某个元素的值
这里的实现方法不是给定一个get函数,而是重载符号“[]”,这样就能更加方便地访问了

    T &operator[](size_t index){Node * curr = head;for(size_t i = 0; i < index; i ++){if(!curr) throw std::out_of_range("Index out of range!");curr = curr->next;}return curr->data;}const T & operator[](size_t index) const{Node * curr = head;for(size_t i = 0; i < index; i ++){if(!curr) throw std::out_of_range("Index out of range!");curr = curr->next;}return curr->data;}

这里返回值设置为引用是考虑到一下情况

myList testList;
testList[2] = 4;

如果返回的不是引用,那么这样的赋值就会失效,并不能真的修改掉List的节点元素

6、remove函数:删除某个节点
那么这里需要查找到该节点,再进行删除,而且需要注意它是头尾节点时的情况

    void remove(const T & val){Node * temp = head;while (temp && temp->data != val){temp = temp->next;}if(!temp) return;if(temp != head && temp != tail){temp->prev->next = temp->next;temp->next->prev = temp->prev;}else if(temp == head && temp == tail){head = nullptr;tail = nullptr;}else if(temp == head){head = temp->next;head->prev = nullptr;}else{tail = temp->prev;tail->next = nullptr;}current_size --;delete temp;temp = nullptr;}

7、find函数:查找某个值对应的节点
循环遍历对比就行

    Node * getNode(const T & val){Node * node = head;while (node && node->data != val){node = node->next;}return node;}T *find(const T &val){Node * node = getNode(val);if(!node) return nullptr;return & node->data;}

8、empty函数:检查List是否为空

    bool empty(){return current_size == 0;}

其他功能编写

迭代器

    Node * begin() { return head; }Node * end() { return nullptr; }const Node * begin() const { return head; }const Node * end() const { return nullptr; }

重载<<符号

template <typename T>
std::ostream &operator<<(std::ostream &os, myList<T> &pt)
{for (auto current = pt.begin(); current; current = current->next){os << " " << current->data;}os << std::endl;return os;
}

总结

List的编写中,难点在于考虑链表为空的情况,很多个函数都需要去考虑,并且处理头尾节点,实际上是个细致的工作,并不在于思路上有多难。

在经常增删的情况下,用List会比Vector更为合适,代价是内存用得比较多,空间换时间。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Huawei、Cisco 路由中 RIP 协议 summary 的用法
  • 基于深度学习的商品推荐
  • C语言航空售票系统
  • HackTheBox--Knife
  • golang 基础 泛型编程
  • DB-GPT:LLM应用的集大成者
  • 【关于PHP性能优化,内存优化,日志工具等问题处理】
  • Python面试整理-Python中的控制流语句
  • 04 B端产品经理能力培养
  • 【入门教程一】基于DE2-115的My First FPGA 工程
  • 分类损失函数 (一) torch.nn.CrossEntropyLoss()
  • 机械学习—零基础学习日志(高数09——函数图形)
  • 【iOS】——探究isKindOfClass和isMemberOfClass底层实现
  • 基于电鸿(电力鸿蒙)的边缘计算网关,支持定制
  • vite + vue3 + uniapp 项目从零搭建
  • ES2017异步函数现已正式可用
  • FineReport中如何实现自动滚屏效果
  • Fundebug计费标准解释:事件数是如何定义的?
  • JDK9: 集成 Jshell 和 Maven 项目.
  • mysql中InnoDB引擎中页的概念
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Nodejs和JavaWeb协助开发
  • Promise面试题,控制异步流程
  • SpriteKit 技巧之添加背景图片
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • 初识MongoDB分片
  • 从0实现一个tiny react(三)生命周期
  • 对超线程几个不同角度的解释
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 爬虫模拟登陆 SegmentFault
  • 如何在GitHub上创建个人博客
  • 算法---两个栈实现一个队列
  • 写代码的正确姿势
  • 原生Ajax
  • 在weex里面使用chart图表
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • NLPIR智能语义技术让大数据挖掘更简单
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • ​水经微图Web1.5.0版即将上线
  • (152)时序收敛--->(02)时序收敛二
  • (21)起落架/可伸缩相机支架
  • (Charles)如何抓取手机http的报文
  • (C语言)球球大作战
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (十三)Flask之特殊装饰器详解
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (四)鸿鹄云架构一服务注册中心
  • (四)库存超卖案例实战——优化redis分布式锁
  • (算法)Game
  • (一)appium-desktop定位元素原理
  • (一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景
  • (转)JAVA中的堆栈
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法