自己动手实现简易STL

2023-10-27

之前学习C++看侯老师的书的时候实现了一下STL的基本组件,包括了6个组件,allocator, iterator, container, trait, functor, algorithm的组件,也是终于搞清楚了6个组件之间的相互关联.分享给大家。

STL六个组件

比较难理解的就是萃取器和迭代器还有算法之间的交互。其实就是一个算法库根据你迭代器属于不同类型进行的一个选择,选择相对最优的方法。下面展示一下核心部分。对应于STL的容器,分别是单向,双向,随机访问迭代器。

class input_iterator {};
class forward_iterator :public input_iterator {};
class bidirectional_iterator :public forward_iterator {};
class random_acess_iterator final: public bidirectional_iterator {};

用继承关系来表达这样一个迭代器的从属关系的原因是由于C++的一个机制,是利用重载的机制。我们首先考虑public继承关系是一个 is a 的关系,那么再这个层次的继承关系中,random_acess_iterator 是最为特殊的一个。
所以从特殊性的角度来考虑 (从特殊性来比较): random_acess_iterator > bidirectional_iterator > forward_iterator ,他们都是input_iterator。
C++的重载是有一个机制,如果有多个同时可以匹配的选择最特殊的那个,这个机制仔细想想也OK,既然我可以匹配更特殊的,何必匹配更加泛化的呢。
下面举一个最简单的算法例子就懂了。
计算两个迭代器之间的距离。

迭代器 & 算法

下面这个是调用函数,可以看到他还调用了一个_distance函数来作为辅助函数。

template<class InputIterator>
unsigned int distance(InputIterator first, InputIterator last)
{
	return _distance(first, last, Traits<InputIterator>::iterator_type());
}

辅助函数一:

template<class InputIterator>
unsigned int _distance(InputIterator first, InputIterator last, random_acess_iterator iter){
	return abs::abs(last - first);
}

第一行是为了debug的,后面一行可以看到只需要做一个减法就可以计算first到last的长度了。也就是O(1)的时间。为什么可以用减号,因为所有的随机访问迭代器都重载了 operator -
可以看到第三个参数就是做了这样一个选择。

辅助函数二:

template<class InputIterator>
unsigned int _distance(InputIterator first, InputIterator last, forward_iterator iter){
	unsigned int dis = 0;
	for (; first != last; ++first)
		++dis;
	return dis;
}

对于其他类型来说,我不能和随机访问迭代器一样,因此只能一个一个地加了,所以是O(last - first)的时间复杂度。

这里就体现了为什么需要迭代器,就是为了性能!

Q:为什么我不直接用这种重载的方式,非要加一个间接的调用层啊。
A: 因为容器有很多种,每个容器对应的迭代器实际是对应容器的特化的迭代器。但是这种重载机制需要迭代器的类型。而这个需要的类型是特化的那几种迭代器。
举个例子就是 deque和vector 都是random_acess_iterator.但是相应的算法具体是针对某个iterator。所以加入直接使用而不加这个distance的间接,是没有办法找到。或者你需要向里面一样复杂的写法,而又希望接口简单,隐藏细节,因此采用这种写法。

容器,迭代器部分

对于每个可以整迭代器的容器,都会有一个与之相当于的迭代器的,并且他们的关系是耦合的,也就是你实现一个容器,需要实现一个对应的迭代器才能加入STL的大家庭。当然也不是必须这么做,因为有的数据结构没法设计迭代器。能这么设计的好处就是你可以用标准库的算法。
下面简单列一下大概的结构。

template<class T>
class iterator{
	//用同意的来表示其数据或者容器的特性。
	using iterator_type = ...;
	...
	// 需要的重载,不多不少
	operator ++(){}
	operator ++(int){}
	operator != (const iterator&) {}
	...
};

template<class T, class Alloc = alloc>
class container{
public:
	// 表示其类型,方便萃取器trait来萃取类型。
	using value_type = T;
	using iterator = ...;
	...
	//函数实现 字段等等
	container();
	...
};

这样一说,就只有allocator没有说了,这里推荐看侯捷老师的内存管理,里面讲到了这种简单的实现,以及SGI的二层分配,也讲了一个loki的能够消除内部碎片的分配器。
这里为了简化,只是用了简单的new delete。
注意这里是不能使用 malloc 或者free的 除非你显示地调用对象析构函数。

class easy_allocator{
public:
	template<class T>
	static T* alloc(){
		return new T();
	}
	template<class T>
	static void dealloc(T* p){
		assert(p != nullptr);
		delete p;
	}
};

适配器

这个没有太多可以说的,你可以认为就是接口之间的转换。比如queue stack。举个例子就是queue 我需要先进先出 也就是push_back() & pop_front()两种操作, 因此我可以选择list deque。
那么对于stack来说 我需要push_back() & pop_back() 那么vector list deque都可以作为我的底层实现。你在使用标准库的时候也会看到你可以再模板自己定义一个类型。

stack<int, vector<int>> st;
queue<int, deque<int>> que;

仿函数

functor 就是为了让标准库的容器, 算法使用更加灵活。C++11之后很多接口可以直接用lambda 非常好用。

额外的工作

当时觉得这么写代码(CTRL + C + V)有点没有意思,于是当时想能不能稍微扩充个整个小玩具。于是封装win平台的窗口作为一个类, 实现了一个相当于一个 容器的 一个可视化。(并没有很好的完善,后面完善一下 贴出源代码。) 封装window窗口创建作为一个类 网络上可以查一下,有点点技巧。
使用方式类似与迭代器,因为是直接和容器相关的。类似于这样是直接耦合再一起

using window = window_vector<T>;

能够简单的可视化一些数据结构,也还是稍微有点意思吧。。。还增加的个按钮 可以看到你最近的操作是怎么变化的。

在这里插入图片描述
这个就是一个二叉树的啦。可以看到插入一个元素对他带来的变化。
红黑树 具体的变化:
红黑树就是不像AVL树对于高度那样严格,通过红黑节点的定义(有证明说明红黑树是和2-3-4树本质是一样的)
并且红黑树旋转的一个很重要性质就是: O(1)的旋转进行插入或者删除,对比AVL树是O(lgn)的旋转数。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这个是list的window…
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

而且用这个窗口可以很好的debug,比如树可以直观的看里面的东西对不对,print出来不太直观。
这些实现要注意窗口resize的消息,需要重新绘制一下。没有加入滚动条的功能,有时间再加。

小结

非常推荐去实现一个vector 包括所有的组件,其实也不是那么简单。不需要实现那么多算法 容器等,知道6个组件具体是怎么工作的就够了。
这里简洁一下6个组件如何交互,下篇文章会用最简单的vector举例子 示范一个具体的实现。

了解标准库,可以更好地扩展他。比如怎么使用它底层的分配器,容器等等。根据自己的需求进行选择,甚至可以做必要的扩充。

初学者,欢迎各位指出问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自己动手实现简易STL 的相关文章

  • 为什么 std::vector 可以处理类定义中的不完整类型?

    出现了以下问题 C 标准似乎说 std vector需要一个完整的类型才能工作 看https en cppreference com w cpp container vector https en cppreference com w cp
  • 未找到 DEADLINE 调度策略

    我想在 C 中实现 DEADLINE 调度策略 我知道该功能已实现Linux 3 14 10我正在使用 Ubuntu 14 04Linux 3 17 0 031700 lowlatency 201410060605 SMP PREEMPT这
  • 无法在 CUDA 中找到 1 到 100 数字的简单和?

    我正在研究使用 CUDA 的图像处理算法 在我的算法中 我想使用 CUDA 内核找到图像所有像素的总和 所以我在cuda中制作了内核方法 来测量16位灰度图像的所有像素的总和 但我得到了错误的答案 所以我在cuda中编写了一个简单的程序来查
  • 如何使用T4从一个模板同时生成两个文件?

    我遇到的情况是 我需要生成两个 CSharp 代码文件 它们的代码几乎相同 但方法的输入和输出类型的命名空间不同 事实上 每个文件都针对特定国家 地区 并且类型来自特定国家 地区的 WSDL 我正在围绕服务编写一些包装器 逻辑完全相同 但从
  • 如何在 C++ 中为指针“this”赋值

    在函数中 如何分配this一个新的价值 您可以分配对象this点于 this XY 但你不能分配直接值this this XY Error Expression is not assignable
  • 如何在 C# 中以编程方式将行添加到 DataGrid?

    正如标题所述 我正在尝试使用 C 以编程方式将行添加到 DataGrid 但我似乎无法使其工作 这是我到目前为止所拥有的 I have a DataGrid declared as dg in the XAML foreach string
  • C# 结构默认值

    我有一个方法 它接受一个包含许多具有基本数据类型的字段的结构 我想传递大部分默认值 但需要进行一些调整 但我了解结构声明中的基本字段不能包含默认值声明 例如struct S int a 42 现在是这样的 OptionsStruct opt
  • 注入包含接口的所有已注册实现的 Enumerable

    给出以下接口 public interface IMyProcessor void Process 我希望能够注册多个实现 并让我的 DI 容器将它们的可枚举注入到这样的类中 public class MyProcessorLibrary
  • 如何在 C 中链接目标文件?失败并显示“架构 x86_64 的未定义符号”

    因此 我尝试在我的文件 file2 c 中使用另一个 C file1 c 文件中定义的函数 为了做到这一点 我包含了 file1 file1 h 的标头 但是 每当我尝试使用 gcc 编译文件时 我都会收到以下错误 Undefined sy
  • ASP.NET - Crystal Report Viewer 打印按钮在 ASP.NET 中不起作用

    我正在使用 Visual Studio 2008 但我遇到了水晶报告问题 当我单击打印按钮时 它会将我带到弹出窗口 但未找到页面 弹出的网址是 http localhost aspnet client System Web 2 0 5072
  • 如何在 C++ 中正确使用 cin.fail()

    我正在编写一个程序 从用户那里获取整数输入cin gt gt iUserSel 如果用户输入一个字母 程序就会进入无限循环 我试图用下面的代码来阻止这种情况 但程序进入无限循环并打印出 错误 输入 我该如何修复我的程序 cin gt gt
  • C# 可以为控制台应用程序部分类“程序”类吗?

    我想知道是否可以将为任何控制台应用程序创建的默认 程序 类更改为部分类 我想这样做是因为我想要更好的组织 而不是将所有方法都放在按区域分类的 1 个文件中 对我来说 将某些方法类别放在单独的文件中会更有意义 我对分部类的理解是 它是多个文件
  • MINIX内部碎片2

    我正在用 C 语言编写一些软件 它递归地列出给定目录中的所有文件 现在我需要计算出内部碎片 我花了很长时间研究这个问题 发现 ext2 上的内部碎片只发生在最后一个块中 我知道理论上你应该能够从索引节点号获得第一个和最后一个块地址 但我不知
  • 将 AutomationID 与 ListView 结合使用

    我正在尝试将 AutomationId 附加到列表视图中的项目 理想情况下 将项目名称绑定到显示的项目
  • MPI - 发送和接收列

    我需要从一个进程发送矩阵列并从另一个进程接收它 我尝试运行以下程序 但得到了一个奇怪的结果 至少我这么认为 仅复制矩阵的第一个元素 某些矩阵元素会发生意外变化 include
  • 从单应性估计 R/T

    我一直在尝试计算 2 个图像中的特征 然后将这些特征传递回CameraParams R没有运气 特征已成功计算并匹配 但是问题是将它们传递回R t 我明白你必须分解Homography为了使这一点成为可能 我已经使用如下方法完成了 http
  • 如何防止 Lotus Notes 用户转发或复制通过 System.Net.Mail 发送的邮件?

    我想使用 SMTP 客户端 uiing microsft net 以 C 作为编程语言发送电子邮件 但是对于通过SMTP客户端发送的电子邮件 我们是否可以添加 禁止转发 或 禁止复制 等安全功能 我不希望电子邮件的收件人转发或复制电子邮件的
  • C# 多维数组解析

    我有一个多维数组 内容在调试器中看起来像这样 数组设置为 String s new String 6 4 A B Yes C A B Yes C A B No C A B Yes C A B Yes C A B Yes C A B No C
  • 为什么存在系统调用

    我一直在阅读有关系统调用及其在 Linux 中如何工作的内容 我还有更多的阅读要做 但我读过的一件事都没有回答 那就是 为什么我们需要系统调用 我知道系统调用是用户空间程序要求内核执行某些操作的请求 但我的问题基本上是 为什么用户空间程序本
  • C++ 中的析构函数

    我的 AB h 文件中有一个构造函数 class AB private int i public AB i 0 constructor AB i 0 destructor virtual void methodA unsigned int

随机推荐

  • Redis主从及哨兵模式配置教程

    提示 以下是本篇文章正文内容 下面案例可供参考 本文环境 CentOS7 3 Redis 5 0 7 一 Redis主从配置 1 主从搭建服务器情况 IP 角色 redis版本 192 168 223 131 主 Redis 5 0 7 1
  • C++判断输入结束的简单方法(从键盘输入+从文件读入)

    判断输入结束的简单方式 1 从键盘输入 1 最简单的方式 while cin gt gt a 当想结束时只需 换行 输入Ctrl Z 回车 此时cin gt gt a的返回值为false 例1 初始化字符数组 include
  • MySQL validatequery_Druid配置参数详解-validationQuery

    Druid配置参数详解 validationQuery Druid是一个由阿里开源的数据库连接池 Druid的配置非常丰富 但是设置不当会对生产环境造成严重影响 网上Druid的资料虽多 但大部分都是互相复制粘贴 有很多不准确甚至完全错误的
  • response.setContentType()的作用及参数

    response setContentType MIME 的作用是使客户端浏览器 区分不同种类的数据 并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据 例如web浏览器就是通过MIME类型来判断文件是GIF图片 通过MIM
  • SpringBoot 日志正确使用方式,这样才优雅!

    一 日志重要吗 程序中的日志重要吗 在回答这个问题前 笔者先说个事例 笔者印象尤深的就是去年某个同事 收到了客户反馈的紧急bug 尽管申请到了日志文件 但因为很多关键步骤没有打印日志 导致排查进度很慢 数个小时都没能排查到问题 也无法给出解
  • 基于单片机的七彩音乐喷泉设计

    目录 一 方案流程及技术规格书设计 二 系统硬件电路设计 三 软件编写及调试 四 系统调试测试与分析 前言 随着时代的进步 人们对生活质量的要求也在不断提升 因此 51单片机七彩音乐喷泉系统应运而生 它不仅可以满足人们对舒适环境的追求 而且
  • 安装mariadb启动报错

    报错如下 从这里并看不出什么端倪 7月 07 07 08 16 localhost localdomain mariadb prepare db dir 3287 Please check all of the above before s
  • MySQL添加字段和修改字段的方法

    原文地址 http database 51cto com art 201011 234549 htm MySQL添加字段的方法并不复杂 下面将为您详细介绍MySQL添加字段和修改字段等操作的实现方法 希望对您学习MySQL添加字段方面会有所
  • HBase建表函数createTable的几点说明

    HBase建表函数提供了四个重载函数 分别是 void createTable HTableDescriptor desc void createTable HTableDescriptor desc byte startKey byte
  • ctf.show web3

    打开题目 出现提示 php文件 考虑文件包含漏洞 输入参数 url etc passwd 这个报错界面出来了 说明存在文件包含漏洞 构造url值 php input 使用php协议 使用burp抓包 使用ls命令查看php下的文件 得到文件
  • 认认真真推荐几个高质量人工智能方向的优质原创公众号

    人工智能与计算机编程和数学相关性比较大 网络上的资料比较繁杂 想系统的学习人工智能谈何容易 今天给大家推荐9个原创公众号 这些公众号定期会发些高质量原创 希望可以让你更高效的学习 AI有道 一个值得关注的 AI 技术的公众号 作者红色石头是
  • 分布式session的4种解决方案

    分布式session的4种解决方案 1 cookie和session cookie和session都是用来跟踪用户身份信息的会话方式 cookie存储的数据保存在本地客户端 用户获取容易 但安全性不高 存储数据小 session存储的数据保
  • matlab plot三维图形

    偶尔 我们会用到三维图形 目前我所了解的matlab中有三种方式可以实现 分别是scatter plot3和meshgrid 具体用法如下 1 scatter x y z 其中x y z为同纬度的向量 生成的三维图是点的形式 2 x 1 0
  • Simulink单元测试

    本文使用Matlab2018a版本 一 主要使用Simulink中的Analysis下的Test Harness和Test Manager 1 创建Test Harness 前提 有测试模型 1 在测试模型里 直接右击 gt Test Ha
  • Ubuntu 18.04安装CUDA 11.4.0 cuDNN 8.2.2

    CUDA和cuDNN为NVIDIA支持GPU运算以及深度神经网络计算加速的算法库 通常需要安装以支持利用GPU加速神经网络的训练和推理 安装前需要确定主机显卡为NVIDIA显卡 且驱动安装无误 通过nvidia smi查看显卡信息和适合的C
  • 使用pyLDAvis可视化LDA结果,与解决FileNotFoundError: [Errno 2] No such file or directory: ‘https://cdn.jsdel....

    建议安装 pip install pyLDAvis 2 1 2 否则会报错 FileNotFoundError Errno 2 No such file or directory https cdn jsdelivr net gh bmab
  • java math类 平方_Java Math类

    首页 gt 基础教程 gt 常用类 gt 常用 Number Math类 Java Math类 Java 的 Math 包含了用于执行基本数学运算的属性和方法 如初等指数 对数 平方根和三角函数 这些方法基本可以分为三类 三角函数方法 指数
  • 深入理解Java虚拟机jvm-对象如何进入老年代

    HotSpot虚拟机中多数收集器都采用了分代收集来管理堆内存 那内存回收时就必须能决策哪些存 活对象应当放在新生代 哪些存活对象放在老年代中 为做到这点 虚拟机给每个对象定义了一个对象年龄 Age 计数器 存储在对象头中 对象通常在Eden
  • 视频插帧—学习笔记(算法+配置+云服务+Google-Colab)

    恰好碰到同学项目需要 了解了一下关于利用深度学习视频插帧的相关知识 在这里做一个简单的记录 目录 一 方法 论文 1 DAIN Depth Aware Video Frame Interpolation 2 FLAVR Flow Agnos
  • 自己动手实现简易STL

    自己动手实现简易STL STL六个组件 迭代器 算法 容器 迭代器部分 适配器 仿函数 额外的工作 小结 之前学习C 看侯老师的书的时候实现了一下STL的基本组件 包括了6个组件 allocator iterator container t