c++还原简单的vector

news2024/12/24 0:30:37

image-20221206160819924

文章目录

  • vector
      • vecotor的介绍
    • vector的模拟实现
      • 类的框架
      • 成员变量
      • 迭代器
      • 构造函数
      • 析构函数
      • size()
      • capacity()
      • operator[]重载
      • 扩容
      • resize()
      • 尾插
      • 验证是否为空
      • 尾删
      • clear 清除
      • swap交换
      • insert插入
      • erase删除
      • 迭代器区间初始化构造函数
      • 拷贝构造
      • 赋值运算符重载
      • n个val构造函数
      • 再谈构造函数

vector

vecotor的介绍

1.vector是表示可变大小数组的序列容器

2.就像数组一样,vector也采用连续存储空间来存储元素,意味着可以采用下标对vector的元素进行访问。(和数组一样高效)

3.vector使用动态分配数组来存储元素,当新元素插入时,而旧的空间不够时,一般分配一个新的数组,然后把全部元素移到新的数组,再进行新元素的插入。

4.vector会分配一些额外的空间来适应可能的增长,因此存储空间比实际需要的存储空间更大。

库里vector

vector的模拟实现

类的框架

namespace Vect
{
    template<typename T>//模板参数
    class vector
    {
public:
        
    成员函数
    ......
private:
        
    成员变量
    ......
    };
}

成员变量

	private:

		iterator _start;//从0开始
		iterator _finish;//最后一个成员变量的下一个位置
		iterator _endofstorage;//容量

迭代器

这里的迭代器iterator可看作为指针

typedef T*iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start; 
}

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

const_iterator end() const
{
return _finish;
}

构造函数

vector()//构造函数
    //这里用初始化列表比较方便
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}

析构函数

~vector()//析构函数
		{
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}

size()

指针相减 得指针之间成员个数

size_t size() const//size---有效成员个数
		{
		return 	_finish - _start;
		}

capacity()

	size_t capacity()const//capacity---容量
		{
			return _endofstorage - _start;
		}

operator[]重载

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

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

扩容

void reserve(size_t n)
{
  if (n > capacity())
  {
  size_t oldsize = size();//记录成员个数
  T* tmp = new T[n];//开辟新空间
  if (_start)//如果_start指向的空间不为空就要拷贝数据
  {
         //把_start的数据拷贝到tmp上面
		//memcpy(tmp, _start, sizeof(T) * oldsize);//这里实现了浅拷贝
                    //后面会谈到memcpy的弊端
      for(size_t i=0;i<oldsize;i++)
           {
                   tmp[i]=_start[i];//这里实现深拷贝
           }
		//删除旧空间-空间不为空才需要释放
          delete[]_start;
	}
		_start = tmp;//指向新空间
//原来的size=_finish-_start,而此时的_start是新空间的_start,   //_finish是旧空间的_finish,相减得出不是之间成员个数了;所以我们要用oldsize()来记录先前的成员个数,用新的_start+oldsize()得出新的_finish
				_finish = _start + oldsize;
				_endofstorage = _start + n;
 }
}

resize()

image-20221206160315297

void resize(size_t n, T val = T())//value_type val 给匿名对象
//n<size():缩容;
//size()<n<capacity():用val初始化有效成员以外的成员变量;
//n>capacity():扩容且用val初始化有效成员以外的成员变量;		
{
    
			if (n > capacity())//n大于容量
			{
				reserve(n);//扩容
			}
			if (n > size())
			{
				while (_finish < _start + n)
				{//用val初始化有效成员以外的成员变量;
					*_finish = val;
					++_finish;
				}
			
			}
			else
			{
				_finish = _start + n;//缩size()
			}
		}

尾插

image-20221206160248020

void push_back(const T& x)//尾插
		{
			if (_finish == _endofstorage)//扩容
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			++_finish;
		}

验证是否为空

image-20221206160336743

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

尾删

image-20221206160354972

void pop_back()
		{
			assert(!empty());
			--_finish;
		}

clear 清除

image-20221206160408484

void clear()//清理---不影响容量
		{
			_finish = _start;
		}

swap交换

image-20221206160423909

void swap(vector<T>& v)//交换
{//这里要指明是std库里的swap,因为头文件展开后从上往下找swap函数不一定先找到的是std库里的swap,还可能是vector的swap
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage,v._endofstorage);
		}

insert插入

这里我们实现第一个接口

image-20221206160542301

//迭代器失效:当插入时要扩容,pos指针指向原来的空间,而_start指向新空间,在挪动数据时会出问题
		iterator insert(iterator pos, const T& val)
		{//pos要在_start和_finish之间
			assert(pos >= _start);
			assert(pos < _finish);

			//_start为空时插入要扩容或者容量满了都要扩容
			if (  _finish==_endofstorage)
			{
				size_t len = pos - _start;//记录pos的相对位置
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len;//针对迭代器失效要对pos更新
			}
			iterator end = _finish - 1;
			while (end >= pos)//挪动数据
			{
				*(end + 1) = *(end);
				--end;
			}
			pos = val;
			++_finish;	
            return pos;
		}

erase删除

这里我们实现第一个接口

image-20221206160618001

	iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			iterator begin=pos+1;
			while (begin<_finish)
			{
				*(begin-1) = *(begin );
				++begin;
			}
			--_finish;
			return pos;//返回迭代器
		}

在这里我们创建一个vector往里面尾插1234;之后我们删除4;并且打印删除后的迭代器位置

image-20221205160153100

这里我们运行了看起来没有问题,实际上问题很大!删除4后容器的数据只有123,而我们访问到了4—野指针越界访问! 这里我们实现的模拟跟Linux系统下相似,所以在Linux系统下也不会报错;但我们如果删除的是2或者是3呢?会越界吗?换句话说:迭代器会失效吗?答:在Linux系统下迭代器可能会失效也可能不会!但在vs下必然失效!

这里我们换库里面的vector的erase试一下

image-20221205161257821

果然在vs下对迭代器做了更严格的检查,读都不给读,更何况是写;—迭代器失效了吗???好像失效了,这里有人会说你这个删除4肯定是越界了,那我删除别的对象不就不越界了嘛?

好现在我们删除2

image-20221205161333702

照样报错!所以我们使用完迭代器之后最好就不要再用迭代器了

如果硬要用就更新迭代器!

删除4—会被断言出越界

image-20221205162933187

删除2—更新了迭代器不会报错

image-20221205163424455

现在在vs还是Linux下都不会发生迭代器失效了,若越界直接断言!

迭代器区间初始化构造函数

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
            //构造函数:用迭代器区间去初始化
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

拷贝构造

	vector(const vector<T>& v)//拷贝构造
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)//因为要扩容,所以要提前初始化三个迭代器
{
vector<T> tmp(v.begin(),v.end());//用迭代器构造tmp;因为是用的const+引用所以要有中间者tmp
			
			swap(tmp);//this和临时对象tmp交换
			
}

赋值运算符重载

vector<T>& operator=( vector<T> v)//传值
		{
//赋值的时候如果容量满了会扩容,就算是自己赋值給自己也会扩容;先后经过后者拷贝构造临时对象,拷贝构造时用到迭代器区间构造临时对象,//然后构造时就用到尾插push_back,尾插时就验证要不要扩容!
			swap(v);//this和v交换
			return *this;
		}

n个val构造函数

		vector(size_t n, const T& val = T())//库里面給的是size_t n
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);//扩容
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
  //int模式构造函数的n重载size_t模式构造函数的n
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);//扩容
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

上面出现了size_t n的构造函数和int n的构造函数

我们做个实验,把int n的构造函数注释掉;然后用10个5初始化构造v

image-20221205211232270

运行后发现报错了,原因是非法的间接寻址。这是为啥呢?

我们看到上面10个5初始化,类型为**(int ,int)** ;而n个val的参数类型为**(size_t ,int)**

另一个迭代器区间初始化的参数类型为**(InputIterator first, InputIterator last)**

分别为两个参数模板类型,而int参数也可以作为参数模板

(int,int)参数类型进到**(size_t,int)参数类型前者int还需要整形提升**;而对于(InputIterator first, InputIterator last)类型(int,int)可以直接进入。但到后面的地址解引用就是非法寻址了!

image-20221205220032278

所以我们还需要在写一个(int,int)参数类型的构造函数重载(size_t ,int)构造函数

再谈构造函数

这里我们创建一个4个vector<vector<int>>,給vector<int>序列初始化为11

然后我们打印出来

image-20221205224024586

我们打印四个vector<int>,没毛病。我们打印五个:报错了!

image-20221205224221463

这是为啥呢?我们前面写到构造函数扩容,size达到4时要扩容,前者四个没报错而后者5个报错了!这说明扩容有问题!

image-20221205225959910

构造函数用到push_back,数量达到4个要扩容,而这里的扩容是先new一块8个对象(类型为vector<int>)大的新空间tmp(类型为vector<vector<int>>),把旧空间的vector<int>的_start一个个拷贝(这里是memcpy-浅拷贝)到新空间;再delete旧空间vv(先依次析构v再析构空间vv);再把 vv的 _start指向新空间tmp;这时我们发现vv扩容为tmp是完成了深拷贝(把v的 _start一个个拷贝到tmp上),而v却还是浅拷贝( v的 _start 还是指向原来的空间,而原来的空间已经被析构了—属于野指针越界访问!)这里配合着下图理解

image-20221205231309672

所以我们要换种方式扩容拷贝

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();
				T* tmp = new T[n];//开辟新空间
				if (_start)//如果_start指向的空间不为空就要拷贝数据
				{
					for (size_t i = 0; i < oldsize; ++i)
					{
						tmp[i] = _start[i];//复用运算符重载=完成深拷贝
				delete[]_start;//删除旧空间-空间不为空才需要释放
                    }
				}
				_start = tmp;//指向新空间
				_finish = _start + oldsize;
				_endofstorage = _start + n;
			}
		}

image-20221205232650522

好啦,vector的模拟实现就到这了。小伙伴们要多多实现加深印象噢!

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

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

相关文章

数仓日记 - 数仓理论

寒刃尽断处&#xff0c;吾心作剑霜作锋&#x1f3c2; 目录 一、数仓简介 二、关系建模与维度建模 1. 关系建模   2. 维度建模    • 三种模型    • 事实表    • 维度表   3. 事实表的分类    • 事务型事实表    • 周期型快照事实表    • 累积型快照事实表…

Python操作Excel表格

本文介绍如何通过轻量级、零依赖&#xff08;仅使用标准库&#xff09;的 pylightxl 库操作Excel表格。 官网&#xff1a;Welcome to pylightxl documentation — pylightxl 2019 documentation 目录 一、入门 1. 读写CSV文件 2. 读Excel文件 3. 获取工作表和单元格数据 3…

前端css实现特殊日期网页变灰功能

前端变灰效果在网页实际使用过程中使用的比较少&#xff0c;但有时候又缺一不可&#xff0c;一般在大型哀悼日或纪念日的时候使用&#xff0c;使用后的网站页面会变成灰色(黑白色)。 我们先看下各大网站是怎么实现的&#xff1a; 1.csdn实现方式 2.淘宝 3.人民网 4.京东 5.掘…

Unity【Multiplayer 多人在线】服务端、客户端通用架构的使用指南

文章目录&#x1f6a9; Import&#x1f680; protogen使用方法&#x1fa90; 客户端接口&#x1f308; 服务端接口&#x1f9ed; 数据处理&#x1f3a8; Example&#x1f6a9; Import 下载SKFramework框架&#xff0c;导入到Unity中&#xff1b; 在框架Package Manager中搜索并…

osgEarth示例分析——osgearth_colorfilter

前言 osgearth_colorfilter颜色过滤器示例。本示例中&#xff0c;主要展示了6种颜色过滤器的使用&#xff0c;分别是:HSLColorFilter、RGBColorFilter、CMYKColorFilter、BrightnessContrastColorFilter、GammaColorFilter、ChromaKeyColorFilter。 执行命令 // 一条命令是一…

Docker日常运维小技巧

一、故障定位 1、查看容器内部 https 请求响应时间 docker exec -t $(docker ps -f nameblog_web -q) curl -H X-Forwarded-Proto:https \-w %{time_total} -o /dev/null -s localhost 2、查看容器日志 docker logs --tail 50 --follow --timestamps mediawiki_web_1 3、删…

深圳SMT贴片行业MES系统解决方案~MES系统服务商~先达智控

随着我国工业的迅速发展&#xff0c;所有电子行业都离不开SMT贴片生产&#xff0c;SMT贴片生产是电子行业的至关重要的一道工业环节&#xff0c;我国作为一个工业制造大国&#xff0c;有着完备的SMT现代产业体系。SMT贴片领域是我国支柱性产业其一&#xff0c;SMT贴片产品涵盖工…

【JavaWeb开发-Servlet】day01-使用TomCat实现本地web部署

目录 1、准备java web开发环境 &#xff08;1&#xff09;下载javaJDK&#xff08;推荐使用JDK1.8&#xff0c;企业常用且稳定&#xff09; &#xff08;2&#xff09;下载TomCat服务器 2、创建web服务器TomCat (1)创建一个项目文件夹 (2)在文件夹中新建一个记事本并编以下…

算法大神左程云耗尽5年心血分享程序员代码面试指南第2版文档

前言 学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生…

移动WEB开发之rem布局--苏宁首页案例制作(技术方案1)

案例&#xff1a;苏宁网移动端首页 访问地址&#xff1a;苏宁易购(Suning.com)-家电家装成套购&#xff0c;专注服务省心购&#xff01; 1. 技术选型 方案&#xff1a;我们采取单独制作移动页面方案 技术&#xff1a;布局采取rem适配布局&#xff08;less rem 媒体查询&am…

用 TensorFlow.js 在浏览器中训练一个计算机视觉模型(手写数字分类器)

文章目录Building a CNN in JavaScriptUsing Callbacks for VisualizationTraining with the MNIST DatasetRunning Inference on Images in TensorFlow.jsReferences我们在《在浏览器中运行 TensorFlow.js 来训练模型并给出预测结果&#xff08;Iris 数据集&#xff09;》中已…

数字源表如何测试MOS管?

MOSFET(金属—氧化物半导体场效应晶体管)是 一种利用电场效应来控制其电流大小的常见半导体器件&#xff0c;可 以 广 泛 应 用 在 模 拟 电 路 和 数 字 电 路 当 中 。 MOSFET可以由硅制作&#xff0c;也可以由石墨烯&#xff0c;碳纳米管 等材料制作&#xff0c;是材料及器件…

集成电路模拟版图入门-转行模拟版图基础学习笔记(二)

在众多IC岗位中&#xff0c;模拟版图确实属于容易入门&#xff0c;吸引来很多想要转行IC行业的朋友&#xff0c;但需要掌握的知识点和技巧并不比设计少&#xff0c;属于门槛简单&#xff0c;上手不易&#xff0c;想要自学模拟版图似乎比较困难。 之前为大家分享过移知学员的模…

(十四)笔记.net学习之RabbitMQ工作模式

RabbitMQ在.net中简单使用一、简单模式1.生产者2.消费者二、工作队列模式1.工作队列模式介绍2.生产者发送消息3.消费能力三、发布/订阅模式1.介绍2.生产者3.消费者四、Routing路由模式1.介绍2.生产着3.消费者五、topic 主题模式1.介绍2. 生产者3.消费者一、简单模式 1.生产者 …

MyBatis系列第1篇:MyBatis未出世之前我们那些痛苦的经历

这么多技术&#xff0c;为什么我们选择的是mybatis 不知道大家是否还记得使用jdbc如何操作数据库&#xff1f; 加载驱动、获取连接、拼接sql、执行sql、获取结果、解析结果、关闭数据库&#xff0c;这些操作是纯jdbc的方式必经的一些过程&#xff0c;每次操作数据库都需要写这…

三面:请设计一个虚拟DOM算法吧

一、问题剖析 这不是前几天面试官开局面试官就让我设计一个路由&#xff0c;二面过了&#xff0c;结果今天来个三面。 问你道简单的送分题&#xff1a;设计一个虚拟DOM算法&#xff1f; 好家伙&#xff0c;来吧&#xff0c;先进行问题剖析&#xff0c;谁让我们是卑微的打工人…

学习python基础知识

1、Python 基础语法 计算机组成&#xff1a;硬件、软件、计算机运行程序方式、Python 语言的特点、应用领域、Python IDE、程序注释&#xff1a;单行注释、多行注释&#xff1b;变量的作用、定义、 命名规则、变量的数据类型、查看变量类型、输入和输入函数、算术运算符、赋值…

gazebo中添加动态障碍物

文章目录gazebo 教程gazebo 添加动态障碍物gazebo添加动态障碍物插件gazebo中动态障碍物实时posegazebo 教程 gazebo github https://github.com/gazebosim/gazebo-classic/tree/gazebo9gazebo tutorials https://classic.gazebosim.org/tutorials运行一个空白环境 <sdf v…

深入了解Java中的SQL注入

深入了解Java中的SQL注入 本文以代码实例复现了Java中JDBC及Mybatis框架采用预编译和非预编译时可能存在SQL注入的几种情况&#xff0c;并给予修复建议。 JDBC 首先看第一段代码&#xff0c;使用了远古时期的JDBC并且并没有使用预编译。这种简单的字符串拼接就存在SQL注入 …

一云七芯!ZStack 祝贺上海市金融信创联合攻关基地荣获一等奖

2022年11月初&#xff0c;由上海市总工会、中共上海市经济和信息化工作委员会、上海市经济信息化委员会主办的2022上海城市数字化转型 “智慧工匠”选树、“领军先锋”评选活动信创应用竞赛决赛暨颁奖典礼中&#xff0c;“一云七芯适配验证云平台及服务解决方案”获得信创应用案…