C++手撕简易vector

news2024/9/26 1:23:53

提前准备工作

由于vector跟string不同,vector是可以存储不同类型的变量的容器,因此实现类模板是肯定的

在原本的STL的vector容器中,主要成员变量有,start,finish,和 end_of_storage

所以

template<class T>
class vector{
public:

private:
	iterator _start;		//vector容器的起始位置
	iterator _finish;		//vector容器的有效结束位置
	iterator _endofstorage;	//vector容器的结束位置
};

构造函数 

这里的构造函数 不会那么多麻烦,可走缺省参数,也可以走参数列表

		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{ }

析构函数

		~vector() {
			if (_start) {	//当vector不为空时才析构
				delete[] _start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

要先判断_strat是否为空指针,以免对空指针进行delete[ ]

定义迭代器begin( ) 和 end( )

在我们手撕vector中,所使用的依旧用指针当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;
		}
		size_t size() const{
			return _finish - _start;
		}

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

size( ) 和 capacity( )

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

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

 测试:

	void test3() {
		vector<int> v;
		v.push_back(1);
		v.push_back(1);
		v.push_back(1);
		v.push_back(1);
		v.push_back(1);

		cout << "我现在的数量是:" << v.size() << endl;
		cout << "我现在的容量是:" << v.capacity() << endl;
	}

push_back尾插

		void push_back(const T& x) {
			//先判断空间够不够
			if (_finish == _endofstorage) {
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);	//不够扩容
			}
			*_finish = x;
			++_finish;	//最后一步要++
		}

reserve

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

此处看起来没有什么任何问题

我们开测试一下

测试

	void test1() {
		vector<int> v;
		v.push_back(1);
		v.push_back(1);
		v.push_back(1);
		v.push_back(1);
		for (auto e : v) {
			cout << e << " ";
		}
		cout << endl;
	}

 

分析

结果崩了,何以至此?

它说我们的finish是空指针

为何如此

_finish在扩容时是怎么更新的?

_start 没有问题,那么size( )?

我们可以发现,在经历了reserve了的时候,我们的 _start 的地址已经发生了变化 

问题就出于此,当我们返回 size( ) 的时候,我们的 _finish 还是空指针的,但是 _start 已经是有值了,相减为负,出来之后

便相当于变为 : _start + _finish + _start = _finish

所以此时 _finish 依旧是 空指针,这就是问题所在

解决办法

因此需要用一个值来记录原本 size( ) 返回的数值

		void reserve(size_t n) {
			if (n > capacity()) {
				size_t old = size();	//来记录_finish 距离 _start 的距离
				T* tmp = new T[n];      //,防止被更改
				if (_start) {
					memcpy(tmp, _start, sizeof(T) * n);
					delete[] _start;
				}

				_start = tmp;
				_finish = _start + old;
				_endofstorage = _start + n;
			}
		}

 此刻便能运行成功

自定义类型扩容:vector<string>

 我们可以看到此刻的代码是没有任何毛病的

再塞一个,代码就寄了,为什么呢?

这是原函数在扩容前

 这里扩容的原则是,从0开始,一开始开辟四个空间,四个空间后,每次以两倍方式扩容

扩容的基本方法是,开辟新空间,释放旧空间

但是问题就出现在memcpy,所创建的新空间所指向的 _str 是跟原来的就空间指向的内容是一样的,也就是说当原空间释放的 时候,新空间所指向的内容也是被释放了,因为这是浅拷贝,而不是深拷贝

解决办法

因此,我们需要为容器里的容器也写一个深拷贝,或者想办法保护好_str,因此memcpy不能够再次使用了

将memcpy 换成下列循环就ok了

 pop_back尾删

		void pop_back() {
			assert(size() >= 0 || (!_start));
			I--_finish;
		}

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

测试

打印自定义的vector类

		void print(const vector<T>& v) {
			for (auto e : v) {
				cout << e << " ";
			}
			cout << endl;
		}

其实没什么用,我觉得,但是写这个函数的前提是要有const_iterator

insert

在pos位置之前插入一个数值

		void insert(iterator pos, const T& x) {
			assert(pos <= _finish);
			assert(pos >= _start);
			if (_finish == _endofstorage) {
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			memmove(pos + 1, pos, sizeof(_finish - pos) * (T));
			*pos = x;
			++finish;
		}

 测试

		cout << "-----------------------" << endl;
		cout << "现在进行插入测试" << endl;

		v.insert(v.begin(), 100);
		v.print(v);

程序崩溃

 

可以看出,pos指针出现问题了

分析 

根据代码回溯,我们可以发现,就我们传指针的时候设计到了pos迭代器,那我们从这开始

先判断是不是需要扩容 可以看到,是需要扩容的,我们接着往下看 

问题就出现在这里

扩容函数从来都不会说考虑pos的位置,也就是说当我扩容是,原来的pos并没有过来,反而是留在了原来的空间里被释放变成野指针,所以才出现了报错 

此种类型就是迭代器失效,本质就是pos扩容后失效了 

解决办法

同样要记录下原本pos的位置

		void insert(iterator pos, const T& x) {
			assert(pos <= _finish);
			assert(pos >= _start);
			cout << "我现在的数量是:" << size() << endl;
			cout << "我现在的容量是:" << capacity() << endl;
			if (_finish == _endofstorage) {
				size_t len = pos - _start;	//记录原本要的长度,用于恢复pos
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len;			//恢复pos
			}
			memmove(pos + 1, pos, sizeof(T)* (_finish - pos));
			*pos = x;
			++_finish;
		}

 自定义类型插入

此处跟reserve所用一样,采用vector<string>

结果处一样,同样无法达到string类深拷贝的问题

 swap

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

 operator=

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

注意

参数那里不能带引用,不带引用的话,只是一个拷贝,v只是一个拷贝对象,跟this内的内容交换就交换了,出了作用域也是要被销毁的,但是用来引用的话,就是原对象本身,这样原对象的值会被干掉的,变成this里的值

测试

	void test2() {
		vector<int> v1,v2;
		v1.push_back(1);
		v1.push_back(2);
		v2.push_back(3);
		v2.push_back(4);
		cout << "我原本的v1是:" << " ";
		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;
		v1 = v2;
		cout << "我现在的v1是:" << " ";
		for (auto e : v1) {
			cout << e << " ";
		}
		cout << endl;
	}

报错了,那是因为我还没有构造函数呢

构造函数

		vector(const vector<T>& v) {
			_start = new T[v.capacity()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
		}

此刻就很完美了 

resize

resize有三种情况

  • 一种是比原size大,但是未能超过capacity
  • 一种是超过capacity
  • 一种是比原size小

内置类型的默认构造

原本我们会认为 只有自定义类型才有默认构造,但是自C++不断发展以来,为了更好的与之兼容,比如,类模板的出现,让这个类显示的去推类型,从而知道它的初始值是什么。即当类模板一旦推出这个类型,就给予赋相应的值。

 

测试 

但是我们可以通过用reserve将第一种跟第二种直接连续起来 

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

earse

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

测试

测试1

		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);
		for (auto e : v) {
			cout << e << " ";
		}
		cout << endl;

		v.earse(v.begin() + 3);
		for (auto e : v) {
			cout << e << " ";
		}
		cout << endl;

 

测试2

删除所有的偶数

		vector<int>::iterator it = v.begin();
		while (it != v.end()) {
			if (*it % 2 == 0) {
				v.erase(it);
			}
			else {
				it++;
			}
		}
		for (auto e : v) {
			cout << e << " ";
		}
		cout << endl;

我们看着好像一点问题都没有,其实不是,这种写法只是带有平常的偶然性罢了

我们换成STL里面的库来试试

测试3

	std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);
		for (auto e : v) {
			cout << e << " ";
		}
		cout << endl;

		std::vector<int>::iterator it = v.begin();
		while (it != v.end()) {
			if (*it % 2 == 0) 
				v.erase(it);
			else
				it++;
		}
		for (auto e : v) {
			cout << e << " ";
		}
		cout << endl;
	}

这就报错了,也就是说换成stl的库里面的vector就顶不住 

分析

在VS下,它会进行强制的检查验证,它会认为erase了以后,这个it就会失效,不让我们再次使用了,我们回到STL文档里,看看设计者们是怎么解决的

 

解决方案

 STL中的vector容器对于erase,它会采取使用返回值的做法,就它会返回调用函数之后被删除的元素后面的那个元素,就直接指向新元素

 此刻就是没事的

构造函数再续

vector还有两种构造

 类模板里面的模板

类模板里是支持可以再次创建自己所需要的模板的

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

这里用 first != last 而不用 first< last的原因是

有些容器的迭代器底层并不是物理连续的!

另一个构造函数就跟resize的实现原理一模一样,这里就不再过多阐述,但是实现后的类模板会与上面的类模板构造函数,发生冲突。因为编译器会觉得这个构造要隐式转换,另外一个不会隐式转换,所以选类模板构造函数

以上就是本次博文的学习内容,如有错误,还望大佬指正,谢谢阅读

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

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

相关文章

leetcode 2415.反转二叉树的奇数层

1.题目要求: 给你一棵 完美 二叉树的根节点 root &#xff0c;请你反转这棵树中每个 奇数 层的节点值。例如&#xff0c;假设第 3 层的节点值是 [2,1,3,4,7,11,29,18] &#xff0c;那么反转后它应该变成 [18,29,11,7,4,3,1,2] 。 反转后&#xff0c;返回树的根节点。完美 二叉…

SolverLearner:提升大模型在高度归纳推理的复杂任务性能,使其能够在较少的人为干预下自主学习和适应

SolverLearner&#xff1a;提升大模型在高度归纳推理的复杂任务性能&#xff0c;使其能够在较少的人为干预下自主学习和适应 提出背景归纳推理&#xff08;Inductive Reasoning&#xff09;演绎推理&#xff08;Deductive Reasoning&#xff09;反事实推理&#xff08;Counterf…

npm ERR! missing script: serve

报错原因&#xff1a;我这里是因为跑错命令了&#xff0c;我用的npm run serve 解决办法&#xff1a;去package.json文件里面找到对应的serve命令运行即可&#xff0c;每个系统都不太一样&#xff0c;如果还不行可以看看是不是项目终端搞错了&#xff0c;比如我这个项目有两个前…

行为型设计模式3:模板方法/备忘录/解释器/迭代器

设计模式&#xff1a;模板方法/备忘录/解释器/迭代器 (qq.com)

无需提示的思考链推理:深度探索大型语言模型的内在能力

人工智能咨询培训老师叶梓 转载标明出处 在人工智能领域&#xff0c;提升大模型&#xff08;LLMs&#xff09;的推理能力一直是研究的重点。传统的方法主要依赖于特定的提示技术&#xff0c;例如少量样本或零样本的思考链&#xff08;CoT&#xff09;提示。这些方法虽然有效&am…

力扣爆刷第169天之TOP200五连刷111-115(课程表、单词搜索、归并)

力扣爆刷第169天之TOP200五连刷111-115&#xff08;课程表、单词搜索、归并&#xff09; 文章目录 力扣爆刷第169天之TOP200五连刷111-115&#xff08;课程表、单词搜索、归并&#xff09;一、207. 课程表二、LCR 125. 图书整理 II三、402. 移掉 K 位数字四、79. 单词搜索五、9…

Python自动化办公2.0:重塑工作效率的未来

在现代办公环境中&#xff0c;自动化技术和数据分析已经成为提升工作效率和决策质量的关键。随着Python编程语言的发展&#xff0c;我们迎来了“Python自动化办公2.0”时代&#xff0c;这一时代不仅包括强大的数据分析工具&#xff0c;还涵盖了酷炫的可视化技术和前沿的机器学习…

【卷积神经网络】卷积层详解【数学+python代码】

1、简介 学习目标&#xff1a; 掌握卷积计算过程掌握特征图大小计算方法掌握PyTorch卷积层API 基本概念&#xff1a; ①在计算机视觉领域&#xff0c;往往我们输入的图像都很大&#xff0c;使用全连接网络的话&#xff0c;计算的代价较高。 另外图像也很 难保留原有的特征 &am…

科技赋能生活——便携气象站

传统气象站往往庞大而复杂&#xff0c;需要专业人员维护&#xff0c;它小巧玲珑&#xff0c;设计精致&#xff0c;可以轻松放入背包或口袋&#xff0c;随身携带&#xff0c;不占空间。无论是城市白领穿梭于高楼大厦间&#xff0c;还是户外爱好者深入山林湖海&#xff0c;都能随…

numpy如何按等长分割数组

numpy如何按等长分割数组 1、效果 2、流程 1、分割数组 2、转列表3、代码 # -*- coding: utf-8 -*-""" @contact: 微---信 1257309054 @file: test.py @time: 2024/8/03 19:46 @author: LDC """ import numpy as np# 假设arr是需要分割的nump…

virtualbox7安装centos7.9配置静态ip

1.背景 我大概在一年之前安装virtualbox7centos7.9的环境&#xff0c;但看视频说用vagrant启动的窗口可以不用第三方工具(比如xshell、secure等)连接centos7.9&#xff0c;于是尝鲜试了下还可以&#xff0c;导致系统文件格式是vmdk了&#xff08;网上有vmdk转vdi的方法&#xf…

ChatGLM3-6B模型部署微调实战

准备 教程 视频教程 https://www.bilibili.com/video/BV1ce411J7nZ?p14&vd_source165c419c549bc8d0c2d71be2d7b93ccc 视频对应的资料 https://pan.baidu.com/wap/init?surlAjPi7naUMcI3OGG9lDpnpQ&pwdvai2#/home/%2FB%E7%AB%99%E5%85%AC%E5%BC%80%E8%AF%BE%E3%8…

Keil5.40因为Jlink驱动闪退问题

现象 Cannot load driver ‘C:\Keil_v5\ARM\Segger\JL2CM3.dll 原因 由于Jlink为盗版&#xff0c;导致闪退。 具体为JLinkARM.dll这个插件搞鬼。 这个插件的来源为 也就是我们装Jlink驱动的时候&#xff0c;勾选了这个选项&#xff0c;而导致这个毒瘤插件进入Keil&#xff…

代码题-01_顺序表_基础知识

线性表 线性表&#xff08;list&#xff09;:零个或多个相同数据元素的有限序列 线性表是逻辑结构&#xff08;元素之间一对一相邻关系&#xff09;按存储方式分为 顺序表链表 顺序表 顺序表的定义 静态分配&#xff08;使用数组存数据&#xff09; 有溢出风险 typedef…

特定领域软件架构-系统架构师(三十七)

软件架构复用 有三个阶段&#xff1a; 首先构造/获取可复用的软件资产其次管理这些资产&#xff08;构件库&#xff09;最后针对这些需求&#xff0c;从这些资产中选择可复用的部分&#xff0c;满足需求应用系统。 特定领域软件架构 DSSA&#xff08;Domain Specific softwa…

(四)activit5.23.0修复跟踪高亮显示BUG

一、先看bug 在 &#xff08;三&#xff09;springboot2.7.6集成activit5.23.0之流程跟踪高亮显示 末尾就发现高亮显示与预期不一样&#xff0c;比如上面的任务2前面的箭头没有高亮显示。 二、分析原因 具体分析步骤省略了&#xff0c;主要是ProcessInstanceHighlightsResour…

饿了么冰杯外卖爆涨350%,“冰+X”激发酒饮即时零售夏季增长加速

近日&#xff0c;饿了么联合尼尔森IQ共同发布的《2024夏季即时零售冰品酒饮消费洞察报告》显示&#xff0c;大暑前后冰杯外卖量同比去年增长350%&#xff0c;冰杯搭配啤酒等酒水饮料的外卖量也同比增长约300%。 报告综合多渠道零售数据和案例分析&#xff0c;剖析了冰品酒饮在…

基于51单片机的车窗控制系统proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1w5qrAvn1cUK7ZX2GJvWBQw 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

DedeCMS-V5.7.82-UTF8织梦

先进行安装 然后进入首页和管理员后台 内容要在后台进行操作 首页 后台 1.通过文件管理器上传WebShell 访问目标靶场其思路为 dedecms 后台可以直接上传任意文件&#xff0c;可以通过文件管理器上传php文件获取webshel 登陆到后台点击【核心】【文件式管理器】【文件上传】将…

基于深度学习的面部表情分类识别系统

&#xff1a;温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 面部表情识别是计算机视觉领域的一个重要研究方向&#xff0c; 它在人机交互、心理健康评估、安全监控等领域具有广泛的应用。近年来&#xff0c;随着深度学习技术的快速发展&#xf…