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

Vector深度剖析及模拟实现

目录

一、vector的结构

二、size,capacity,reserve和operator[]函数的实现

三、push_back函数的实现

四、迭代器的实现

五、const迭代器的实现

六、print_vector的模板化

七、pop_back函数的实现

八、insert函数的实现

九、erase函数的实现

十、resize函数的实现

十一、默认构造函数,析构函数和拷贝构造函数

十二、赋值运算符重载

12.1 第一种写法

12.2 第二种写法

十三、代码

13.1 vector.h

13.2 test.cpp


一、vector的结构

在std中的vector是用模板来实现的,我们也使用模板。vector主要有三个成员变量,分别是T指针类型的_start,_finish,_end_of_storage.使用他们三个就可以构建一个vector。因为vector是连续存放的,所以我们也可以使用原生指针来模拟迭代器的实现。std中的vector是用模板来实现的。

代码如下:

namespace zx 
{template<class T>class vector {public:typedef T* iterator;private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}

其中_start指向的是数据的起始位置,

        _finish指向的是最后一个数据的后一个位置,

        _end_of_storage指向的是最后一个空间的下一个位置

二、size,capacity,reserve和operator[]函数的实现

size_t size() 
{return _finish - _start;
}
size_t capacity() {return _end_of_storage - _start;
}
T& operator[](size_t i) {assert(i < size());return _start[i];
}
void reserve(size_t n) {if (n > capacity()) {T* tmp = new T[n];size_t old_size = size();memcpy(tmp, _start, size() * sizeof(T));delete[] _start;_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}
}

注意reserve函数,我们要新创建一个old_size变量来存放原来的size,这是因为当我们更新_finish的时候,需要使用_start加上size,如果不保存的话,size内部的逻辑是_finish-_start.此时我们的_start已经更新了,得到的size就会出错。

三、push_back函数的实现

当我们往vector里面尾插数据的时候,我们要先来判断容量满了没有,如果满了就需要扩容。代码如下:

void push_back(const T& x) {//满了,需要扩容if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;_finish++;
}

测试代码如下:

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

四、迭代器的实现

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

测试代码如下:

void test2() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);vector<int>::iterator it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << endl;for (auto ele : v) {cout << ele << " ";}cout << endl;
}

五、const迭代器的实现

当我们想要实现一个打印vector数据的函数时,会出现bug,代码如下:

void print_vector(const vector<int>& v) {vector<int>::iterator it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << endl;
}
void test3() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print_vector(v);
}

这是因为我们的print_vector函数里面的vector是const,const对象只能调用const函数,不能调用非const函数,所以我们就需要写一个const迭代器。代码如下:

typedef const T* const_iterator;
const_iterator begin() const
{return _start;
}
const_iterator end() const
{return _finish;
}以下代码放在zx类域里面,不是放在vector类模板里面
void print_vector(const vector<int>& v) {vector<int>::const_iterator it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << endl;
}

测试代码如下:

void test3() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print_vector(v);
}

由于还有const对象,所以我们需要修改以下函数和新增const的operator[]函数

const_iterator end() const
{return _finish;
}
size_t size() const
{return _finish - _start;
}
T& operator[](size_t i) {assert(i < size());return _start[i];
}
const T& operator[](size_t i) const
{assert(i < size());return _start[i];
}

六、print_vector的模板化

在上面的print_vector函数里面,我们只能打印vector<int>类型的数据如果我们是vector<double>呢?就需要重新写一个print_vector函数来打印double类型。于是我们就可以将这个函数写成模板的形式。代码如下:

template<class T>
void print_vector(const vector<T>& v) {vector<T>::const_iterator it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << endl;
}

但是我们修改之后运行,会有一个bug如下所示:

这是由于c++语法的规定,不能去没有实例化的类模板里面去取东西,编译器不能区分这里的const_iterator是类型还是静态成员变量,所以我们就需要在前面加上一个typename,或者使用auto。代码如下所示:

	template<class T>void print_vector(const vector<T>& v) {typename vector<T>::const_iterator it = v.begin();//auto it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << endl;}

七、pop_back函数的实现

这个函数是尾删最后一个元素,在删除之前,需要判断vector是否为空。代码如下:

bool empty() {return _start == _finish;
}
void pop_back() {assert(!empty());--_finish;
}

八、insert函数的实现

再插入数据之前先扩容,代码如下:

iterator  insert(iterator pos, const T& x) {if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : 2 * capacity());}iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;end--;}*pos = x;_finish++;return pos;
}

测试代码如下:

void test4() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print_vector(v);v.insert(v.begin() + 2, 30);print_vector(v);}

运行如下:

我们发现运行时没有问题的,

上面是没有扩容时候的情况,如果我们需要扩容呢?

代码如下:

void test4() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);print_vector(v);v.insert(v.begin() + 2, 30);print_vector(v);}

此时运行,发现出bug了,这是为什么呢?这就是经典的迭代器失效的问题。

这是因为,我们扩容之后,_start,_finish,_end_of_storage都改变了,而pos指向的还是之前的旧空间的位置,所以就出bug了。修改代码如下:

iterator  insert(iterator pos, const T& x) {if (_finish == _end_of_storage) {//记录相对位置size_t relative = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + relative;}iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;end--;}*pos = x;_finish++;return pos;
}

如果我们想在第二个位置插入40,然后将第二个位置的值*10.会发生上面呢?代码如下:

void test5() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);print_vector(v);auto pos = v.begin() + 1;v.insert(pos, 40);(*pos) *= 10;print_vector(v);
}

运行结果如下:

我们发现第二个位置没有乘以10,这是因为pos还是指向的旧空间。

所以,我们在insert之后,pos就失效了,不要直接访问,要访问就要更新这个失效的迭代器的值。

在vs编译器会进行强制的pos检查。如果插入,无论扩容没有,都不能使用这个pos,访问就会报错。

九、erase函数的实现

void erase(iterator pos) {assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()) {*(it - 1) = *it;it++;}_finish--;
}

我们使用erase来测试一段代码,来删除偶数,如下所示:

void test6() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(5);auto it = v.begin();while (it!=v.end()) {if (*it % 2 == 0) {v.erase(it);}it++;}print_vector(v);
}

运行之后结果不是我们想要的,有时候还会报错。

我们在删除一个vector数据之后,会导致原本的迭代器的意义发生变化,在vs编译器里面,如果我们使用迭代器删除一个数据之后,再次使用这个迭代器,会直接报错。所以在vs编译器里面,erase也会返回一个迭代器,它指向刚刚被删除元素的下一个位置。代码如下:

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

测试删除偶数的代码如下:

void test6() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(5);auto it = v.begin();while (it!=v.end()) {if (*it % 2 == 0) {it=v.erase(it);}else {it++;}}print_vector(v);
}

我们在insert和erase之后,可能都会导致迭代器失效,所以insert会返回插入元素的迭代器,erase会返回删除元素下一个的迭代器。用来更新迭代器。

十、resize函数的实现

resize的函数原型如下:

当我们的n大于原本的size时,可能需要插入数据,如果传入了数据,就使用传入的数据,如果没有传入数据,就会创建一个匿名对象,这个匿名对象会调用默认构造函数,然后将值赋给val。为什么不使用0呢?这是因为vector里面的数据类型可能是自定义类型。如果是int或者double类型呢?int和double是没有构造函数的概念的。但是为了兼容自定义类型,还是可以使用int()。代码如下:

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

十一、默认构造函数,析构函数和拷贝构造函数

在上面的代码中,我们是没有写构造函数,析构函数和拷贝构造的,我们使用的都是类里面默认生成的,默认构造函数和析构函数都默认是空,拷贝构造函数时浅拷贝。当我们使用拷贝构造时,会导致浅拷贝,也就是一个字节一个自己的拷贝,但是我们的vector类里面是从堆区new出来的空间,使用拷贝构造时,会导致这两个vector指向同样的空间,代码如下所示:

我们打开监视窗口,发现两个vector<int> 里面的参数是一模一样的。这就是经典的浅拷贝问题。这个程序解释之后是不会报错的,这是因为我们默认的析构是空的,不会析构两次,但是如果我们在堆区申请了空间,是需要写析构函数的,我们先写一个析构函数,代码如下:

~vector()
{if (_start) {delete _start;_start = _finish = _end_of_storage = nullptr;}
}

然后再次执行上述的代码,发现程序崩了,这是因为同一块空间析构了两次。所以我们就需要重新写一个拷贝构造函数来实现深拷贝,但是在写拷贝构造函数之前,我们还需要写一个默认构造函数,这是因为如果我们没有写构造函数,会默认生成构造函数和拷贝构造,如果我们写了构造函数,就不会自动生成构造函数了,所以我们在写拷贝构造之前,需要写一个构造函数。代码如下:

vector(){}
vector(const vector<T>& v)
{reserve(v.size());for (auto& e : v) {push_back(e);}
}

十二、赋值运算符重载

12.1 第一种写法

void clear() {_finish = _start;
}
vector<T>& operator=(const vector<T>& v) {if (_start != v._start) {clear();reserve(v.size());for (auto& e : v) {push_back(e);}}return *this;
}

12.2 第二种写法

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=(vector<T> v) {swap(v);return *this;
}

十三、代码

13.1 vector.h

#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
#include<string.h>namespace zx 
{template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;vector(){}vector(const vector<T>& v){reserve(v.size());for (auto& e : v) {push_back(e);}}~vector(){if (_start) {delete _start;_start = _finish = _end_of_storage = nullptr;}}void clear() {_finish = _start;}/*vector<T>& operator=(const vector<T>& v) {if (_start != v._start) {clear();reserve(v.size());for (auto& e : v) {push_back(e);}}return *this;}*/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=(vector<T> v) {swap(v);return *this;}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 _end_of_storage - _start;}T& operator[](size_t i) {assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}void reserve(size_t n) {if (n > capacity()) {T* tmp = new T[n];size_t old_size = size();memcpy(tmp, _start, size() * sizeof(T));delete[] _start;_start = tmp;_finish = _start + old_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++;}bool empty() {return _start == _finish;}void pop_back() {assert(!empty());--_finish;}iterator  insert(iterator pos, const T& x) {if (_finish == _end_of_storage) {//记录相对位置size_t relative = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + relative;}iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;end--;}*pos = x;_finish++;return pos;}iterator erase(iterator pos) {assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()) {*(it - 1) = *it;it++;}_finish--;return pos;}void resize(size_t n,T val=T()) {if (n<size()) {_finish = _start + n;}else {reserve(n);while (_finish < _start + n) {*_finish = val;_finish++;}}}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};template<class T>void print_vector(const vector<T>& v) {typename vector<T>::const_iterator it = v.begin();//auto it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << endl;}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;}void test2() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);vector<int>::iterator it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << endl;for (auto ele : v) {cout << ele << " ";}cout << endl;}void test3() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print_vector(v);}void test4() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);print_vector(v);v.insert(v.begin() + 2, 30);print_vector(v);}void test5() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);print_vector(v);auto pos = v.begin() + 1;v.insert(pos, 40);(*pos) *= 10;print_vector(v);}void test6() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(5);auto it = v.begin();while (it!=v.end()) {if (*it % 2 == 0) {it=v.erase(it);}else {it++;}}print_vector(v);}void test7() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);vector<int> v1 = v;}void test8() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);vector<int> v1;v1.push_back(1);v1.push_back(2);v1 = v;print_vector(v1);}
}

13.2 test.cpp

#include"vector.h"int main() {//zx::test1();//zx::test2();//zx::test3();//zx::test4();//zx::test5();//zx::test6();//zx::test7();zx::test8();return 0;
}

http://www.dtcms.com/a/541410.html

相关文章:

  • Linux进程:进程属性
  • word文档做网站建立网站地图
  • 大连建设网节能办公室网站随州网站seo
  • SSM老年公寓管理系统4do68(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 网站制作佛山海拉尔网站建设平台
  • 推客系统开发:从技术架构到业务落地的全栈实现指南
  • Spring Boot 日志体系 Logback + SLF4J 深入剖析
  • Perl 格式化输出
  • 网站建设三种方法广州h5网站制作
  • 一文讲透BOM、MRP、WMS
  • ai最新的发展趋势有哪些
  • 简化AI服务构建的Python框架leptonai
  • 解决 Vray for 3ds Max 三大常见生产问题,提升渲染工作流效率
  • 做网站搭建环境网站建设在学校中的作用
  • 优秀的网站建设吉林建设厅官方网站
  • STM32H742-ARM例程32-LCD
  • 基于谱图特征解析与机器学习模型融合的非靶向污染物识别策略
  • 下载| Windows 11 ARM版10月官方ISO系统映像 (适合部分笔记本、苹果M系列芯片电脑、树莓派和部分安卓手机平板)
  • 【u-boot】重定位(ARM32)-relocate_code
  • Java Web 开发的核心组件:Servlet, JSP,Filter,Listener
  • 怎么做qq分享网站深圳中瑞建设集团官方网站
  • 20251028在荣品RD-RK3588-MID开发板的Android13系统下解决关机的时候最近打开的应用不关的问题
  • 网站建设功能套餐表怎样创建网站桌面快捷方式
  • 当 Claude Code 登陆浏览器:我们如何用 Gateone.ai 把“移动编码”从炫技变成生产力核弹
  • Labview多种通信集合
  • 炫酷文字制作网站网站建设最快多长时间
  • 【自然资源】宅地基误划为永久基本农田怎么办?
  • Python:批量下载已知gene symbol蛋白的PDB文件
  • 废旧台式机变身私有NAS
  • 探索Agent工具调用时的高熵时刻