【C++】vector的模拟实现

💗个人主页💗
⭐个人专栏——C++学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

导读

1. vector的核心框架接口

2. 构造函数

2.1 基本构造

2.2 拷贝构造(传统写法)

2.3 析构函数

2.4 operator=运算符重载(传统写法)

2.5 swap函数

2.5 operator=运算符重载(现代写法)

3. vector遍历

3.1 size()和capacity()

3.2 operator[]遍历

3.3 迭代器和范围for

4. vector常见函数

4.1 reserve函数

4.2 resize函数

4.3 insert函数

4.4 erase函数

4.5 push_back函数

4.6 pop_back函数

4.7 empty函数

5. 构造函数和拷贝构造完善

5.1 拷贝构造(现代写法)

5.2 构造函数

初始化构造

 列表构造

区间构造

6. 代码整理

6.1 vector.h文件

6.2 test.cpp文件


导读

我们在上期讲解了vector的一下基本使用,今天我们来模拟实现一下vector。

1. vector的核心框架接口

vector类有三个成员变量:start,finish和end_of_storage。

这三个成员变量可以用于遍历vector中的元素、确定vector的大小和容量,或者进行其他操作。

  1. start:指向vector中第一个元素的指针或迭代器。它表示vector中数据的起始位置。

  2. finish:指向vector中最后一个元素的下一个位置的指针或迭代器。它表示vector中数据的结束位置。

  3. end_of_storage:指向vector内部存储空间的末尾的指针或迭代器。它表示vector内部存储空间的结束位置。

start、finish和end_of_storage三者之间的关系是:start <= finish <= end_of_storage。

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

2. 构造函数

2.1 基本构造

我们在上面已经初始化过了。

    vector(){}

2.2 拷贝构造(传统写法)

创建了一个大小与传入向量的容量相同的动态数组,并将其地址赋给临时指针tmp。

接下来的for循环遍历传入向量的每个元素,并逐个将其赋值给临时数组中对应位置的元素。

最后,将临时数组的起始地址赋给数据成员_start,将结束地址赋给数据成员_finish,将容量末尾地址赋给数据成员_end_of_storage,完成拷贝构造函数的工作。

        vector(const vector& v){//capacity()和size()后面会写实现方法T* tmp = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){tmp[i] = v[i];}_start = tmp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();}

2.3 析构函数

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

2.4 operator=运算符重载(传统写法)

通过判断this指针与传入向量的地址是否相等来避免自赋值的情况。

如果不是自赋值,首先通过delete[] _start;释放当前对象的动态数组

然后通过new T[v.capacity()];重新分配一个大小与传入向量的容量相同的动态数组,并将其起始地址赋给_start

再用使用memcpy函数将传入向量的数据复制到重新分配的动态数组中。


vector<T>& operator=(const vector<T>& v)
{if (this != &v){delete[] _start;_start = new T[v.capacity()];memcpy(_start, v._start, sizeof(T) * v.size());}return *this;
}

2.5 swap函数

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

2.5 operator=运算符重载(现代写法)

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

3. vector遍历

3.1 size()和capacity()

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

3.2 operator[]遍历

可以通过下标访问向量中的元素,并且根据是常量还是非常量对象,返回相应的引用类型或常引用类型,以实现读写和只读的访问操作。

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

3.3 迭代器和范围for

begin和end获取

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

测试代码:

	void test_vector1(){vector<int> v1 = { 1,2,3,4 };vector<int>::iterator it = v1.begin();while (it != v1.end()){*it += 1;cout << *it << " ";++it;}cout << endl;for (auto e : v1){cout << e << " ";}}

4. vector常见函数

4.1 reserve函数

我们在进行使用循环遍历 _start 到 _finish 之间的元素,并将其赋值给 tmp 指针所指向的位置之前,需要记录之前的size大小。

这是因为我们后面需要先把原先_start给释放,如果不提前记录就会丢失数据,不知道原先有多少个元素,也就无法确定新的_finish的位置。

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

4.2 resize函数

调用 reserve(n) 函数来预留至少能容纳 n 个元素的内存空间。

然后,通过一个循环将指定值 val 添加到 _finish 指针所指向的位置,直到 _finish 指针达到新的大小 n,即 _finish < _start + n

如果 n 小于当前大小,则需要进行大小调整。

将 _finish 指针指向新的末尾位置 _finish = _start + n,即删除多余的元素。

这样就实现了调整向量的大小为 n,并在必要时添加或删除元素。

		void resize(size_t n, const T& val = T()){if (n > size()){reserve(n);// 插入while (_finish < _start + n){*_finish = val;++_finish;}}else{//删除_finish = _start + n;}}

4.3 insert函数

首先,使用断言 assert 来确保插入位置 pos 在有效范围内,即在 _start 和 _finish 之间。

然后,检查向量是否已满,即 _finish 是否等于 _endofstorage。如果已满,需要进行内存扩容。

调用 reserve() 函数来扩容向量的容量。如果当前容量为0,则扩容为4;否则扩容为当前容量的两倍。

然后,通过计算 len 来获取插入位置在 _start 中的偏移量。

然后将 pos 更新为 _start + len,即新的插入位置。

接下来,使用迭代器 iterator it 初始化为 _finish 的前一个位置。

然后,通过一个循环将 it 从 _finish - 1 向 pos 移动,将每个元素依次向后移动一个位置。

在循环中,每个元素通过赋值运算符 = 将其值赋给下一个位置 *(it + 1)

完成元素的移动后,将插入位置 pos 赋值为 val

最后,将 _finish 指针向后移动一个位置,即新增了一个元素。

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

4.4 erase函数

首先,使用断言 assert 来确保删除位置 pos 在有效范围内,即在 _start 和 _finish 之间。

然后,将迭代器 iterator it 初始化为 pos 的下一个位置,即 pos + 1

然后,通过一个循环将 it 从 pos + 1 向 _finish 移动,将每个元素向前移动一个位置。

在循环中,每个元素通过赋值运算符 = 将其值赋给上一个位置 *(it - 1)

完成元素的移动后,将 _finish 指针向前移动一个位置,即删除了一个元素。

最后,返回被删除元素的位置 pos

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

4.5 push_back函数

我们前面既然已经实现了insert函数,那就直接调用,在尾部插入一个元素。

		void push_back(const T& val){insert(end(), val);}

4.6 pop_back函数

和前面一样,直接调用已经实现的erase函数,删除尾部元素即可。

		void pop_back(){erase(end() - 1);}

4.7 empty函数

直接判断_start和_finish是不是在同一位置。

		bool empty(){return _start == _finish;}

5. 构造函数和拷贝构造完善

5.1 拷贝构造(现代写法)

我们既然已经实现了push_back函数,可以直接开辟一个新的空间,

用尾插的形式把原先的数据拷贝过来。

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

5.2 构造函数

初始化构造

实现了一个构造函数,用于通过重复一个值来初始化向量对象。

第一个构造函数接受一个整数 n 和一个值 value,默认为类型 T 的默认值 T()。首先调用 reserve 函数来保证向量的容量至少为 n,然后通过一个循环调用 push_back 函数 n 次,将值 value 添加到向量的末尾。

第二个构造函数接受一个 size_t 类型的参数 n 和一个值 value,其余与第一个构造函数类似。

		vector(int n, const T& value = T()){reserve(n);for (int i = 0; i < n; i++){push_back(value);}}vector(size_t n, const T& value = T()){reserve(n);for (int i = 0; i < n; i++){push_back(value);}}

 列表构造

构造函数接受一个 initializer_list<T> 类型的参数 il,表示初始化列表。首先调用 reserve 函数来保证向量的容量至少为初始化列表中的元素个数,然后通过一个循环遍历初始化列表,将每个元素都添加到向量的末尾,使用 push_back 函数实现。

这个构造函数的作用是创建一个包含初始化列表中的元素的向量对象。通过初始化列表可以直接在创建向量对象的同时,指定向量的初始内容。

	// vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };	vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}

区间构造

构造函数接受两个迭代器类型的参数 first 和 last,表示要复制的元素的范围。

通过一个循环,将从 first 到 last 的元素依次调用 push_back 函数添加到向量的末尾。

这个构造函数的作用是创建一个包含迭代器范围内元素的向量对象。通过传入迭代器范围,可以将其他容器中的元素复制到向量中,或者从一个自定义的数据结构中取出元素并添加到向量中。这样可以方便地初始化向量对象,而不必手动逐个添加元素。

		template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}

6. 代码整理

6.1 vector.h文件

#pragma once#include <assert.h>
namespace my_vector
{template<class T>class vector{public:typedef T* iterator;typedef T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}vector(){}//v2(v1)构造函数vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}// vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}vector(int n, const T& value = T()){reserve(n);for (int i = 0; i < n; i++){push_back(value);}}vector(size_t n, const T& value = T()){reserve(n);for (int i = 0; i < n; i++){push_back(value);}}// 类模板的成员函数可以是函数模板template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}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;}~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t old_size = size();for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_endofstorage = tmp + n;}}void resize(size_t n, const T& val = T()){if (n > size()){reserve(n);// 插入while (_finish < _start + n){*_finish = val;++_finish;}}else{//删除_finish = _start + n;}}bool empty(){return _start == _finish;}void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator it = _finish - 1;while (it>=pos){*(it + 1) = *it;--it;}*pos = val;++_finish;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;}void push_back(const T& val){insert(end(), val);}void pop_back(){erase(end() - 1);}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};// 函数模板template<class T>void print_vector(const vector<T>&v){for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;}void test_vector1(){vector<int> v1 = { 1,2,3,4 };vector<int>::iterator it = v1.begin();while (it != v1.end()){*it += 1;cout << *it << " ";++it;}cout << endl;for (auto e : v1){cout << e << " ";}}
}

6.2 test.cpp文件

#include <iostream>
using namespace std;
#include "vector.h"int main()
{my_vector::test_vector1();return 0;
}

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

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

相关文章

《python》poetry install下载缓慢,网络问题断开连接--poetry换源镜像下载

在使用打包工具poetry进行打包的是出现了一个问题就是&#xff0c;在使用poetry进行打包的时候出现了&#xff0c;连接断开这样的问题&#xff0c;这个问题是可以通过换源&#xff0c;通过国内的镜像来解决这个问题就可以了。 找到项目中的pyoroject。toml文件这个文件中写了一…

FJSP:蛇鹫优化算法(SBOA)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

详细介绍 FJSP&#xff1a;蛇鹫优化算法&#xff08;Secretary bird optimization algorithm&#xff0c;SBOA&#xff09;求解柔性作业车间调度问题&#xff08;FJSP&#xff09;&#xff0c;提供MATLAB代码-CSDN博客 完整MATLAB代码 FJSP&#xff1a;蛇鹫优化算法&#xff…

设计模式(十三)行为型模式---命令模式(command)

文章目录 命令模式简介结构UML图具体实现UML图代码实现 命令模式简介 命令模式&#xff08;command pattern&#xff09;也叫动作模式或者事务模式。它是将请求&#xff08;命令&#xff09;封装成对象&#xff0c;使得可以用不同的请求对客户端进行参数化&#xff0c;具体的请…

梯度下降: 01.原理与代码实操

1. 简介 梯度下降法(GradientDescent) 算法,不像多元线性回归那样是一个具体做回归任务的算法,而是一个非常通用的优化算法来帮助一些机器学习算法(都是无约束最优化问题)求解出最优解,所谓的通用就是很多机器学习算法都是用梯度下降,甚至深度学习也是用它来求解最优解。…

区块链--Ubuntu上搭建以太坊私有链

1、搭建私链所需环境 操作系统&#xff1a;ubuntu16.04&#xff0c;开虚拟机的话要至少4G&#xff0c;否则会影响测试挖矿时的速度 软件&#xff1a; geth客户端 Mist和Ethereum Wallet&#xff1a;Releases ethereum/mist GitHub 2、安装geth客户端 sudo apt-get update …

Java——常见进制

在计算机领域有四种比较常见的进制&#xff0c;分别是二进制、八进制、十进制和十六进制。 一、二进制&#xff08;Binary&#xff09; 二进制&#xff08;Binary&#xff09;是一种基数为2的数值系统&#xff0c;仅使用两个符号&#xff1a;0和1。所以它的进位规则就是逢二进…

MYSQL一、MYSQL的了解

一、MySQL概述 1、数据库相关概念 为了方便&#xff0c;我们一般把mysql数据库管理系统简称位mysql数据库 通过可以操作数据库管理系统&#xff0c;然后再通过数据库管理系统操作&#xff08;数据库&#xff09;和&#xff08;数据库里面的数据&#xff09; 2、当前主流的关系…

视频汇聚EasyCVR平台视图库GA/T 1400协议与GB/T 28181协议的区别

在公安和公共安全领域&#xff0c;视频图像信息的应用日益广泛&#xff0c;尤其是在监控、安防和应急指挥等方面。为了实现视频信息的有效传输、接收和处理&#xff0c;GA/T 1400和GB/T 28181这两个协议被广泛应用。虽然两者都服务于视频信息处理的目的&#xff0c;但它们在实际…

Llama(一):Mac M1芯片运行Llama3

目录 安装Ollama for Mac 下载Llama 3模型 运行Llama3 试用Llama3 在命令行中使用Llama3 背景 本地环境&#xff1a;Mac M1,16GB内存 安装Ollama for Mac 官方地址 https://ollama.com/download/Ollama-darwin.zip 链接: 百度网盘 提取码: 8wqx 下载Llama 3模型 oll…

从国产低代码龙头企业零赛云看零(低)代码在软件开发行业和工业企业的应用趋势

从国产低代码龙头企业零赛云看零&#xff08;低&#xff09;代码在软件开发行业和工业企业的应用趋势 --- 什么是零&#xff08;低&#xff09;代码&#xff1f;零&#xff08;低&#xff09;代码开发完整指南 一、什么是零&#xff08;低&#xff09;代码&#xff1f; 零&am…

pdb文件名称被修改导致pdb文件加载失败的实战排查案例分享

目录 1、概述 2、问题说明 3、pdb文件加载失败的可能原因有哪些&#xff1f; 4、使用!sym noisy打开pdb加载详情&#xff0c;发现pdb文件名称确实被修改了 5、Windbg是如何知道要加载pdb文件名称的&#xff1f; C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表…

linux进程加载和启动过程分析

我们的源代码通过预处理,编译,汇编,链接后形成可执行文件,那么当我们在终端敲下指令$ ./a.out argv1 argv2 后,操作系统是怎么将我们的可执行文件加载并运行的呢? 首先知道,计算机的操作系统的启动程序是写死在硬件上的,每次计算机上电时,都将自动加载启动程序,之后…

翻译《The Old New Thing》- What a drag: Dragging a Uniform Resource Locator (URL)

What a drag: Dragging a Uniform Resource Locator (URL) - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080312-00/?p23133 Raymond Chen 2008年03月12日 麻烦的拖拽&#xff1a;拖拽统一资源定位符&#xff08;URL&#xff09; 简要 …

Java(十二)---认识异常

文章目录 前言1. 异常的概念与体系结构1.1.异常的概念1.异常的体系1.3 异常的分类 2. 异常的处理2.1 防御式编程2.2 异常的抛出2.3 异常的捕获2.3.1 异常声明throws2.3.2 try-catch捕获并处理2.3.3 finally 2.4 异常的处理流程 3. 自定义异常类 前言 这一篇就是咱们学习JavaSE…

C++ : 模板初阶

标题&#xff1a;C : 模板初阶 水墨不写bug 正文开始&#xff1a; C语言的问题 &#xff1a; 写不完的swap函数 在学习C语言时&#xff0c;我们有一个经常使用的函数swap函数&#xff0c;它可以将两个对象的值交换。 我们通常这样实现它&#xff1a; void swap(int t1,int t…

【vue实战项目】通用管理系统:作业列表

目录 目录 1.前言 2.后端API 3.前端API 4.组件 5.分页 6.封装组件 1.前言 本文是博主前端Vue实战系列中的一篇文章&#xff0c;本系列将会带大家一起从0开始一步步完整的做完一个小项目&#xff0c;让你找到Vue实战的技巧和感觉。 专栏地址&#xff1a; https://blog…

uni-app解决表格uni-table样式问题

一、如何让表格文字只显示一行&#xff0c;超出部分用省略号表示 步骤 &#xff1a; 给table设置table-layout:fixed; 列宽由表格宽度和列宽度设定。&#xff08;默认是由单元格内容设定&#xff09;让表格元素继承父元素宽度固定table-layout: inherit;overflow: hidden;超过…

list的简单模拟实现

文章目录 目录 文章目录 前言 一、使用list时的注意事项 1.list不支持std库中的sort排序 2.去重操作 3.splice拼接 二、list的接口实现 1.源码中的节点 2.源码中的构造函数 3.哨兵位头节点 4.尾插和头插 5.迭代器* 5.1 迭代器中的operator和-- 5.2其他迭代器中的接口 5.3迭代器…

【气象常用】剖面图

效果图&#xff1a; 主要步骤&#xff1a; 1. 数据准备&#xff1a;我用的era5的散度数据&#xff08;大家替换为自己的就好啦&#xff0c;era5数据下载方法可以看这里【数据下载】ERA5 各高度层月平均数据下载_era5月平均数据-CSDN博客&#xff09; 2. 数据处理&#xff1a…

windows10系统64位安装delphiXE11.2完整教程

windows10系统64位安装delphiXE11.2完整教程 https://altd.embarcadero.com/download/radstudio/11.0/radstudio_11_106491a.iso XE11.1 https://altd.embarcadero.com/download/radstudio/11.0/RADStudio_11_2_10937a.iso XE11.2 关键使用文件在以下内容&#xff1a;windows10…