vector模拟实现【C++】

文章目录

  • 全部的实现代码放在了文章末尾
  • 准备工作
    • 包含头文件
    • 定义命名空间和类
      • 类的成员变量
  • 迭代器
    • 迭代器获取函数
  • 构造函数
    • 默认构造
    • 使用n个值构造
    • 迭代器区间构造
    • 解决迭代器区间构造和用n个值构造的冲突
    • 拷贝构造
  • 析构函数
  • swap【交换函数】
  • 赋值运算符重载
  • empty
  • size和capacity
  • operator[]
  • reserve【调整容量大小】
  • resize【调整size大小】
  • push_back
  • assign【把所有数据替换成迭代器区间中的数据】
  • insert
    • 为什么扩容会导致pos迭代器失效?
    • 为什么要返回pos-1?
  • erase
    • 为什么要返回pos?
  • 全部代码

全部的实现代码放在了文章末尾

准备工作

创建两个文件,一个头文件myvector.hpp,一个源文件 tesr.cpp
【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件myvector.hpp中】

  1. mystring.h:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义

  2. test.cpp:存放main函数,以及测试代码


包含头文件

  1. iostream:用于输入输出

  2. assert.h:用于使用报错函数assert


定义命名空间和类

在文件myvector.hpp中定义上一个命名空间myvector
把vector类和它的成员函数放进命名空间封装起来,防止与包含的头文件中的函数/变量重名的冲突问题

类的成员变量

参考了stl源码中的vector的实现
成员变量有3个,都是迭代器

在这里插入图片描述
画图理解一下
在这里插入图片描述


迭代器

迭代器
因为存放数据的空间是从堆区申请的连续的内存,且只是简单模拟

所以我用了指针T*作为普通迭代器,const T*作为const迭代器
T是vector中存储的数据的类型

直接把T*重命名为iterator,把const T*重命名为const_iterator就完成了迭代器的实现

在这里插入图片描述

迭代器获取函数

在这里插入图片描述

因为const修饰的对象只能调用const修饰的成员函数
所以如果是const修饰的对象调用begin()和end()的时候,就会自动调用到const修饰的begin和end.


构造函数

默认构造

因为stl库里实现的默认构造是没开空间的,所以默认构造直接让3个成员变量都为nullptr就行

直接在声明的时候给缺省值,缺省值会传给成员初始化列表

在这里插入图片描述

而成员初始化列表会比构造函数先调用
并且每个构造函数调用之前都会先调用成员初始化列表,这样不管调用哪一个构造函数初始化,都会先把3个成员变量初始化成nullptr


使用n个值构造

在这里插入图片描述


迭代器区间构造

在这里插入图片描述


解决迭代器区间构造和用n个值构造的冲突

当重载了迭代器区间构造和用n个值构造的时候
如果传入的两个参数都是int类型的话就会报错

为什么?
因为在模板函数构成重载时,编译器会调用更合适的那一个
什么叫更合适?
就是不会类型转

如果传入的两个参数都是int类型,那么调用的应该是使用n个值构造,因为没有int类型的迭代器

但是使用n个值构造的第一个参数是size_t,int传进去要隐式类型转换
而调用迭代器区间构造,两个int的实参传进去,就会直接把InputIterator推导成int,不会发生类型转换,所以编译器会调用迭代器区间构造

解决方法:
再重载一个使用n个值构造的函数,把第一个参数改成int
在这里插入图片描述


拷贝构造

因为成员申请了堆区空间,所以要深拷贝
【不知道什么是深拷贝的可以看我这篇文章:类和对象【三】析构函数和拷贝构造函数】
在这里插入图片描述


析构函数

在这里插入图片描述


swap【交换函数】

因为存放数据的空间是在堆区开辟的,用3个成员变量去指向的

所以直接交换两个对象的成员变量就可以了

不需要拷贝数据

在这里插入图片描述


赋值运算符重载

因为成员申请了堆区空间,所以要深拷贝
【不知道什么是深拷贝的可以看我这篇文章:类和对象【三】析构函数和拷贝构造函数】

在这里插入图片描述
为什么上面的两句代码就可以完成深拷贝呢?
这是因为:

使用了传值传参,会在传参之前调用拷贝构造,再把拷贝构造出的临时对象作为参数传递进去

赋值运算符的左操作数,*this再与传入的临时对象obj交换,就直接完成了拷贝

在函数结束之后,存储在栈区的obj再函数结束之后,obj生命周期结束

obj调用析构函数,把指向的从*this那里交换来的不需要的空间销毁


empty

在这里插入图片描述


size和capacity

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述


operator[]

在这里插入图片描述
因为
const修饰的对象只能调用const修饰的成员函数

所以const对象只会调用下面的那个重载


reserve【调整容量大小】

在这里插入图片描述


resize【调整size大小】

在这里插入图片描述


push_back

在这里插入图片描述


assign【把所有数据替换成迭代器区间中的数据】

在这里插入图片描述


insert

	iterator insert(iterator pos, const T& val){assert(pos <= _finish);  防止插入的位置是 越界的if (_finish == _end_of_storage)  如果容量满了{记录一下扩容前的pos与start的相对位置因为扩容的话会导致pos迭代器失效size_t n = pos-_start;if (capacity() == 0)  如果容量为0reserve(2);else  容量不为0,就扩2reserve(capacity() * 2);更新pospos = _start + n;}iterator it = end()-1;把pos及其之后的数据向后挪动一位while (it >= pos){*(it + 1) = *it;it--;}_finish++;*pos = val;插入数据返回指向新插入的数据的迭代器  用于处理迭代器失效问题return pos-1;}

为什么扩容会导致pos迭代器失效?

在这里插入图片描述
因为扩容之后原来的空间被释放了
又因为使用的扩容方式是reserve所以那3个成员变量的值扩容后可以指向正确的位置。
但是pos如果不更新的话,就还是指向被释放的空间,就成了野指针了。

更新方法也很简单,保存扩容之前的pos与start的相对距离n,扩容之后再让pos=_start+n就可以了。

为什么要返回pos-1?

这是stl库里面处理迭代器失效的方法之一
因为我们在使用stl库里面的insert函数的时候,是不知道什么时候会扩容的【每个平台实现的vector是不同的
只能默认使用了之后传进去pos,在调用一次insert之后就失效了,失效的迭代器是不能使用的。
所以如果还要用pos就要把它更新一下,stl库里提供的更新方式就是:
==让pos接收insert的返回值。【pos是传值调用,形参改变不影响实参】==并且规定insert的返回值要是指向新插入的数据的迭代器


erase

在这里插入图片描述

为什么要返回pos?

因为使用了erase之后的迭代器也会失效,需要提供更新的方法

为什么使用了erase之后的迭代器会失效?

  1. 不确定是否删除到一定数据时,会不会减小容量,以适应size
    此时和insert的一样,因为不能部分释放,所以会把原来的空间释放掉,申请新空间
  2. 不确定是否删除的是最后一个数据,如果是那么调用完erase之后pos指向的就
    不是vector的有效数据范围了

所以和insert一样,调用了erase之后如果还要使用pos,就要接收返回值。

stl库里面规定erase的返回值是指向删除数据的下一个数据的迭代器,因为挪动覆盖的原因,下一个数据就是pos指向的数据,所以返回pos【没有接收返回值的迭代器,在检测较严格的编译器中,不管指向的位置是否正确,都会禁止使用,使用了就报错


全部代码

#include<iostream>
#include<assert.h>using namespace std;namespace myvector
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector(){}vector(size_t n,const T& val=T()){_start = new T[n];//从堆区可容纳申请n个元素大小的空间_finish = _start;//还没有 有效数据 时start与finish重合_end_of_storage = _start + n;//指向最大容量 的 下一个位置for (size_t i = 0; i < n; i++)//循环n次{push_back(val);//把数据尾插进去}}vector(int n, const T& val = T()){_start = new T[n];_finish = _start;_end_of_storage = _start + n;for (size_t i = 0; i < n; i++){push_back(val);}}template<class InputIterator>vector(InputIterator first, InputIterator last){//使用迭代器进行循环while (first != last){push_back(*first);//把数据尾插进去++first;}}void swap(vector<T>& obj){//使用库里面的swap交换3个成员变量std::swap(_start, obj._start);std::swap(_finish, obj._finish);std::swap(_end_of_storage, obj._end_of_storage);}vector(const vector<T>& obj){size_t size = obj.size();//记录有效数据个数size_t capacity = obj.capacity();//记录容量大小_start = new T[capacity];//申请与obj相同大小的空间_end_of_storage = _start + capacity;//指向最大容量 的 下一个位置for (size_t i = 0; i < size; i++)//循环size次{_start[i] = obj._start[i];//把有效数据拷贝过去}_finish = _start + size;//指向最后一个有效数据的  下一个 位置}~vector(){//释放从堆区申请的空间delete[] _start;//把3个成员变量  置空_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}vector<T>& operator= (vector<T> obj){swap(obj);return *this;}bool empty() const{//如果size等于0,就是空的return size() == 0;}size_t size()const{//finish指向最后一个有效数据的 下一个//start指向第一个有效数据//两个指针相减就是  两个指针之间的  数据个数return _finish - _start;}size_t capacity()const{//end_of_storage指向最大容量的 下一个位置//start指向第一个有效数据//两个指针相减就是  两个指针之间的  数据个数return _end_of_storage - _start;}T& operator[] (size_t n){//防止越界访问assert(n < size());//因为start是T*类型,所以可以像数组一样直接随机访问return _start[n];}const T& operator[] (size_t n) const{//防止越界访问assert(n < size());//因为start是T*类型,所以可以像数组一样直接随机访问return _start[n];}iterator begin()//普通起始迭代器{return _start;}iterator end()// 普通结束迭代器{return _finish;}const_iterator begin()const//const起始迭代器{return _start;}const_iterator end()const//const结束迭代器{return _finish;}void reserve(size_t n){if (n > capacity())//要调整的容量n,大于capacity才扩容{size_t origsize = size();//记录扩容前的sizeT* tmp = new T[n];//申请空间//把原来的数据拷贝到  新空间for (size_t i = 0; i <origsize; i++){tmp[i] = _start[i];}delete[] _start;//释放旧空间//让成员变量指向  新的空间的相对位置_start = tmp;_finish = _start + origsize;_end_of_storage = _start + n;}}void resize(size_t n, const T& val = T()){if (size() == n)//如果size与要调整的n相等return;//直接返回else if (size() < n)//如果size小于n{if (n > capacity())//如果n大于capacity{reserve(n);//把容量调整到n}//再把size到n 之间的空间用  val填上for (size_t i = size(); i < n; i++){push_back(val);}}else//如果size  大于  n{//就调整标识有效数据的末尾的finish//让size=_finish - _start = n_finish = _start + n;}}void push_back(const T&val){if (_end_of_storage == nullptr)//如果容量为0{reserve(2);//把容量调整到可容纳 2个元素大小}else if (_finish==_end_of_storage)//容量满了{reserve(capacity()*2);//扩容}//在下标为size【最后一个有效数据的下一个】//插入值_start[size()] = val;_finish++;//更新有效数据的末尾}void pop_back(){assret(size() > 0);_finish--;}template <class InputIterator>void assign(InputIterator first, InputIterator last){delete[] _start;//释放原来申请的空间//把3个成员变量置空_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;while (first != last){//一个一个尾插进去push_back(*first);++first;}}iterator insert(iterator pos, const T& val){assert(pos <= _finish);//防止插入的位置是 越界的if (_finish == _end_of_storage)//如果容量满了{//记录一下扩容前的pos与start的相对位置//因为扩容的话会导致pos迭代器失效size_t n = pos-_start;if (capacity() == 0)//如果容量为0reserve(2);else//容量不为0,就扩2倍reserve(capacity() * 2);//更新pospos = _start + n;}iterator it = end()-1;//把pos及其之后的数据向后挪动一位while (it >= pos){*(it + 1) = *it;it--;}_finish++;*pos = val;//插入数据return pos-1;}template <class InputIterator>void insert(iterator pos, InputIterator first, InputIterator last){assert(pos <= _finish);size_t len = 0;InputIterator in = first;while (in != last){in++;len++;}if (_finish + len >= _end_of_storage){size_t n = pos - _start;if (capacity() == 0){reserve(len);}reserve(capacity()+len);pos = _start + n;}iterator it = end() - 1;while (it >= pos){*(it + len) = *it;it--;}_finish+=len;it = pos;while (it != pos + len){*it = *first;it++;first++;}}iterator erase(iterator pos){// 防止传入的pos 是越界的assert(pos < _finish);iterator it = pos;//把pos之后的数据都向前挪动一位,把pos指向的位置给覆盖掉while (it <end()-1){*it = *(it + 1);it++;}//更新数据末尾 迭代器_finish--;//返回posreturn pos;}iterator erase(iterator first, iterator last){assert(first >= begin());assert(last <= end());iterator fi = first;iterator la = last;size_t len = last - first;while (la != end()){*fi = *la;fi++;la++;}_finish -= len;return first;}private://start指向从堆区申请的空间的  起始  位置,与begin()返回的迭代器相等//标识有效数组的开始iterator _start = nullptr;//finish指向  最后一个有效数据的  下一个位置,与end()返回的迭代器相等//标识有效数据的结束iterator _finish = nullptr;//_end_of_storage指向从堆区申请的空间的 末尾的 下一个位置//标识容量iterator _end_of_storage = nullptr;};
}

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

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

相关文章

字符串知识点

API API和API帮助文档 API:目前是JDK中提供的各种功能的Java类。 这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这些类如何使用即可。 API帮助文档&#xff1a;帮助开发人员更好的使用API和查询API的一个工具。 String概…

【Linux】线程封装与互斥(万字)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 C多线程的用法 对原生线程进行一次封装 理解pthread线程 Linux线程互斥 进程线程间的互斥相关背景概念 互斥量mutex 操作共享变量会有问题的售票…

eventloop 事件循环机制 (猜答案)

// eventloop 事件循环机制// console.log(555);setTimeout(() > {console.log(666);})let p new Promise((resolve,reject)>{// 同步执行console.log(111);resolve();});// promise 的回调函数是异步的微任务p.then(v > {console.log(222);}, r > {console.log(r…

解决ps暂存盘已满的问题

点击编辑->首选项->暂存盘 ps默认暂存盘使用的是c盘&#xff0c;我们改成d盘即可 然后重启ps

OpenSSL的一些使用案例

目录 一、介绍 二、基本使用 1、Shell &#xff08;1&#xff09;文件加解密 &#xff08;2&#xff09;生成密钥文件 2、API &#xff08;1&#xff09;md5sum &#xff08;2&#xff09;AES256加解密 一、介绍 本篇博客重点不是详细描述 OpenSSL 的用法&#xff0c;只…

snap和apt的区别简单了解

Linux中没有tree命令的时候提示安装的时候出现了两个命令&#xff0c;简单看了看两者有何区别&#xff08;一般用apt就可以了&#xff09;&#xff1a; sudo snap install tree 和 sudo apt install tree 这两个命令都是用来安装 tree 命令行工具的&#xff0c;但它们使用的是不…

antfu/ni 在 Windows 下的安装

问题 全局安装 ni 之后&#xff0c;第一次使用会有这个问题 解决 在 powershell 中输入 Remove-Item Alias:ni -Force -ErrorAction Ignore之后再次运行 ni Windows 11 下的 Powershell 环境配置 可以参考 https://github.com/antfu-collective/ni?tabreadme-ov-file#how …

SpringBoot源码阅读3-启动原理

SpringBootApplication public class DistApplication {public static void main(String[] args) {// 启动入口SpringApplication.run()SpringApplication.run(DistApplication.class, args);} }1、服务构建 这里"服务"指的是SpringApplication对象&#xff0c;服务…

【QT】概述|对象树模型|两种控件模式|信号和槽|lambda

目录 什么是QT 特点 QT程序 main函数 QT按钮 纯代码模式 图形化模式 对象树模型 信号和槽 连接与断开 自动连接 断开连接 信号的发射 lambda表达式 基本语法 捕获列表 Lambda表达式用于信号与槽的连接 例如 什么是QT Qt是一个跨平台的C图形用户界面应用…

WAF的新选择,雷池 SafeLine-安装动态防护使用指南

什么是 WAF WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。 区别于传统防火墙&#xff0c;WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击&#xff1b; 通俗来讲&#…

【数据库】Oracle安装报错(win10安装oracle提示环境不满足最低要求)

目录 一、问题场景&#xff1a; 二、问题描述 三、原因分析&#xff1a; 四、解决方案&#xff1a; 一、问题场景&#xff1a; 安装Oracle数据库 二、问题描述 安装之前提示&#xff08; [INS-13001]环境不满足最低要求。 是否确实要继续? &#xff09; 如图所示&…

运维锅总浅析云原生DevOps工具

本文从Tekton与Kubevela、Jenkins、GitLab CI的区别与联系对常见的云原生DevOps工具进行对比分析&#xff0c;最后给出DevOps工具选型思路。希望对您有所帮助&#xff01; 一、DevOps简介 DevOps是一种结合了软件开发&#xff08;Development&#xff09;和IT运维&#xff08…

1、项目基础

1、系统架构图 2、项目业务组成 3、技术选型 3.1 前端 vue3 ts sass axios 3.2后端 spring-cloud系列 gateway openfeign spring-cloud-alibaba系列 nacos sentinel seata

开放式耳机怎么选?五大2024年口碑销量爆棚机型力荐!

在选购开放式耳机的时候&#xff0c;我们总会因为有太多的选择而陷入两难&#xff0c;又想要一个颜值比较高的&#xff0c;又想要同时兼顾性能还不错的&#xff0c;所以作为测评博主&#xff0c;今天我们就给大家带来自己的一些选购技巧和自己觉得还不错开放式耳机&#xff0c;…

TP8/6 子域名绑定应用

原www.xxx.com/admin改为admincms.xxx.com config/app.php

3d模型里地毯的材质怎么赋予?---模大狮模型网

在进行3D建模时&#xff0c;赋予地毯逼真的材质是营造现实感和增强场景氛围的重要步骤。模大狮将介绍在常见的3D建模软件中&#xff0c;如何有效地为地毯赋予各种材质&#xff0c;以及一些实用的技巧和注意事项。 一、选择合适的地毯材质 在3D建模中&#xff0c;地毯的材质选择…

uniapp实现可拖动悬浮按钮(最新版2024-7月)

此章主要介绍如何使用uniapp跨平台开发&#xff0c;实现悬浮按钮&#xff0c;移动端经常会有所这样的需求&#xff0c;那么功能如下&#xff1a; 1.圆圈悬浮球&#xff0c;上下左右靠边显示 2.可以界面任何拖动&#xff0c;不会超出界面 3.单击悬浮球的点击事件 效果&#xf…

C++ 和C#的差别

首先把眼睛瞪大&#xff0c;然后憋住一口气&#xff0c;读下去&#xff1a; 1、CPP 就是C plus plus的缩写&#xff0c;中国大陆的程序员圈子中通常被读做"C加加"&#xff0c;而西方的程序员通常读做"C plus plus"&#xff0c;它是一种使用非常广泛的计算…

【Linux】线程——线程的概念、线程的特点、线程的优点和缺点、线程和进程、线程函数的使用

文章目录 Linux线程1. 线程的概念1.1 什么是线程 2. 线程的特点2.1 线程的优点2.2 线程的缺点2.4 线程和进程 3. 线程函数的使用pthread_create() 创建线程pthread_self() 获取线程IDpthread_exit() 线程终止pthread_cancel() 线程取消pthread_join() 线程等待pthread_detach()…

Android跨进程通信,binder传输数据过大导致客户端APP,Crash,异常捕获,监听异常的数值临界值,提前Hook拦截。

文章目录 Android跨进程通信&#xff0c;binder传输数据过大导致Crash&#xff0c;异常捕获&#xff0c;监听异常的数值临界值&#xff0c;提前Hook拦截。1.binder在做跨进程传输时&#xff0c;最大可以携带多少数据1.1有时候这个1m的崩溃系统捕获不到异常&#xff0c; 2.监测异…