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

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;}}}

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

相关文章:

  • 学习Spring的第十六天
  • 风行智能电视G32Y 强制刷机升级方法,附刷机升级数据MstarUpgrade.bin
  • Node.js-1
  • visual studio注册码
  • 20240202在WIN10下使用whisper.cpp
  • mysql入门到精通005-基础篇-约束
  • 大规模机器学习简介
  • Netty源码系列 之 EventLoop run()方法 源码
  • Swift Combine 发布者订阅者操作者 从入门到精通二
  • 蓝桥杯嵌入式第8届真题(完成) STM32G431
  • yt-dlp快速上手
  • vim最简单命令学习
  • Python脚本之操作Elasticsearch【二】
  • C# async/await的使用
  • kubernetes部署nacos2.3.0
  • @jsonView过滤属性
  • 3.7、@ResponseBody 和 @RestController
  • 30秒的PHP代码片段(1)数组 - Array
  • create-react-app做的留言板
  • CSS魔法堂:Absolute Positioning就这个样
  • es6--symbol
  • ES6系列(二)变量的解构赋值
  • Javascript编码规范
  • JavaScript标准库系列——Math对象和Date对象(二)
  • JavaScript学习总结——原型
  • Python 反序列化安全问题(二)
  • windows-nginx-https-本地配置
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 编写符合Python风格的对象
  • 讲清楚之javascript作用域
  • 聊聊directory traversal attack
  • 实现简单的正则表达式引擎
  • 使用 @font-face
  • 使用agvtool更改app version/build
  • 《天龙八部3D》Unity技术方案揭秘
  • 说说我为什么看好Spring Cloud Alibaba
  • # centos7下FFmpeg环境部署记录
  • #include
  • #pragam once 和 #ifndef 预编译头
  • #pragma data_seg 共享数据区(转)
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • (solr系列:一)使用tomcat部署solr服务
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (简单) HDU 2612 Find a way,BFS。
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (转) 深度模型优化性能 调参
  • (转)Linux下编译安装log4cxx
  • .htaccess配置重写url引擎
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .Net - 类的介绍
  • .net php 通信,flash与asp/php/asp.net通信的方法
  • .NET Remoting学习笔记(三)信道
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .Net各种迷惑命名解释