位置: IT常识 - 正文

Vector底层实现(vector 底层原理)

编辑:rootadmin
Vector底层实现 vector的三个私有成员 :_start 记录初始位置 , _finish 记录有效字符 , _endofstoage 记录容量大小 vector会存储的类型不同,所以要用模版来定类型 typedef T* iterator; iterator _start; iterato ... Vector底层实现

推荐整理分享Vector底层实现(vector 底层原理),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:vector的底层数据结构,vector底层结构,vector 底层,vector底层结构,vector的底层原理,vector 底层,vector 底层原理,vector的底层数据结构,内容如对您有帮助,希望把文章链接给更多的朋友!

vector的三个私有成员

:_start 记录初始位置, _finish记录有效字符, _endofstoage 记录容量大小

vector会存储的类型不同,所以要用模版来定类型

typedef T* iterator;

iterator _start;iterator _finish;iterator _endofstoage;

也就是T*

构造函数的方法很多可以用迭代器的范围来构造

//用迭代器构造的构造函数

传过来的是它的迭代器的类型我们也用它的类型来接收不比加* &

三个属性先初始化

只要根据传过来的范围来push_back()即可

push_back函数后面会实现

//用迭代器构造的构造函数 template <class InputIterator> vector(InputIterator first, InputIterator last) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { while (first != last) { push_back(*first); ++first; } }

构造是也可以根据n分val来构造所以这个功能也需要提供

传过来的是n个用size_t接收因为n必须是>=0的 而val是根据类型所以用模版类型接受

有些情况下val会不传参 那么我们就会提供他的默认构造 (注意在C++中,内置类型也是有默认构造的)

三个属性初始话

先用reserve函数创建n个空间

在分别push_back()添加val

//构造n个val的构造函数 vector(size_t n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { reserve(n); for (size_t i = 0; i < n; ++i) { push_back(val); } }

因为某些情况第一个参数是int 第二个参数也是int会调用到迭代器的函数因为这两个类型更加适配,所以会出问题,所以需要再提供一个第一个参数为int的相同函数,来避免这种情况

//构造n个val的构造函数 //因为用int会调用到其他函数 所以为了区分 单独写出一个第一个为int vector(int n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { reserve(n); for (int i = 0; i < n; ++i) { push_back(val); } }

swap函数

这个函数是用来给拷贝构造使用

交换类的三个属性成员

//交换 void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstoage, v._endofstoage); }

拷贝构造

拷贝的本质是把一个有数据的拷贝给一个无数据的,只需要用这个无数据的迭代器去调用迭代器构造,给一个临时tmp,最后再用这个无数据的与临时tmp交换 即可

因为传过来的是另一个vector所以必须用vector<T>接收 C++传参是特别需要注意的,真的很容易乱

//拷贝构造 vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { vector<T> tmp(v.begin(), v.end());//复用用迭代器构造的构造函数 swap(tmp); }

赋值重载

v1=v2 是两个vector的数据赋值所以它的返回值必须是vector<t> 而传参数时也必须是vector<t>

函数本质也是交换,所以直接调用swap 这里之所以能直接调用而不影响到v2是因为函数是用传值传参,它是不会影响到v2本体的,(现代写法)

返回时是返回本体 (*this)

vector<T>& operator=(vector<T> v)//赋值重载 不用引用 现代写法 { swap(v);//现代写法 return *this; }

析构函数

判断是否为空当不为空时才需要析构 如果为空去析构会崩溃

把数据释放,并且它三个属性置空

// 资源管理 ~vector() { if (_start) { delete[] _start; _start = _finish = _endofstoage = nullptr; } }

迭代器部分

vector的迭代器本质就是指针 根据传进来的类型 iterator就是这个类型的指针

并且迭代器分为const版本和非const版本

所以需要提供两个版本

//迭代器 typedef T* iterator; typedef const T* const_iterator;

注意end返回的是你的实际有效字符而不是你的的空间多大

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

size

你的有效字符 _finish 而_finish实际上是有效字符的下一个位置

所以需要减去初始的位置得到它真正的有效字符

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

capacity

计算的是vector的空间大小也就是你的记录空间大小减去初始位置

size_t capacity() const { return _endofstoage - _start; }

【】重载

vector是支持随意访问的,所以【】的重载必不可少

返回的是vector里存储的类型 实际上就是模版类型 因为传出去也代表它需要改变所以需要传引用

T& operator[](size_t n) { assert(n < size()); //return *(_start + n); return _start[n]; //这个也可以 }

【】重载有些地方是需要提供const版本不允许更改的版本

const T& operator[](size_t n) const { assert(n < size()); //return *(_start + n); return _start[n]; //这个也可以 }Vector底层实现(vector 底层原理)

reserve

扩容空间 但不初始化

这里需要注意扩容后的三个属性更新出现的问题 正常运行会崩溃。 问题的关键:开辟一个新空间时,他们三个的类型是指针!而不是下标,一个地址更新,去用以前的指针,去更改这个新的地址,是会崩溃的,而下标是固定位置,地址在怎么更新,下标的位置就是固定的。

我们需要记录原先的有效字符个数

正常比较和扩容,只是这里用memcpy函数时,出现嵌套情况时,会出现浅拷贝情况

需要用赋值深拷贝

另外两个要更新时,要用预先存储好的数据来更新详情看注释!

void reserve(size_t n) { size_t sz = size(); if (n > capacity()) { T* tmp = new T[n]; if (_start) //如果_start为空时 就不需要拷贝数据了 { //memcpy(tmp, _start, size() * sizeof(T));//把_start的数据拷贝到tmp //这样做 在嵌套时,会发生浅拷贝 for (size_t i = 0; i < size(); i++)//正确做法是直接赋值 在vector<vector> 这种嵌套时 是深拷贝 { tmp[i] = _start[i]; } delete[] _start; } _start = tmp;//更新过后 _start就不再是nullptr } //_finish = _start + size();//× 结果为空 解引用无地址 赋值会崩溃 ==_start+(_finish-_start) 这里的_finsih最后是等于空 //而跳出函数后 _finish解引用再赋值会崩溃 因为空地址无法赋值 _finish = _start + sz;//√ 结果为_start有地址。解引用能赋值 ==_start+0 == 这里先让原先的_finish-_start==0 再+上0 因为_start已经更新过了 所以需要在开头记录_size的大小 //而更新过的_start不是空 这时候在调用size _finish-_start就不是0了 而是其他值了 _endofstoage = _start + n; }

resize

扩容,但会初始化数据

要扩容的大小大于实际空间大小(不是实际字符大小!)时,我们先开需要大小空间即可

如果n大于实际字符大小时

我们需要在实际字符的位置后开始不断添加val(要初始化的值)

如果n小于实际字符大小

那么就把实际字符大小改为 n个 即初始值+n

void resize(size_t n, T val = T()) { if (n > capacity()) { reserve(n); } if (n > size()) { while (_finish < _start + n) { *_finish = val; ++_finish; } } else { _finish = _start + n; } }

push_back()

添加字符

只要需要添加字符的函数,基本是需要检查空间是否足够的

一种情况为实际空间为0还未扩容时默认给它开4个空间,如果有空间,但满时,扩二倍即可(为什么扩容二倍?没什么!因为合适或者1.5倍,扩容其他倍数要么太小,要么太大,1.5和2的倍数是适应性最好的)

扩容好后,或者空间足够时

直接在有效字符的位置添加字符 (为什么不先++?因为前面说过,实际字符的指针实际上是指向实际字符的后一位,所以你要添加,直接在实际字符指针添加即可)

最后添加好后,实际字符++ (这也是返回真实字符时,不直接返回_finish,而是返回_finish-_start

因为嵌套的作用,此函数在insert函数实现后,可以直接复用insert就可以了

void push_back(const T& x) { /*if (_finish == _endofstoage) { size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); } *_finish = x; ++_finish;*/ insert(end(), x); }

pop_back()

此函数只需要把实际字符--即可

先判断合法性,实际字符必须大于初始值

因为嵌套的作用,此函数在erase函数实现后,可以直接复用erase就可以了

void pop_back() { /* if (_finish > _start) { --_finish; }*/ erase(end() - 1); }

insert

注意此函数会有迭代器失效问题!

通常此函数是不需要返回的,但因为迭代器失效问题,所以必须有返回值,因为返回的就是此指向此函数的指针也就是T*模版类型指针 所以用重命名的iterator即可

pos是指向要插入的位置,这个函数都是用指针来指针,所以没办法使用下标,这也是它的失效的问题之一!

首先判断是否需要扩容

上面的reserve提到过,扩容后,_start的地址是会变的,而pos也是迭代器,它也是指向这个地址的指针,它不会跟着更新地址而变化。

所以这个pos它指向的位置,是原来_start的位置,而这个位置已经被释放了,所以再去使用这个pos时,是会崩溃的!

所以为了防止这种情况,我们需要先记录这个pos的位置,等待_start的地址更新好后,我们要根据原先这个存储好pos的位置,在去用_start的地址,去更新新的pos位置,并且pos的位置不变。

然后开始移动pos后的数据给pos位置开出空间,能让val插入

最后在pos的位置插入val

再++实际空间

最后需要放回插入后的位置

那么我们在使用是,需要用迭代器去接收,才能防止迭代器的失效,因为原先的迭代器已经失效,需要根据这个返回值,来更新迭代器。

while (it != v1.end()) { if (*it % 2 == 0) { it = v1.insert(it, 100);//因为迭代器更新数据会失效 所以要用it接收 防止失效 ++ it;//返回的是插入的位置 再次++会再次遇到原来的位置 所以插入后 要自增++一次 } ++it; }

erase

此函数存在迭代器失效的问题

正常删除即可

只是返回必须返回删除后的下一个值

使用这个函数时,也是需要迭代器用函数返回值接收,来更新迭代器

while (it != v.end()) { if ((*it) % 2 == 0) { it = v.erase(it); } else { ++it; } }

clear

置空功能,只需要把有效字符置为初始值即可。

//置空 void clear() { _finish = _start;//把有效字符改为初始 }

接下来是源码

#pragma once#include<iostream>#include<assert.h>using namespace std;namespace moxuan{ template<class T> class vector { public: //迭代器 typedef T* iterator; typedef const T* const_iterator; //默认无参构造 vector() :_start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) {} //用迭代器构造的构造函数 template <class InputIterator> vector(InputIterator first, InputIterator last) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { while (first != last) { push_back(*first); ++first; } } //构造n个val的构造函数 vector(size_t n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { reserve(n); for (size_t i = 0; i < n; ++i) { push_back(val); } } //构造n个val的构造函数 //因为用int会调用到其他函数 所以为了区分 单独写出一个第一个为int vector(int n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { reserve(n); for (int i = 0; i < n; ++i) { push_back(val); } } //交换 void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstoage, v._endofstoage); } //拷贝构造 vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { vector<T> tmp(v.begin(), v.end());//复用用迭代器构造的构造函数 swap(tmp); } vector<T>& operator=(vector<T> v)//赋值重载 不用引用 现代写法 { swap(v);//现代写法 return *this; } // 资源管理 ~vector() { if (_start) { delete[] _start; _start = _finish = _endofstoage = nullptr; } } iterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin() const { return _start; } const_iterator end() const { return _finish; } size_t size() const { return _finish - _start; } size_t capacity() const { return _endofstoage - _start; } void reserve(size_t n) { size_t sz = size(); if (n > capacity()) { T* tmp = new T[n]; if (_start) //如果_start为空时 就不需要拷贝数据了 { //memcpy(tmp, _start, size() * sizeof(T));//把_start的数据拷贝到tmp //这样做 在嵌套时,会发生浅拷贝 for (size_t i = 0; i < size(); i++)//正确做法是直接赋值 在vector<vector> 这种嵌套时 是深拷贝 { tmp[i] = _start[i]; } delete[] _start; } _start = tmp;//更新过后 _start就不再是nullptr } //_finish = _start + size();//× 结果为空 解引用无地址 赋值会崩溃 ==_start+(_finish-_start) 这里的_finsih最后是等于空 //而跳出函数后 _finish解引用再赋值会崩溃 因为空地址无法赋值 _finish = _start + sz;//√ 结果为_start有地址。解引用能赋值 ==_start+0 == 这里先让原先的_finish-_start==0 再+上0 因为_start已经更新过了 所以需要在开头记录_size的大小 //而更新过的_start不是空 这时候在调用size _finish-_start就不是0了 而是其他值了 _endofstoage = _start + n; } void resize(size_t n, T val = T()) { if (n > capacity()) { reserve(n); } if (n > size()) { while (_finish < _start + n) { *_finish = val; ++_finish; } } else { _finish = _start + n; } } void push_back(const T& x) { /*if (_finish == _endofstoage) { size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); } *_finish = x; ++_finish;*/ insert(end(), x); } void pop_back() { /* if (_finish > _start) { --_finish; }*/ erase(end() - 1); } T& operator[](size_t n) { assert(n < size()); //return *(_start + n); return _start[n]; //这个也可以 } const T& operator[](size_t n) const { assert(n < size()); //return *(_start + n); return _start[n]; //这个也可以 } //插入 注意会有迭代器失效问题! iterator insert(iterator pos, const T& val) { assert(pos >= _start && pos <= _finish); if (_finish == _endofstoage) { size_t n = pos - _start;//因为更新start后 pos无法跟着更新 所以要记录pos的位置 size_t newsize = capacity() == 0 ? 4 : capacity() * 2; reserve(newsize); pos = _start + n; } iterator cur = _finish - 1; while (cur >= pos) { *(cur + 1) = *cur; --cur; } *pos = val; ++_finish; return pos; } //删除 iterator erase(iterator pos)//返回删除后的下一个位置 { assert(pos >= _start && pos < _finish); iterator it = pos+1; while (it != _finish) { *(it-1) = *it; ++it; } --_finish; return pos++;//返回删除后的下一个位置 } //置空 void clear() { _finish = _start;//把有效字符改为初始 } private: iterator _start; iterator _finish; iterator _endofstoage; };

接下来是用来测试这个vector的可行性 杨辉三角

大家可以源码拿去编译器上,然后调用这个test4函数即可

class Solution { public: vector<vector<int>> generate(int numRows) { vector<vector<int>> vv; vv.resize(numRows); for (size_t i = 0; i < vv.size(); ++i) { // 杨辉三角,每行个数依次递增 vv[i].resize(i + 1, 0); // 第一个和最后一个初始化成1 vv[i][0] = 1; vv[i][vv[i].size() - 1] = 1; } for (size_t i = 0; i < vv.size(); ++i) { for (size_t j = 0; j < vv[i].size(); ++j) { if (vv[i][j] == 0) { // 中间位置等于上一行j-1 和 j个相加 vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j]; } } } for (size_t i = 0; i < vv.size(); ++i) { for (size_t j = 0; j < vv[i].size(); ++j) { cout << vv[i][j] << " "; } cout << endl; } cout << endl; return vv; } }; void test4() { vector<vector<int>> ret = Solution().generate(5); for (size_t i = 0; i < ret.size(); ++i) { for (size_t j = 0; j < ret[i].size(); ++j) { cout << ret[i][j] << " "; } cout << endl; } cout << endl; }}

这就是本文章的全部内容,感谢您能观看到这里,如有问题请评论或私信。感谢观看!

本文链接地址:https://www.jiuchutong.com/zhishi/304543.html 转载请保留说明!

上一篇:ubuntu日志的设置(ubuntu系统日志配置文件)

下一篇:Joe是一款优雅功能强大的Typecho主题功能多上手快

  • 企业做微信营销的重要价值体现在哪些方面(做企业微信营销挣钱吗)

    企业做微信营销的重要价值体现在哪些方面(做企业微信营销挣钱吗)

  • 荣耀60怎么设置来电闪光(荣耀60怎么设置三指截屏)

    荣耀60怎么设置来电闪光(荣耀60怎么设置三指截屏)

  • 手机qq怎么开启情侣空间(手机QQ怎么开启简洁模式)

    手机qq怎么开启情侣空间(手机QQ怎么开启简洁模式)

  • intel virtualization technology什么意思(intel virtualization technology)

    intel virtualization technology什么意思(intel virtualization technology)

  • ipadpro怎么开机(ipadpro怎么开机 按哪个键)

    ipadpro怎么开机(ipadpro怎么开机 按哪个键)

  • e的x次方怎么打出来(e的x次方怎么打出来手机)

    e的x次方怎么打出来(e的x次方怎么打出来手机)

  • 微博通讯录跟手机不匹配(微博通讯录跟手机不一致)

    微博通讯录跟手机不匹配(微博通讯录跟手机不一致)

  • 淘宝永久冻结怎么解封(淘宝永久冻结怎么注销支付宝)

    淘宝永久冻结怎么解封(淘宝永久冻结怎么注销支付宝)

  • 苹果11屏幕不灵敏怎么办(苹果11屏幕不灵敏如何矫正)

    苹果11屏幕不灵敏怎么办(苹果11屏幕不灵敏如何矫正)

  • 电池没充满电就拔了有什么副作用(电池电量没充满,就拔掉使用)

    电池没充满电就拔了有什么副作用(电池电量没充满,就拔掉使用)

  • 苹果6s能升级13.3系统吗(苹果6S能升级ios15吗)

    苹果6s能升级13.3系统吗(苹果6S能升级ios15吗)

  • 软盘上第几磁道最重要(软盘的第几道损坏)

    软盘上第几磁道最重要(软盘的第几道损坏)

  • 华为手机快应用是什么意思(华为手机快应用怎么删除)

    华为手机快应用是什么意思(华为手机快应用怎么删除)

  • vue快动作怎么不能用(vue点击动画)

    vue快动作怎么不能用(vue点击动画)

  • 手机怎么设置qq密保问题(手机怎么设置qq空间不让看)

    手机怎么设置qq密保问题(手机怎么设置qq空间不让看)

  • pdcch信道是由什么组成(pdsch信道)

    pdcch信道是由什么组成(pdsch信道)

  • 小米6x可以用27w快充吗(小米6x可以用6A的数据线充电吗)

    小米6x可以用27w快充吗(小米6x可以用6A的数据线充电吗)

  • 电脑自动开关机在哪里设置(电脑自动开关机在哪里设置方法)

    电脑自动开关机在哪里设置(电脑自动开关机在哪里设置方法)

  • 10w充电器是快充吗(十瓦充电器是快充吗?)

    10w充电器是快充吗(十瓦充电器是快充吗?)

  • vivos1摄像头怎么用(vivo摄像头怎么设置)

    vivos1摄像头怎么用(vivo摄像头怎么设置)

  • 华为nova5和pro的区别(华为nova5和5pro)

    华为nova5和pro的区别(华为nova5和5pro)

  • iphone xr录屏在哪里(苹果手机xr录屏在哪)

    iphone xr录屏在哪里(苹果手机xr录屏在哪)

  • vivox27支持人脸识别吗(vivo x27人脸识别)

    vivox27支持人脸识别吗(vivo x27人脸识别)

  • inetpub是什么文件夹(inetpub文件可以删除吗)

    inetpub是什么文件夹(inetpub文件可以删除吗)

  • Vmware16虚拟机打不开怎么拷贝文件到本地?(vmware15虚拟机)

    Vmware16虚拟机打不开怎么拷贝文件到本地?(vmware15虚拟机)

  • win10商店怎么换区(windows商店如何切换地区)

    win10商店怎么换区(windows商店如何切换地区)

  • 收到个税返还手续费怎么算增值税附加
  • 企业出售商铺需要预缴增值税吗
  • 套期会计新旧准则对比
  • 应纳税额与应纳税额差额
  • 应交税费负数调整
  • 应付债券利息计入哪里
  • 税收的组成
  • 个税专项扣除需要提供哪些依据
  • 无形资产摊销是谨慎性原则吗
  • 工程造价咨询服务流程
  • 应纳出口关税怎么算
  • 跨年收到暂估费用的发票如何处理
  • 风险溢价包括哪些违约风险溢价 流动性风险溢价
  • 退回以前年度费用怎么做帐
  • 免税企业可以开具有税率的增值税专用发票吗
  • 房产转让的房产税怎么算
  • 收到注册资金要交税吗
  • 美团扣点怎么做凭证
  • 增长率应该要如何计算呢?
  • 非房地产开发企业土地增值税扣除项目
  • 营改增建筑业税率变化时间
  • win10 删除文件 没有找到项目
  • windows无法连接到system Events
  • 印花税和所得税需要计提吗
  • 账务处理程序有什么
  • 存货的核算方法
  • 防伪税控盘全额抵扣政策
  • nrm报错
  • 建筑业统一发票真伪查询
  • 商品零售企业一般具有什么特征
  • 搬迁收入增值税
  • 事业单位财产清查内容包括
  • 开发票如何计算税率
  • video.js教程
  • php事务特性
  • php搜索框查询数据库
  • jqueryfor
  • vue 绑定子组件属性
  • fastdfs和minio哪个好
  • 公司采购一直没走对公付款怎么处理
  • 异地托收承付结算金额起点为
  • 养老保险产生的利息怎么入账
  • 企业贷款贴息怎么做账
  • 电子发票报销需要签字吗
  • 小规模纳税人税率2023年是多少
  • 利息补缴税款加收利息计算
  • 收据的种类有哪些
  • 合同金额含税么
  • 财务报表是指的什么内容
  • 无形资产减值迹象有哪些
  • 公司注销时退还实收资本要交个税吗
  • 住宿费开的增值税专用发票怎么记账
  • 资产减值损失什么科目
  • 会计处理的相关知识点
  • 机票行程单上没有金额怎么报销
  • 企业购买黄金有限制吗
  • 研发费用会影响什么
  • 公允价值模式下出售投资性房地产
  • 资产评估资产如何入帐
  • 颁发数字证书要符合什么条件
  • win10怎么关闭防火系统
  • 重装系统的简写
  • win10 rundll
  • scardsvr32.exe - scardsvr32是什么进程 有什么用
  • win7系统无法更改主题
  • win8.1开不了机怎么办
  • 微软为什么这么贵
  • 塔防类的网游
  • python怎么样学
  • javascript scrollTop正解使用方法
  • 用python分析csv文件
  • linux -lc
  • django forloop
  • ubuntu下安装visual studio
  • js做运算
  • jquery创建map集合
  • 电子税务局更改密码怎么改
  • 保险免保费是什么意思
  • 河北省国家税务局电子税务局官网
  • 湖北省税务局网站授权
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设