vector类的模拟实现

 实现基本的vector框架

参考的是STL的一些源码,实现的vector也是看起来像是一个简略版的,但是看完能对vector这个类一些接口函数更好的认识。

我们写写成员变量,先来看看STL的成元变量是那些

namespace tjl
{template<class T>class vector{public:typedef T* iterator;vector():_start(nullptr),_finish(nullptr),_eof(nullptr){}private:iterator _start;//iterator _finish;iterator _eof;//表示的是end_of_storage};}

这里的成员变量有些不一样,我们也用iterator来表示,其实这里就是一个指针,但是用迭代器的名称来称呼它,还有我们这里加上了模板,更加凸显了vector的不一样,这样的好处就是我们这个vector这个类就可以去适应各种不同的类型,达到一个____效果(填一个四字成语)。

实现构造函数

构造函数的意义就是来进行初始化,因为我们构造函数里面都是会走初始化列表的这个过程的,所以我们就可以写成这个样子。

vector():_start(nullptr),_finish(nullptr),_eof(nullptr){}

但是要注意的是我们的库里面的构造函数可不是只有这些,大家可以也来查询我们的网站来查看

vector构造函数

我们可以看到他是可以支持迭代器进行构造的,也就是说我们这里可以使用任意类型来进行构造

也可以用给值的方式,所以vector的作用还是很大的。
 

析构函数的实现

析构函数的作用就是在我们程序结束的时候对我们的空间进行释放,所以我们可以这样写

~vector(){if (_start){delete[]_start;_start = _finish = _eof = nullptr;}}

这里我们可以增加if这个判断,因为_start可能是为空的,所以我们可以写一个对它进行检查的功能。

实现vector的size()和capacity()

size_t size()const{return _finish - _start;}size_t capacity const{return _eof - _start;}

我们这里的实现细节其实只要注意的是const,因为我们可能是const对象进行调用,所以这里可以加上const,防止我们的权限进行放大。

实现push_back

这里的实现会有点小细节值的我们注意,我们先来想想push_back是在尾部插入数据·就可以了,但是我们可不可能如果当我们的空间是满的时候,就会遇到需要扩容的问题,所以这个时候我们需要先来写一个reserve的函数来进行扩容

reserve函数的实现

因为我们每次扩容的时候到要用到这个函数,比如尾插还是随机插入都会用到,那我们的思路是那些还有注意事项呢?

首先我们得先知道一个问题就是我们当什么时候才是要扩容的时候,因为reserve会传一个参数n

表示我们得开多大的空间,所以我们需要做的时候就是先判断要不要开这么大的空间。

先来看我们的代码

void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t pos = _finish - _start;if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[]_start;}_start = tmp;_finish = _start + pos;_eof = _start + n;}}

思路就是我们重新开辟一块空间,然后把原来空间的内容拷贝到新内容上,这里要注意的是当我们赋值的时候,就是给_start和_finish给值的时候要先记录pos位置,因为我们扩容的时候是重新开辟的,可能存在_finish进行赋值的时候它还是指向空,这样就出现空指针的现象了。

那我们实现reserve之后就可以继续来实现push_back函数了。

void push_back(const T& val){if (_finish == _eof){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;++_finish;}

先检查空间是不是够,如果空间不够我们就需要进行扩容,其次就是大家这里要明白一个点,为什么我们可以对_finish进行解引用,然后直接进行给值,原因就是val的类型。

实现operator[]

这个很简单直接一把过了。

	const T& operator[](size_t pos) const{return _start[pos];}

用[]进行遍历来看看。

void test1()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (int i = 0; i < v.size(); i++){cout << v[i] << " ";}
}

迭代器的实现

iterator begin(){return _start;}iterator end(){return _finish;}

因为迭代器我们必须要给它命名begin和end要不然就不能使用范围for来进行遍历,我们现在可以用迭代器来对我们的数据进行遍历,也能使用范围for,这里两种方式都写出来给大家看看。

void test1(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (int i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;for (auto e : v){cout << e << " ";}auto it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;}

这样有三种方式可以对我们进行遍历了,完成迭代器只后需要来处理一些细节问题,还是我们权限放大的问题,因为当我们用const的对象去调用的时候就不行了,这里只需要在写一个const版本的🆗了,我们可以先typedef一下。

typedef const T* const_iterator;

这样就表示这个迭代器是const修饰过的迭代器。那它的begin和end就是这样写的。

const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}

不过我们对const的迭代器只能进行读的操作就不能进行写操作了。

迭代器失效问题

这个问题是出现在insert和erase的时候是会出现问题

首先我们实现insert需要注意的细节有哪些

和我们之前遇到的顺序表其实本质是没有多大区别的,这里用的是指针,而不是数组下标,因为这里模拟实现的时候,其实我们的迭代器就是原生指针,虽然和VS里的不一样,Vs里的iterator并不是原生指针,所以很好实现我们这里的insert,来看看代码吧。

iterator insert(iterator pos, const T& val = T()){assert(pos >= begin());assert(pos <= end());size_t len = pos - _start;if (_finish == _eof){reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = val;++_finish;return pos;}

其实这里我就一步到位了,但是还是有很多的细节值的我们去注意,第一个就是为什么说这里会有迭代器失效的问题,首先就是如果我们步扩容的时候迭代器是不会失效的,但是扩容之后的pos还是指向原来控空间的,因为我们这里的扩容的步骤是开空间,然后对我们的内容进行值拷贝,释放之前的空间,这样pos就是一个野指针,所以我们需要做的就是更新pos位置到新的空间上,可以先记住pos的相对位置,然后更新。所以这里这个坑是没有位置的,还有一个需要注意的是我们需要更新外面的pos,因为形参的改变是不会影响实参的改变的,所以这里的一个重要的步骤就是返回pos的值。

迭代器的失效:第一个重要的原因就是这个指针变成野指针了,我们需要更新它

第二个原因就是当我们进行insert的时候需要进行返回,因为我们在这个函数内改变了,但是在外面还是没有进行改变(形参的改变是不会影响实参的改变的)

我们来进行测试一下

void test2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v){cout << e << " ";}cout << endl;auto pos = find(v.begin(), v.end(), 2);v.insert(pos, 100);for (auto e : v){cout << e << " ";}}

发现最后的结果也是正确的,这样还要提醒大家,我们认为insert和erase之后的迭代器是失效的,不再使用,虽然我们可以接受pos位置,但是最好还是不要使用。

实现erase

iterator erase(iterator pos){assert(pos >= begin());assert(pos < end());iterator start = pos;while (start < _finish){*start = *(start + 1);start++;}_finish--;return pos;}

这个erase也是很简单,就是移动进行数据的覆盖就可以解决问题了。我们也来测试一下看看结果是不是对的。

void test3(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v){cout << e << " ";}cout << endl;auto pos = find(v.begin(), v.end(), 2);v.erase(pos);for (auto e : v){cout << e << " ";}cout << endl;}

深浅拷贝问题

先来实现拷贝构造。

拷贝构造的实现可以有现代写法和原始写法,我们先来写原始写法那个,然后引出我们的深浅拷贝的问题。

	vector(const vector<T>& v){reserve(v.capacity());memcpy(_start, v._start, sizeof(T) * v.size());}

我们这里进行的值拷贝,是按照字节的拷贝的,如果我们T是内置类型的时候,我们的的代码是不会出现问题的,但是如果是string或者里面还是一个vector<int>的时候代码就会出现问题,我们可以先来看看string的结果。

void test4(){vector<string> v;v.push_back("111111");v.push_back("111111");v.push_back("111111");for (auto e : v){cout << e << " ";}cout << endl;vector<string> v1(v);for (auto e : v1){cout << e << " ";}}

我们这样写的时候就是会出现错误,原因是我们这里的拷贝构造是进行的值拷贝,不过如果我们不写这个拷贝构造的时候是不会出现问题的,原因string会去调用它自己的拷贝构造。

但是其根本原因还是我们扩容的是进行的是值拷贝和我们没有写拷贝构造造成的问题。现在来修改一下reserve。

void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t pos = _finish - _start;if (_start){//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}delete[]_start;}_start = tmp;_finish = _start + pos;_eof = _start + n;}}

然后加上我们的考拷贝构造。

vector(const vector<T>& v){reserve(v.capacity());for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_eof = _start + v.capacity();}

这个就是原始的写法,这样我们的代码是没有问题的,但是这里为什么没有问题的第一个原因就是string是我们库里面的,进行赋值的时候会去调用它的赋值重载,但是如果我们这里的类型是vector<vector<int>>又会出现问题,我们可以来看看,如果我们需要拷贝一个杨辉三角的时候,会不会有问题。

下面来演示一下,我们可以先把杨辉三角的代码直接先拿过来

class Solution {public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv;vv.resize(numRows, vector<int>());//进行初始化//进行的是每行初始化,因为这里表示的是顺序表里面是个顺序表for (int i = 0; i < vv.size(); i++)//初始化没列{vv[i].resize(i + 1, 0);vv[i][0] = vv[i][vv[i].size() - 1] = 1;}for (int i = 0; i < vv.size(); i++){for (int j = 0; j < vv[i].size(); j++){if (vv[i][j] == 0){vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];}}}return vv;}};

然后进行拷贝一个杨辉三角,看看有没有什么问题

void test5(){vector<vector<int>> v = Solution().generate(5);auto v1(v);for (int i = 0; i < v1.size(); i++){for (int j = 0; j < v1[i].size(); j++){cout << v1[i][j] << " ";}cout << endl;}}

熟悉的感觉,真是太美妙了 

那我们其实可以通过调试来看看问题所在的地方。

 

可以看到什么不一样的地方,一个就是我们的外面的大vector是完成了深拷贝,里面还是没有,为什么其他类型的string就可以,因为string有它自己的赋值重载,我们这里没有写vector的赋值重载,所以才会有这样的问题,那我们只需要写一个赋值重载就可以解决问题了。

 

void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_eof, v._eof);}vector<T>& operator=( vector<T>& v){swap(v);return *this;}

这样我们的问题就能够很好的解决了。

那我们再来完善一下其他的接口函数就解决了。

pop_back的实现
void pop_back(){erase(--end());}
	template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}

完整的代码实现

#pragma once
#include<assert.h>
namespace tjl
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr),_finish(nullptr),_eof(nullptr){}vector(size_t n, const T& val = T()):_start(nullptr), _finish(nullptr), _eof(nullptr){reserve(n);while (n--){push_back(val);}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_eof, v._eof);}vector<T>& operator=( vector<T> v){swap(v);return *this;}vector(int n, const T& val = T()):_start(nullptr), _finish(nullptr), _eof(nullptr){reserve(n);while (n--){push_back(val);}}template<class inputiterator>vector(inputiterator first, inputiterator last): _start(nullptr), _finish(nullptr), _eof(nullptr){while (first != last){push_back(*first);++first;}}vector(const vector<T>& v): _start(nullptr), _finish(nullptr), _eof(nullptr){reserve(v.capacity());for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_eof = _start + v.capacity();}void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t pos = _finish - _start;if (_start){//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}delete[]_start;}_start = tmp;_finish = _start + pos;_eof = _start + n;}}iterator begin(){return _start;}iterator end(){return _finish;}const T& operator[](size_t pos) const{return _start[pos];}T& operator[](size_t pos) {return _start[pos];}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else {reserve(n);while (_finish != _start + n){*_finish = val;_finish++;}}}void push_back(const T& val){if (_finish == _eof){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;++_finish;}size_t size()const{return _finish - _start;}size_t capacity() const{return _eof - _start;}iterator insert(iterator pos, const T& val = T()){assert(pos >= begin());assert(pos <= end());size_t len = pos - _start;if (_finish == _eof){reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = val;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= begin());assert(pos < end());iterator start = pos;while (start < _finish){*start = *(start + 1);start++;}_finish--;return pos;}~vector(){if (_start){delete[]_start;_start = _finish = _eof = nullptr;}}private:iterator _start;//iterator _finish;iterator _eof;//表示的是end_of_storage};class Solution {public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv;vv.resize(numRows, vector<int>());//进行初始化//进行的是每行初始化,因为这里表示的是顺序表里面是个顺序表for (int i = 0; i < vv.size(); i++)//初始化没列{vv[i].resize(i + 1, 0);vv[i][0] = vv[i][vv[i].size() - 1] = 1;}for (int i = 0; i < vv.size(); i++){for (int j = 0; j < vv[i].size(); j++){if (vv[i][j] == 0){vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];}}}return vv;}};void test1(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (int i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;for (auto e : v){cout << e << " ";}auto it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;}void test2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v){cout << e << " ";}cout << endl;auto pos = find(v.begin(), v.end(), 2);v.insert(pos, 100);for (auto e : v){cout << e << " ";}}void test3(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v){cout << e << " ";}cout << endl;auto pos = find(v.begin(), v.end(), 2);v.erase(pos);for (auto e : v){cout << e << " ";}cout << endl;}void test4(){vector<string> v;v.push_back("111111");v.push_back("111111");v.push_back("111111");for (auto e : v){cout << e << " ";}cout << endl;vector<string> v1(v);for (auto e : v1){cout << e << " ";}}void test5(){vector<vector<int>> v = Solution().generate(5);auto v1(v);for (int i = 0; i < v1.size(); i++){for (int j = 0; j < v1[i].size(); j++){cout << v1[i][j] << " ";}cout << endl;}}}

今天的分享就到这里了,我们下次再见!!!!

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

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

相关文章

11.Swift数组

Swift 数组 在 Swift 中&#xff0c;数组是一种用于存储相同类型数据的有序集合。Swift 的数组是类型安全的&#xff0c;可以存储任意类型的数据&#xff0c;但数组中的所有元素类型必须相同。以下是 Swift 中常用的数组操作&#xff1a; 1. 创建数组 可以使用数组字面量语法…

c#内置委托

C#语言中有许多内置的委托&#xff0c;其中一些是常用的&#xff0c;包括&#xff1a; Action&#xff1a;表示不带返回值的方法的委托。它可以接受多个参数&#xff0c;但不返回任何值。 Action<int, string> actionDelegate (x, y) > Console.WriteLine("Ac…

Guitar Pro正版多少钱 Guitar Pro购买后永久使用吗

相信很多玩吉他的小伙伴都听说过Guitar Pro这款软件&#xff0c;Guitar Pro是一款传奇的吉他谱软件&#xff0c;可以用来打谱&#xff0c;看谱&#xff0c;midi音序制作等等&#xff0c;同时做为一款吉他学习辅助软件有着强大的优势&#xff0c;那大家知道Guitar Pro正版多少钱…

C++进阶(十二)lambda可变参数包装器

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、新的类功能1、默认成员函数2、类成员变量初始化3、 强制生成默认函数的关键字default:4、…

【数据结构】链表OJ面试题2《分割小于x并排序链表、回文结构、相交链表》+解析

1.前言 前五题在这http://t.csdnimg.cn/UeggB 休息一天&#xff0c;今天继续刷题&#xff01; 2.OJ题目训练 1. 编写代码&#xff0c;以给定值x为基准将链表分割成两部分&#xff0c;所有小于x的结点排在大于或等于x的结点之前 。链表分割_牛客题霸_牛客网 思路 既然涉及…

(35)IP地址无效化

文章目录 每日一言题目解题思路代码结语 每日一言 台阶是一层一层筑起的&#xff0c;目前的现实是未来理想的基础。只想将来&#xff0c;不从近处现实着手&#xff0c;就没有基础&#xff0c;就会流于幻想。——徐特立 题目 题目链接&#xff1a;IP地址无效化 给你一个有效的…

什么是网络渗透,应当如何防护?

什么是网络渗透 网络渗透是攻击者常用的一种攻击手段&#xff0c;也是一种综合的高级攻击技术&#xff0c;同时网络渗透也是安全工作者所研究的一个课题&#xff0c;在他们口中通常被称为"渗透测试(Penetration Test)"。无论是网络渗透(Network Penetration)还是渗透…

C++初阶之类与对象(上)详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.前言 二.类的定义和使用 2.1类的引入 2.2类的定义和访问限定…

Java学习-常用API(一)

Object类 Object类及其常用方法&#xff1a; 代码示例&#xff1a; Objects Objects类的引入&#xff0c;定义及其常见的方法&#xff1a; 示例 包装类 什么是包装类&#xff1f; 自动装箱和自动拆箱&#xff1a; 常用方法&#xff1a; 注意&#xff1a;字符串的 数值&#xf…

1Panel面板如何安装并结合内网穿透实现远程访问本地管理界面

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

写函数判断闰年

实现函数判断year是不是润年。 下表显示了 C 语言支持的所有算术运算符。假设变量 A 的值为 10&#xff0c;变量 B 的值为 20&#xff0c;则&#xff1a; 运算符描述实例/分子除以分母B / A 将得到 2%取模运算符&#xff0c;整除后的余数B % A 将得到 0 已经知道判断闰年标准…

2024 Google Chrome 浏览器回退安装旧版本

2024 Google Chrome 浏览器回退安装旧版本 查看当前谷歌版本备份浏览器数据卸载浏览器双击重新安装旧版本浏览器 查看当前谷歌版本 详细参考&#xff1a;参考 笔记&#xff1a;最近谷歌浏览器更新后&#xff0c;用着总感觉别扭&#xff1a;不习惯 备份浏览器数据 &#xff…

微服务-微服务Alibaba-Nacos 源码分析 (源码流程图)-2.0.1

客户端注册临时实例&#xff0c;GRPC处理 客户端服务发现 及订阅处理

CloudStack中控制台虚拟机调试

在CloudStack环境中&#xff0c;有时我们需要对系统虚拟机进行调试或者替换其中的JAR包。本文将详细介绍如何通过SSH连接到CloudStack的系统虚拟机&#xff0c;并进行相关的调试和JAR包替换操作。 1. 连接系统虚拟机 首先&#xff0c;我们需要使用SSH连接到目标系统虚拟机。这…

13、gitlab

13、gitlab 4c8g、100g docker安装gitlab&#xff08;使用k8s的ingress暴露&#xff09; 版本&#xff1a;https://gitlab.com/gitlab-org/gitlab-foss/-/tags?sortversion_desc 官方docker仓库&#xff1a;https://hub.docker.com/r/gitlab/gitlab-ce/tags docker pull gi…

为什么要使用Node.JS

脚本语言需要一个解析器才能运行&#xff0c;JavaScript是脚本语言&#xff0c;在不同的位置有不一样的解析器&#xff0c;如写入html的js语言&#xff0c;浏览器是它的解析器角色。而对于需要独立运行的js&#xff0c;nodejs就是一个解析器。 每一种解析器都是一个运行环境&a…

力扣经典题:单值二叉树

思路&#xff1a;验证根节点与左右节点的关系即可&#xff0c;然后向下遍历&#xff0c;此题值得注意的点在于要考虑左右节点为空的情况 bool isUnivalTree(struct TreeNode* root) {if(rootNULL){return true;}if(root->left!NULL&&root->val!root->left-&g…

Hadoop3.x基础(4)- Yarn

来源&#xff1a;B站尚硅谷 目录 Yarn资源调度器Yarn基础架构Yarn工作机制作业提交全过程Yarn调度器和调度算法先进先出调度器&#xff08;FIFO&#xff09;容量调度器&#xff08;Capacity Scheduler&#xff09;公平调度器&#xff08;Fair Scheduler&#xff09; Yarn常用命…

【数据分析】Excel中的常用函数公式总结

目录 0 引用方式0.1 相对引用0.2 绝对引用0.3 混合引用0.4 3D引用0.5 命名引用 1 基础函数1.1 加法、减法、乘法和除法1.2 平均数1.3 求和1.4 最大值和最小值 2 文本函数2.1 合并单元格内容2.2 查找2.3 替换 3 逻辑函数3.1 IF函数3.2 AND和OR函数3.3 IFERROR函数 4 统计函数4.1…

Elasticsearch中Document Routing特性

Document Routing在Elasticsearch中是一种高级特性&#xff0c;它允许用户在索引文档时指定一个路由值。通过这种方式&#xff0c;可以确保具有相同路由值的所有文档都存储在同一个分片中。这对于提高查询效率特别有用&#xff0c;因为它允许查询只针对包含相关文档的特定分片&…