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

11.vector的模拟实现

在学习了vector各个接口的作用后,为了更加了解vector的底层架构,我们还是来模拟实现一下vector容器。

如果还不了解vector容器的可以先阅读上一篇文章:10.vector容器

目录

一、说明

二、模拟实现

1.成员变量

2.构造、拷贝构造和析构函数

3.size()和capacity()

4.reserve()

5.尾插和尾删

6.begin()和end()

7.operator[ ]

测试代码:

8.指定位置插入

9.指定位置删除

10.clear

11.initializer_list支持的构造

12.swap

13.operator==

14.关于深拷贝问题

源码:

vector.h

test.cpp


一、说明

  1. 由于模板的声明和定义不能分离到两个文件,所以不再创建vector.cpp文件,而是只由两个文件构成,vector.h和test.cpp。声明和定义都会被放到vector.h文件中。
  2. 为了不与库中的vector定义冲突,所以把类放在命名空间ddd中。

二、模拟实现

1.成员变量

首先我们来看成员变量如何定义,由于vector的结构和string类似,所以我们初步可能会想到写成这样。

template<class T>
class vector {private:T* _a;size_t _size;size_t _capacity;
};

但是我们在这里尝试另一种写法,这种写法更接近底层库中的实现方法。它用三个迭代器代替实现,实质就是三个指针:

  • _start指向数组的开始位置,相当于原来的_a
  • _finish指向数组结尾的位置的下一个位置,相当于_a + _size
  • _endofstorage指向申请空间的结束位置的下一个位置,相当于_a + _capacity
template<class T>
class vector {
public:typedef T* iterator;typedef const T* const_iterator;
private:iterator _start;// 数组开始位置iterator _finish;// 数组结束位置iterator _endofstorage;// 数组容量的结束位置
};

2.构造、拷贝构造和析构函数

默认构造函数将成员变量都初始化为nullptr即可

// 默认构造
vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr)
{}// 拷贝构造
vector(const vector<T>& v) :_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(v.capacity());for (auto& e : v) {push_back(e);}
}//析构
~vector{if (_start) {delete[] _start;_start = _finish = _endofstorage = nullptr;
}

3.size()和capacity()

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

4.reserve()

reserve主要是用于扩容

void reserve(size_t n) {if (n > capacity()) {size_t oldsize = size();T* tmp = new T[n];if (_start) {memcpy(tmp, _start, sizeof(T) * oldsize);}_start = tmp;//_finish = _start + size();_finish = _start + oldsize;_endofstorage = _start + n;}
}

这里有个注意点:_finish不能直接等于_start + size(),因为size()是返回_finish - _start的值,而此时_start已经指向了tmp,所以size()的返回值会出现问题。要解决这个问题,一是先改_finish后改_start,但更常用的方式是提前存储size()的值,然后再用oldsize去改变_finish的值。

5.尾插和尾删

void push_back(const T& x) {if (_finish == _endofstorage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}void pop_back() {assert(size() > 0);--_finish;
}

6.begin()和end()

为了使用迭代器和范围for,需要先实现begin()和end(),为了支持const,需要额外的两个版本

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

7.operator[ ]

为了方便打印,需重载[ ],且需要分为const和非const版本

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

测试代码:

在实现了以上接口后我们就能进行简单的测试了

8.指定位置插入

iterator insert(iterator pos, const T& x) {assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage) {// 记录pos和_start位置的差值size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 更新pos位置pos = _start + len;}// 挪动数据iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}

这里有一个非常需要注意的问题!insert插入时如果触发了扩容,扩容后_start会指向新空间,而pos指向的位置是原空间,这就导致了迭代器失效问题。所以要提前记录pos和_start的相对位置,然后在扩容之后更新pos指向的位置。

接下来再看一个情景,关于迭代器失效问题

  • 可以看到300被成功插入了,而再次对*it操作时,1000却没有修改成功。
  • 这是因为在插入过程中触发了扩容,导致it失效。而之前在insert函数中对pos的操作只能影响函数体内的局部变量,不能反过来影响it。
  • 又因为it插入可能会触发扩容,导致迭代器失效问题,且由于扩容在不同编译器有不同操作方式,所以it什么时候失效不确定。
  • 这种情况下使用it极其危险,相当于野指针。所以我们认为,同一个迭代器进行过一次插入操作后就是失效的,将不再使用

如果真的要多次使用,可以用返回值进行重新赋值

9.指定位置删除

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

再看一个关于迭代器失效问题的场景

  • 可以看到程序直接崩溃了,因为在进行删除操作时,本质是将数据左移,而左移完成后it指向的实际已经是下一个数据了,这会导致一些问题。
  • 比如后面再++it,就相当于跳过了一个数据,即使没有越界访问,也会导致程序出错,无法完成预期目的。
  • 因此当一个迭代器进行erase操作后我们也认为它是失效的,不再使用。
  • 并且一些编译器会进行强制检查,如vs,发生这类情况时程序会直接崩溃。

而要解决这种问题,就要给erase函数设置一个返回值,返回下一个数据的位置(其实就是pos)

修改后:

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

为了不改变原pos的值,再定义一个i来实现遍历,最后返回pos

10.clear

void clear() {_finish = _start;
}

11.initializer_list支持的构造

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

12.swap

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}

13.operator==

vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}

14.关于深拷贝问题

前面实现的reserve存在深拷贝问题,因为vector是模板容器,他的函数类型也可以是各种形式。当拷贝涉及指针时,使用memcpy会只拷贝指针而不拷贝指针指向的数据,这就导致两者指向的是同一块空间,当其中一块数据修改或释放时会影响另一块的数据。

解决方式是不使用memcpy而是直接使用=来赋值,因为这样对于自定义类型,他会自动调用拷贝构造进行深拷贝。

修改后:

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

源码:

vector.h

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
using namespace std;// 模板的声明和定义不能分离到两个文件,所以不再创建Vector.cpp
namespace ddd {template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;// 数组开始位置iterator _finish;// 数组结束位置iterator _endofstorage;// 数组容量的结束位置public:// 构造vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}// initializer_list支持的常量数组初始化vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}// 拷贝构造vector(const vector<T>& v) :_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (auto& e : v) {push_back(e);}}// 析构~vector(){if (_start) {delete[] _start;_start = _finish = _endofstorage = nullptr;}}// size和capacitysize_t size() {return _finish - _start;}size_t capacity() {return _endofstorage - _start;}// begin和enditerator begin() {return _start;}iterator end() {return _finish;}const_iterator begin() const {return _start;}const_iterator end() const {return _finish;}// 扩容void reserve(size_t n) {if (n > capacity()) {size_t oldsize = size();T* tmp = new T[n];if (_start) {//memcpy(tmp, _start, sizeof(T) * oldsize);for (size_t i = 0; i < oldsize; i++) {tmp[i] = _start[i];}delete[] _start;}_start = tmp;//_finish = _start + size();_finish = _start + oldsize;_endofstorage = _start + n;}}// 重载[]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 push_back(const T& x) {if (_finish == _endofstorage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}// 尾删void pop_back() {assert(size() > 0);--_finish;}//指定位置插入iterator insert(iterator pos, const T& x) {assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage) {// 记录pos和_start位置的差值size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 更新pos位置pos = _start + len;}// 挪动数据iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}// 指定位置删除iterator erase(iterator pos) {assert(pos >= _start && pos < _finish);iterator i = pos;while (i < _finish - 1) {*i = *(i + 1);++i;}--_finish;return pos;}// 清空void clear() {_finish = _start;}// 重定义大小// 涉及到一个模板怎么给缺省参数的问题,一般是给一个匿名对象// C++对内置类型也添加了构造函数,所以内置类型也可以适配void resize(size_t n, T val = T()) {if (n > size()) {reserve(n);while (_finish != _start + n) {*_finish = val;++_finish;}}else {_finish = _start + n;}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> v){swap(v);return *this;}};
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include"vector.h"void test1() {ddd::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.pop_back();for (auto e : v) {cout << e << " ";}cout << endl;cout << v[0] << endl;cout << v.size() << endl;cout << v.capacity() << endl;
}void test2() {ddd::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;int x;cin >> x;auto it = find(v.begin(), v.end(), x);if (it != v.end()) {v.insert(it, x * 100);// 因为it插入可能会触发扩容,导致迭代器失效问题// 又由于扩容在不同编译器有不同操作方式,所以it什么时候失效不确定// 在这种情况下使用it极其危险,相当于野指针,所以坚决不能再使用//*it = 1000;}v.erase(v.begin());for (auto e : v) {cout << e << " ";}
}void test3() {ddd::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(4);v.push_back(4);v.push_back(5);for (auto e : v) {cout << e << " ";}cout << endl;// 删除所有偶数auto it = v.begin();while (it != v.end()) {if (*it % 2 == 0) {it = v.erase(it);}else {++it;}}for (auto e : v) {cout << e << " ";}
}int main() {//test1();//test2();test3();
}

总结:

以上就是本篇文章的所有内容了,如果觉得有帮助可以点赞收藏加关注支持一下!

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

相关文章:

  • 可以在什么网站做二建题目wordpress插件出错
  • 0716网站建设php图书管理系统网站开发
  • 做电子板报的网站企业宣传册文案范文
  • 深圳做公司网站推广的太原网站建设名录
  • 营口电商网站建设企业模板网站vue
  • python基本程序要素
  • 做镜像网站利润用ps怎么做网站背景
  • 中移物联ML307C模组OPENCPU笔记2
  • 专业网站制作公司排名网站建设底部
  • 优选算法---滑动窗口 题目及算法分析 代码实现
  • 做水果网站首页的图片素材免费空间使用指南
  • 基于SpringBoot的实习管理系统设计与实现
  • 47. 全排列 II
  • ru后缀的网站百度搜索怎么优化
  • 自己做的网站无法访问php网站开发最低配置
  • 手机如何做车载mp3下载网站网站都需要续费吗
  • Linux常用命令52——head显示文件开头的内容
  • muffin窗管无法获取焦点
  • 合肥网站推广哪家好制作网站计划书
  • 张家港公司网站建设云巅seo
  • Python12-聚类算法
  • 卓伊凡的第一款独立游戏-unity安装运行配置以及熟悉整体unity游戏开发和unity editor【02】-优雅草卓伊凡
  • 合肥市建设厅网站手机网站开发步骤
  • Istio服务网格方案
  • wordpress 分享后可见福州短视频seo方法
  • 网站模板下载 网盘枣庄企业网站建设
  • 网站必须做ipv6自定义图片 wordpress
  • CherryStudio+cpolar:让智能工作流突破组织边界
  • count down 87 days
  • 一家专门做开网店的网站中国网评中国网评