手撕STL——vector

目录

引言

1,了解 STL 中的 vector 

2,先来一个简易版跑起来

2_1,构造函数

2_2,扩容reserve()

2_3,push_back()

2_4,pop_back()

2_5,测试一下

3,迭代器

3_1,如何设计iterator

3_2,begin(),end()一锅端

4,功能完善

4_1,operator[ ]()

4_2,size() 和 capacity()

4_3,resize(size_t n, T  x = T())

4_4,insert()

4_5,erase()

4_6,构造函数重载——迭代器

4_7,拷贝构造函数

4_8,赋值运算符重载

4_9,析构函数

5,总结

革命尚未成功,同志仍须努力


引言

上一期我们手撕了list,这一期我们就来手撕一个vector(代码在最后),vector手撕起来就比list简单很多啦。

1,了解 STL 中的 vector 

在学习初阶数据结构的时候,顺序表咱们也撕过好多次了,有静态的,也有动态的,在STL中,vector是一个动态开辟的顺序表,但是它与咱们在数据结构阶段实现的顺序表是有差异的,在数据结构阶段实现的顺序表中,我们是通过 typename* a, int size, int capacity,作为成员变量

#define typename intstruct node
{typename* a;int size;int capacity;
}

但是哈,在vector中,就变得不一样啦。

template <typename T>
class vector
{private:T* _start;T* _finish;T* _end_of_storage;
}

STL 中的 vector,用了三个指针当成员变量,为什么要这样设计呢?

在C中,咱们用的是,struct,struct的成员变量咱们是可以直接访问的,但是在C++的class中,为了保护成员变量,咱们将其设置成private成员,咱们在外面是不能直接访问的,vector中有size()和capacity()函数,来获取size和capacity。但是其他的思路大致是不变的,那咱们就开始手撕吧。

2,先来一个简易版跑起来

2_1,构造函数

2_2,扩容reserve()

vector中数据是存在一段连续的空间,不能像list那样插入前new一个空间出来,我们必须提前开好空间,保证插入数据的时候空间一个是足够的。

reserve的实现过程很简单,在数据结构初阶阶段我们用realloc进行扩容,其大致原理就是,如果这段连续的空间后面的空间够就直接原地扩容,如果不够就找一个空间够的地方,重新开辟空间,并将原有的数据拷贝过去,最后将原有的空间释放。

我们直接用new和delete进行异地扩容,开辟好空间后再进行数据拷贝,拷贝完数据记得释放原来的空间,避免内存泄漏。

2_3,push_back()

因为顺序表头插需要挪动数据,代价是比较大的,STL中的vector也没有提供头插接口,所以这里只来一个头插

尾插分3步

1,检查需不需要扩容

2,将 _finish 位置赋值

3,++finish

2_4,pop_back()

尾删就比较简单啦,直接 --_finish,就可以了

2_5,测试一下

咱们目前还没有完成其他的函数,为了测试,咱们写一个打印函数print()

ok,没有问题,是可以跑起来了的。

3,迭代器

3_1,如何设计iterator

迭代器在STL中是一个非常重要的设计,它观察STL,当然在vector中的迭代器是比较简单的。

因为vector中的数据是存储在一段连续的空间中,咱们的iterator就可以直接由指针typedef得来。

typedef T* iterator;
typedef const T* const_iterator;

3_2,begin(),end()一锅端

4,功能完善

4_1,operator[ ]()

中括号重载是一个比较重要的函数,有他的存在我们就能很方便的插入修改数据。

4_2,size() 和 capacity()

直接用指针相减的中间元素个数的知识实现

4_3,resize(size_t n, T  x = T())

resize()的功能大家一定要清楚,他的注意功能是改变size。

如果 n  < size() 就直接删除数据,通过改变 _finish  实现

如果 size() <  n < capacity (),改变size,并进行初始化

如果  capacity () <  n,先开空间,在改变size,并进行初始化

4_4,insert()

insert 大致分三步

1,检查是否需要扩容

2,挪动数据

3,插入

这里要注意一个问题,当我们进行扩容后,原来的迭代器所指向的位置已经被delete清理了,这时候的iterator实际上是一个野指针。

为了防止出错,我们要在最开始通过指针的减法记录迭代器的位置,后面通过加法还原迭代器。

4_5,erase()

这里也用注意迭代器失效的问题

虽然我们这里没有扩容,进行删除后,迭代器仍然指向原来的位置

但是还是会有一点点风险,为了确保万无一失,咱们这里还是进行了存储迭代器位置,还原迭代器的操作。

4_6,构造函数重载——迭代器

在STL中,我们常可以看见用迭代器去构造一个容器,vector当然也可以,咱们就来实现一下

其实也是比较简单啊,通过迭代器,我们可以变量它,得到它的数据,再通过push_back()插入,遍历完了之后,vector也构造完了。为了可以使所有的迭代器都可以对其进行传参构造,这里还需要用到模板。

4_7,拷贝构造函数

根据参数直接构造。

先根据 被拷贝的对象 的 capacity 对要拷贝的对象进行扩容,再进行遍历和赋值。

4_8,赋值运算符重载

还是有传统写法与现代写法。

传统写法是 遍历参数的数据 给需要被赋值的对象进行数据的赋值

咱们这里写现代写法。

传参的时候不传引用,编译器根据咱们的拷贝构造函数进行拷贝,使用swap函数进行交换,对于参数 x 而言,它的数据存储的空间再堆上,但是x的三个成员变量会在赋值运算符重载这个函数结束的时候进行空间的回收,但是在堆的那一部分数据还是存在的。

使用 swap 函数进行交互 只是交换了 参数 x 和 待赋值的对象的三个指针,让待赋值的对象的三个成员遍历指向堆存储的数据的位置。

传统写法和现代写法原理大差不差的,只不过现代写法把拷贝数据的工作交给了编译器,实现起来更为简洁

4_9,析构函数

因为vector的数据存储在一段连续的区域,直接delete就可以了

5,总结

虽然咱们手撕了一个 vector 但是 咱们撕的只能说是一个简易版,我只是把 vector 里面常用,经典的函数带着大家手撕了一下,库里面的 vector的功能是非常强大的,而且大多函数都有多种重载。

大家可以通过这篇博客感受一下vector 的创建,也可以继续完善咱们自己的vector

革命尚未成功,同志仍须努力

#pragma once
#include <assert.h>
#include <iostream>
namespace ssy
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector(): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}vector(size_t n, T data = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){resize(n, data);}template<class InputIterator>vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){while (first != last){push_back(*first);++first;}}vector(const vector<int>& v): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){_start = new T[v.capacity()];size_t sz = v.size();for (int i = 0; i < sz; i++){_start[i] = v._start[i];}_finish = _start + sz;_end_of_storage = _start + v.capacity();}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}vector<T>& operator=(const vector<T> x){swap(x);return *this;}~vector(){if (_start){delete _start;_start = _finish = _end_of_storage = nullptr;}}size_t size(){return _finish - _start;}size_t capacity(){return _end_of_storage - _start;}void resize(size_t n, T data = T()){size_t sz = size();if (n < capacity()){_finish = _start + n;for (int i = sz; i < n; i++){_start[i] = data;}}else{reserve(n);for (int i = sz; i < n; i++){_start[i] = data;}_finish = _start + n;}}void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];int size = _finish - _start;if (_start){for (int i = 0; i < size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + size;_end_of_storage = _start + n;}}void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;++_finish;}void pop_back(){--_finish;}T& operator[](size_t pos){assert(pos < capacity());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < capacity());return _start[pos];}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}iterator insert(iterator pos, const T& data){size_t ppos = pos - _start;assert(ppos <= size());if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}size_t end = size();while (end > ppos){_start[end] = _start[end - 1];--end;}_start[end] = data;++_finish;return _start + ppos;}iterator erase(iterator pos){size_t ppos = pos - _start;assert(ppos < size());size_t len = ppos;size_t  end = size();while (ppos < end){_start[ppos] = _start[ppos + 1];ppos++;}--_finish;return _start + len;}void print(){int n = _finish - _start;for (int i = 0; i < n; i++){cout << _start[i] << " ";}cout << endl;}private:T* _start;T* _finish;T* _end_of_storage;};
}

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

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

相关文章

优恩-具备浪涌保护功能的固态继电器UNRD0610-无触点开关器件‌

MOSFET固态继电器 : 最高负载电压&#xff1a;60V 最大负载电流&#xff1a;10A 快速响应时间&#xff1a;≤1ms 低驱动电流&#xff1a;≤10mA 高绝缘性&#xff0c;输入输出间隔离电压&#xff1a;AC3000V 耐脉冲浪涌冲击能力强 符合IEC 61000-4-2 ESD标准&#xff1a…

Kaamel隐私与安全分析报告:Microsoft Recall功能评估与风险控制

本报告对Microsoft最新推出的Recall功能进行了全面隐私与安全分析。Recall是Windows 11 Copilot电脑的专属AI功能&#xff0c;允许用户以自然语言搜索曾在电脑上查看过的内容。该功能在初次发布时因严重隐私和安全问题而备受争议&#xff0c;后经微软全面重新设计。我们的分析表…

Kotlin协程Semaphore withPermit约束并发任务数量

Kotlin协程Semaphore withPermit约束并发任务数量 import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.launch import kotlinx.coroutines.runBlockingfun main() {val permits 1 /…

鸿蒙语言基础

准备工作 去鸿蒙官网下载开发环境 点击右侧预浏览&#xff0c;刷新和插销按钮&#xff0c;插销表示热更新&#xff0c;常用按钮。 基础语法 string number boolean const常量 数组 let s : string "1111"; console.log("string", s);let n : number …

C++数据结构与二叉树详解

前言&#xff1a; 在C编程的世界里&#xff0c;数据结构是构建高效程序的基石&#xff0c;而二叉树则是其中最优雅且应用广泛的数据结构之一。本文将带你深入理解二叉树的本质、实现与应用&#xff0c;助你在算法设计中游刃有余。 一、二叉树的基本概念 1. 什么是二叉树 二叉树…

浅析数据库面试问题

以下是关于数据库的一些常见面试问题: 一、基础问题 什么是数据库? 数据库是按照数据结构来组织、存储和管理数据的仓库。SQL 和 NoSQL 的区别是什么? SQL 是关系型数据库,使用表结构存储数据;NoSQL 是非关系型数据库,支持多种数据模型(如文档型、键值对型等)。什么是…

piamon实战-- 如何使用 Paimon 的 Java API 实现数据的点查

简介 Apache Paimon(原 Flink Table Store)是一款基于流批一体架构的 ​​高性能数据湖存储框架​​,支持低延迟的数据更新、实时查询和高效的键值点查(Point Lookup)。 本文将深入解析 Paimon 的点查机制,并通过 Java API 代码案例演示如何实现数据的点查功能。 一、Pai…

社交媒体时代的隐私忧虑:聚焦Facebook

在数字化时代&#xff0c;社交媒体平台已成为人们日常生活的重要组成部分。Facebook作为全球最大的社交媒体之一&#xff0c;拥有数十亿用户&#xff0c;其对个人隐私的影响和忧虑也日益凸显。本文将探讨社交媒体时代下&#xff0c;尤其是Facebook平台上的隐私问题。 数据收集…

问题:el-tree点击某节点的复选框由半选状态更改为全选状态以后,点击该节点展开,懒加载出来子节点数据以后,该节点又变为半选状态

具体问题场景&#xff1a; 用户点击父节点复选框将其从半选变为全选&#xff08;此时子节点尚未加载&#xff09;。 点击节点展开触发懒加载&#xff0c;加载子节点。 子节点加载后&#xff0c;组件重新计算父节点状态&#xff0c;发现并非所有子节点被选中&#xff0c;因此父节…

FastGPT安装前,系统环境准备工作?

1.启用适用于 Linux 的 Windows 子系统 方法一&#xff1a;打开控制面板 -> 程序 -> 启用或关闭Windows功能->勾选 “适用于Linux的Vindows子系统” 方法二&#xff1a;以管理员身份打开 PowerShell&#xff08;“开始”菜单 >“PowerShell” >单击右键 >“…

网页端调用本地应用打开本地文件(PDF、Word、excel、PPT)

一、背景原因 根据浏览器的安全策略&#xff0c;在网页端无法直接打开本地文件&#xff0c;所以需要开发者曲线救国。 二、实现步骤 前期准备&#xff1a; 确保已安装好可以打开文件的应用软件&#xff0c;如&#xff0c;WPS&#xff1b; 把要打开的文件统一放在一个文件夹&am…

EnlightenGAN:低照度图像增强

简介 简介:记录如何使用EnlightenGAN来做低照度图像增强。该论文主要是提供了一个高效无监督的生成对抗网络,通过全球局部歧视器结构,一种自我调节的感知损失融合,以及注意机制来得到无需匹配的图像增强效果。 论文题目:EnlightenGAN: Deep Light Enhancement Without P…

010数论——算法备赛

数论 模运算 一般求余都是对正整数的操作&#xff0c;如果对负数&#xff0c;不同编程语言结果可能不同。 C/javapythona>m,0<a%m<m-1 a<m,a%ma~5%32~-5%3 -21(-5)%(-3) -2~5%(-3)2-1正数&#xff1a;&#xff08;ab&#xff09;%m((a%m)(b%m))%m~正数&#xff…

初识Redis · C++客户端string

目录 前言&#xff1a; string的API使用 set get&#xff1a; expire: NX XX: mset,mget&#xff1a; getrange setrange: incr decr 前言&#xff1a; 在前文&#xff0c;我们已经学习了Redis的定制化客户端怎么来的&#xff0c;以及如何配置好Redis定制化客户端&…

GoogleCodeUtil.java

Google动态验证码实现 GoogleCodeUtil.java package zwf;import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.SecureRandom;/** https://mvnrepository.com/artifact/commons-codec/…

【Leetcode 每日一题】2176. 统计数组中相等且可以被整除的数对

问题背景 给你一个下标从 0 0 0 开始长度为 n n n 的整数数组 n u m s nums nums 和一个整数 k k k&#xff0c;请你返回满足 0 ≤ i < j < n 0 \le i < j < n 0≤i<j<n&#xff0c; n u m s [ i ] n u m s [ j ] nums[i] nums[j] nums[i]nums[j] 且…

如何校验一个字符串是否是可以正确序列化的JSON字符串呢?

方法1&#xff1a;先给一个比较暴力的方法 try {JSONObject o new JSONObject(yourString); } catch (JSONException e) {LOGGER.error("No valid json"); } 方法2&#xff1a; Object json new cn.hutool.json.JSONTokener("[{\"name\":\"t…

【路由交换方向IE认证】BGP选路原则之AS-Path属性

文章目录 一、路由器BGP路由的处理过程控制平面和转发平面选路工具 二、BGP的选路顺序选路的前提选路顺序 三、AS-Path属性选路原则AS-Path属性特性AS-Path管进还是管出呢&#xff1f;使用AS-Path对进本AS的路由进行选路验证AS-Path不接收带本AS号的路由 四、BGP邻居建立配置 一…

2025年热门项目管理软件对比:20款工具详解

本文主要盘点的工具有&#xff1a;1. PingCode; 2. Worktile; 3. Jira; 4. Trello; 5. TAPD; 6. Monday.com; 7. 进度猫; 8. 猪齿鱼; 9. 简道云; 10. Tita项目管理等等20款项目管理软件&#xff08;含免费&#xff09;。 在如今竞争激烈的商业环境中&#xff0c;项目管理软件已…

yaffs_write_new_chunk()函数解析

yaffs_write_new_chunk() 是 YAFFS&#xff08;Yet Another Flash File System&#xff09;文件系统中用于将数据写入新物理块&#xff08;chunk&#xff09;的关键函数。以下是其详细解析&#xff1a; 函数原型 int yaffs_write_new_chunk(struct yaffs_dev *dev, const u8 *…