C++STL---vector模拟实现

通过上篇文章,我们知道vector的接口实际上和string是差不多的,但是他俩的内部结构却大不一样,vector内有三个成员变量:_start、_finish、_endofstorage:

_start指向容器的头元素,_finish指向有效元素末尾的元素,_endofstorage指向整个容器的尾。

而vector的迭代器就是typedef T*,也就是元素的指针。

所以我们在模拟之前就可以先做出一个小框架,为了不与库中的vector冲突,我们把他放在我们自己的命名空间中:

namespace CYF
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;//指向容器的头iterator _finish;//指向有效数据的尾iterator _endofstorage;//指向容器的尾};
}

构造函数

构造函数1

这个是无参构造函数,我们将他所有的成员对象都制成空指针就行:

		//构造函数1vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}

构造函数2

vector还支持使用一段迭代区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说函数接收的迭代器的类型是不确定的,所以我们这里需要将构造函数设计为一个函数模板,在函数体内将该迭代区间的数据一个个尾插到容器当中即可:

		//构造函数2template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中{push_back(*first);first++;}}

构造函数3

vector还支持构造一种初始化有n个值为val的元素的对象。我们可以先使用reserve函数将容器的容量设为n,然后尾插n个值为val的数据到该对象即可:

		vector(size_t n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低for (size_t i = 0; i < n; i++)//尾插n个值为val的元素{push_back(val);}}

但是这个函数需要再实现两个重载函数:

vector(long n, const T& val):_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):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(n); for (int i = 0; i < n; i++) {push_back(val);}
}

这两个重载函数和他不同的就是第一个参数n的类型,如果没有这两个重载函数的话,我们调用下面的代码时,编译器就会优先和构造函数2匹配。

vector<int> v1(1, 3); //这里调用的是构造函数2 

并且因为构造函数2当中对两个参数进行了解引用(即将int类型解引用)报错:

补充:若我们知道需要多少空间,我们最好先使用reserve函数一次性将空间开辟好,避免调用push_back函数时再增容,导致效率降低。

拷贝构造函数

传统写法1

这里仍然需要深拷贝,先开一段与拷贝对象大小相等的空间,然后将该对象内的元素一个个拷贝过来即可,最后记得更新成员对象的值:

		拷贝构造(传统写法1)vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy{_start[i] = v[i];}_finish = _satrt + v.size();//有效数据的尾_endofstorage = _start + v.capacity();//整个容器的尾}

注意我们这里数据需要一个个拷贝过来,不能使用memcpy函数。若vector内部存储的是内置类型或者无需深拷贝的自定义类型时,可以使用memcpy函数,但是当vector内存储的数据是需要深拷贝的自定义类型的时候,使用memcpy就不行了。例如:我们拿存储的数据类型是string举例:

如图:我们vector对象内元素都是string类型,每个元素都指向自己的一个字符串。若我们使用memcpy函数的话,就会出现下面的情况:

memcpy会将每个string元素内成员变量的值原封不动地拷贝过去,也就导致了两个vector对象内的元素都指向同一组string元素。最后析构环节也就会导致内存错误。

而我们拷贝时采用string类的operator=的方式,采用的是深拷贝,最终的结果是这样:

就不会导致内存问题了。

传统写法2

这里我们直接调用reserve函数进行空间开辟,而后通过范围for将原有对象一个个尾插:

		//拷贝构造(传统写法2)vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (const auto& e : v){push_back(e);}}
现代写法

这里我们将v1作为临时对象,通过构造函数构造出来。而后将v1与*this互换即可,经典的现代写法,无需多言:

		//拷贝构造(现代写法)vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){vector<T>v1(v.begin(),v.end());swap(v1);}

赋值运算符重载

传统写法

先判断是否为自己给自己赋值,若给自己赋值则不用做任何操作。若不是,则:先开辟一块和要拷贝对象capacity大小一样的空间,然后将数据一个个拷过来,最后更新成员对象的值:

//赋值运算符重载函数(传统写法)vector<T>& operator=(const vector<T>& v){if (this != &v){delete[]_start;_start = new T[v.capacity()];_finish = _start + v.size();_endofstorage = _satrt + v.capacity();for (const auto& e : v){push_back(e);}}return *this;}

现代写法

operator=的现代写法很巧妙,右值传参的时候使用传值传参而不是引用传参,这样就直接调用了vector的拷贝构造函数,然后用这个拷贝出来的局部对象和左值进行交换,就相当于做了赋值操作,而v是局部变量,在函数结束时会自动调用析构函数销毁。

//现代写法
vector<T>& operator=(vector<T> v) 
{swap(v); return *this; //支持连续赋值
}

析构函数

先判断是否为空指针,若不是,将空间释放,再将成员变量全都置为nullptr:

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

迭代器相关的函数(begin&&end)

我们上面讲过,vector的迭代器实际上就相当于存储数据类型的指针,begin()就是返回第一个元素地址,end()就是返回最后一个元素的下一个数据的地址:

		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对象实际上也就是使用指针遍历。并且实现了迭代器后,也就可以使用范围for,因为范围for在编译的时候,会自动替换为迭代器的形式。

关于容量和大小的函数

size && capacity

这里的size和capacity的算法跟string不同,string内直接就有两个成员变量表示size和capacity,而vector这里需要用_finish - _start和_endofstorage - _start分别得到:

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

reserve

 1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
 2、当n小于对象当前的capacity时,不用做任何操作

我们先判断n是否大于容量,若大于,就开辟一块容量为n的空间,将原来容器中的有效数据拷贝进去,再将原来的空间释放,记得更新容器内的成员变量:

void reserve(size_t n)
{if (n > capacity()) {size_t sz = size(); //记录当前容器当中有效数据的个数T* tmp = new T[n]; //开辟一块可以容纳n个数据的空间if (_start) //判断是否为空容器{for (size_t i = 0; i < sz; i++) //拷贝数据{tmp[i] = _start[i];}delete[] _start; //释放原有空间}_start = tmp; //更新成员对象_finish = _start + sz; _endofstorage = _start + n; }
}

我们这里要注意要先记录元素的个数,否则我们最后更新_finish指针指向的时候,若调用size()函数,而size()函数是通过_finish-_start得到的,而此时_start已经指针指向已经改变,我们就会得到一个随机值,所以我们需要提前记录。

resize

 1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
 2、当n小于当前的size时,将size缩小到n。

我们需要先判断n是否小于size,若小于,将_finish改变即可,若不小于,还需要判断是否需要增容,再将扩大的数据赋值为val即可:

		void resize(size_t n, const T& val = T()){if (n < size()) //当n小于size时{_finish = _start + n; }else {if (n > capacity()) {reserve(n);}while (_finish < _start + n) {*_finish = val;_finish++;}}}

在C++中内置类型也可以看作是一个类,也有自己的默认构造函数,所以val的缺省参数可以设为T()临时对象。

empty

判断对象是否为空:

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

有关修改对象的函数

push_back

先判断是否需要增容,再尾插,最后更新_finish的指向:

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

pop_back

判断对象是否为空,不为空将_finish--即可:

		void pop_back(){assert(!empty());//容器不能为空_finish--;}

insert

我们需要先判断pos是否合法,再判断是否需要增容,然后将pos后的元素都向后移一位,将x插在pos的位置,最后将_finish++:

		void insert(iterator pos, const T& x){if (pos <= _finish)//判断pos是合法{size_t n = pos - _start;//记录pos和_start之间的间隔if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}pos = _start + n;//根据记录的间隔数算出positerator i = _finish;while (i != pos){*(i + 1) = *i;i--;}*pos = x;_finish++;}}

提示:这里我们需要提前记录pos和_start之间的间隔,通过间隔确定增容后的pos的指向,否则pos还是指向原来已经被释放的空间。(我自己在写的时候就出现了这样的错误)

erase

先判断对象不能为空,删除时将pos后面的数据统一前移一位,覆盖掉pos的数据,返回值还为pos,即删除元素的下一个元素的位置:

		iterator erase(iterator pos){assert(!empty());iterator it = pos;while (it != _finish){*it = *(it+1);it++;}_finish--;return pos;}

swap

我们调用库中的swap函数将两个对象中的成员变量交换即可(这样就相当于交换了两个对象内部指针的指向,不耗费太多资源,若我们直接调用swap交换两个对象,若遇见例如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[](size_t i){if(i<size()){return *(_start + i);}}const T& operator[](size_t i)const{if (i < size()){return *(_start + i);}}

最后贴上整体代码:

#pragma once
#include<iostream>
#include<cassert>namespace CYF
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//构造函数1vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}//构造函数2template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中{push_back(*first);first++;}}//构造函数3vector(size_t n, const T& val):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低for (size_t i = 0; i < n; i++)//尾插n个值为val的元素{push_back(val);}}//拷贝构造(传统写法1)//vector(const vector<T>& v)//	:_start(nullptr)//	,_finish(nullptr)//	,_endofstorage(nullptr)//{//	_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间//	for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy//	{//		_start[i] = v[i];//	}//	_finish = _satrt + v.size();//有效数据的尾//	_endofstorage = _start + v.capacity();//整个容器的尾//}//拷贝构造(传统写法2)//vector(const vector<T>& v)//	:_start(nullptr)//	, _finish(nullptr)//	, _endofstorage(nullptr)//{//	reserve(v.capacity());//	for (const auto& e : v)//	{//		push_back(e);//	}//}//拷贝构造(现代写法)vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){vector<T>v1(v.begin(),v.end());swap(v1);}赋值运算符重载函数(传统写法)//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		delete[]_start;//		_start = new T[v.capacity()];//		_finish = _start + v.size();//		_endofstorage = _satrt + v.capacity();//		for (const auto& e : v)//		{//			push_back(e);//		}//	}//	return *this;//}//赋值运算符重载函数(现代写法)vector<T>& operator=(const vector<T>& v){if (this != &v){vector<T> v1(v.begin(),v.end());swap(v1);}return *this;}//析构函数~vector(){delete[]_start;_start = nullptr;_finish = nullptr;_endofstorage = nullptr;}//迭代器相关函数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;}void reserve(size_t n){if (n >= capacity()){iterator _start_ = new T[n];_finish = _start_ + size();_endofstorage = _start_ + n;size_t i = 0;while (_start_ + i != _finish){_start_[i] = _start[i];i++;}delete[]_start;_start = _start_;_start_ = nullptr;}}//void resize(size_t n, const T& val = T())//{//	if (n <= size())//	{//		_finish = _start + n - 1;//这时候只会size,不会改变capacity//	}//	else if (n > size() && n <= capacity())//	{//		size_t more = n - size();//		while (more > 0)//		{//			*_finish = val;//			_finish++;//			more--;//		}//	}//	else//	{//		reserve(n);//		while (_finish != _start + n)//		{//			*_finish = val;//			_finish++;//		}//	}//}void resize(size_t n, const T& val = T()){if (n < size()) //当n小于当前的size时{_finish = _start + n; //将size缩小到n}else //当n大于当前的size时{if (n > capacity()) //判断是否需要增容{reserve(n);}while (_finish < _start + n) //将size扩大到n{*_finish = val;_finish++;}}}bool empty()const{return _start == _finish;}//修改容器内容相关函数void push_back(const T& x){if (_finish==_endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}*_finish = x;_finish++;}void pop_back(){assert(!empty());//容器不能为空_finish--;}void insert(iterator pos, const T& x){if (pos <= _finish)//判断pos是合法{size_t n = pos - _start;if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}pos = _start + n;iterator i = _finish;while (i != pos){*(i + 1) = *i;i--;}*pos = x;_finish++;}}iterator erase(iterator pos){assert(!empty());iterator it = pos;while (it != _finish){*it = *(it+1);it++;}_finish--;return pos;}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}//访问容器相关函数T& operator[](size_t i){if(i<size()){return *(_start + i);}}const T& operator[](size_t i)const{if (i < size()){return *(_start + i);}}private:iterator _start;//指向容器的头iterator _finish;//指向有效数据的尾iterator _endofstorage;//指向容器的尾};
}

以上就是这篇文章的全部内容,谢谢大家!!

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

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

相关文章

Vue2 + Element UI 封装 Table 递归多层级列表头动态

1、在 components 中创建 HeaderTable 文件夹&#xff0c;在创建 ColumnItem.vue 和 index.vue。 如下&#xff1a; 2、index.vue 代码内容&#xff0c;如下&#xff1a; <template><div><el-table:data"dataTableData"style"width: 100%"…

OSM历史10年(2014-2024)全国数据下载(路网、建筑物、POI、水系、地表覆盖利用······)

点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 零、前沿 这次向大家介绍一下OSM&#xff08;OpenStreetMap&#xff09;十年历史数据&#xff08;2014—2014&#xff09;的下载方法。当然我们也下载好分享给大家&#xff…

JAVA web期末复习总结

C/S结构与B/S结构区别&#xff1a; 在C/S结构中&#xff0c;客户端通常是一个独立的应用程序&#xff0c;需要在用户的计算机上安装和运行。而在BS结构中&#xff0c;客户端是一个Web浏览器&#xff0c;用户只需要通过浏览器打开网页&#xff0c;不需要安装额外的应用程序。 C…

程序员上岸指南

如果你还在996&#xff0c;大小周&#xff0c;感觉身体被掏空&#xff0c;那么你可以看看下面这篇文章&#xff0c;我特意搜集了一些苦逼程序员的上岸教程。 人生真的就是做几道选择题&#xff0c;选错了&#xff0c;忙也是瞎忙。选对了&#xff0c;躺着都能赢。总的来说&#…

良心推荐:什么软件能够监控公司电脑,就这5款软件能监控公司电脑

用软件监控员工电脑在大多数国家是非法的&#xff0c;不过在中国&#xff0c;企业在办公场所安装监控设备以监控员工工作&#xff0c;在遵循一定限制和条件的前提下&#xff0c;是合法的。以下是一些符合这些条件&#xff0c;并且广泛被企业采用的员工电脑监控软件的良心推荐&a…

C++:儿童节快乐呀!

六一快乐&#xff01;~ 今天我来宣布一个 ——————Cookie Maker工作室成立了&#xff01;—————— 目前参与人数&#xff1a;7 人名列单&#xff1a; 真实姓名 联系方式以及用户名 ZINCFFO CSDN&#xff08;ZINCFFO&#xff09; &…

使用 Scapy 库编写 IP 地址欺骗攻击脚本

一、介绍 1.1 概述 IP地址欺骗&#xff08;IP Spoofing&#xff09;是一种网络攻击技术&#xff0c;攻击者伪造其数据包的源IP地址&#xff0c;使其看起来像是从其他合法地址发送的。这种技术常用于各种攻击中&#xff0c;例如DDoS攻击、Man-in-the-Middle&#xff08;MITM&a…

buidldroot musl uclib库 编译

buildroot 修改 编译工具链 原本编译器相关信息&#xff1a; Incorrect selection of the C library buidroot编译 注意相关选项&#xff0c;后续使用CUSTOM TOOLCHAIN 时对应 UCLIB 能将生成IMAGE 从2.9K变为2.3K MUSL 能将生成IMAGE 从2.9K变为2.7K 变大了 arm-linux-…

c++的string一键介绍

前言&#xff1a; 这篇文章旨在帮助读者回忆如何使用string&#xff0c;并提醒注意事项。它不是一篇详细的功能介绍&#xff0c;而是一篇润色文章。 先展示重载函数&#xff0c;如果该函数一笔不可带过&#xff0c;就先展示英文原档&#xff08;附带翻译&#xff09;&#xf…

教你搞一个比较简单的计时和进度条装饰器

教你搞一个比较简单的计时和进度条装饰器 什么是装饰器为啥要用装饰器呢&#xff1f;上代码&#xff01;如何使用装饰器效果 什么是装饰器 装饰器的英文是&#xff1a;Decorator。装修的英文是&#xff1a;Decoration。顾名思义就是我们要用装饰器在函数func()上搞点儿事儿&am…

原生APP开发和Flutter开发的比较

原生APP开发和Flutter开发各有优缺点&#xff0c;适用于不同的场景和需求。下面是两者的详细比较&#xff0c;从开发语言、性能、开发效率、维护和更新、社区和支持等多个方面进行分析。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。…

htb_BoardLight

信息收集 nmap -sSVC 10.10.11.11开放80端口&#xff0c;将boardlight.htb写入/etc/hosts 同步进行子域名和目录扫描 子域名扫不到 这个目录扫描很奇怪哈&#xff0c;明明访问80端口有页面&#xff0c;就是扫不出来 直接浏览器访问80端口&#xff0c;四处看看&#xff0c;发…

7、架构-架构的安全性

即使只限定在“软件架构设计”这个语境下&#xff0c;系统安全仍然是一 个很大的话题。我们谈论的计算机系统安全&#xff0c;不仅仅是指“防御系统 被黑客攻击”这样狭隘的安全&#xff0c;还至少应包括&#xff08;不限于&#xff09;以下这些问 题的具体解决方案。 认证&am…

自定义数据集训练 Yolo V10

上次介绍了Yolo 推理&#xff0c;本文我们将使用自己的数据集训练 Yolo V10&#xff0c;训练过程简单&#xff1a; 首先准备数据集&#xff0c;包括图片、标注训练推理 数据集准备 本次采用的数据集为内部数据&#xff0c;标注方法为 VOC 格式&#xff0c;首先我们需要建 VO…

智能辅导班技术架构:运用人工智能提升教育领域的学习效果

在教育领域&#xff0c;随着人工智能的发展&#xff0c;智能辅导班逐渐成为一种趋势。它利用先进的技术和创新的教学方法&#xff0c;为学生提供个性化的学习体验和全方位的辅导服务。本文将介绍智能辅导班的技术架构&#xff0c;探讨如何运用人工智能来提升学习效果。 ### 1. …

力扣之链表专题

1. (LeetCode-21)合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 …

微服务架构-微服务实施

目录 一、概述 二、微服务拆分 2.1 概述 2.2 拆分原则 2.3 拆分方法 2.3.1 以数据为维度进行拆分 2.3.2 按照使用场景拆分 2.3.3 重要和非重要的拆分 2.3.4 变和不变的拆分 三、微服务通信 3.1 概述 3.2 微服务通信方式选择 3.3 微服务编排 3.4 API接口设计 3.5 …

「计网」网络初识

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;计网 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 网络初识 &#x1f349;IP 地址 & 端口号&#x1f349;网络协议&#x1f34c;TCP/IP 网络协议 &#x1f349;封装和分用&#x1f349…

乡村振兴与乡村旅游创新:创新乡村旅游产品,提升旅游服务水平,打造特色乡村旅游品牌,助力美丽乡村建设

目录 一、引言 二、乡村旅游产品的创新 &#xff08;一&#xff09;挖掘乡村特色资源 &#xff08;二&#xff09;注重产品体验性 &#xff08;三&#xff09;创新旅游产品形态 三、旅游服务水平的提升 &#xff08;一&#xff09;加强基础设施建设 &#xff08;二&…

如何上传模型素材创建3D漫游作品?

一、进入3D空间漫游互动工具编辑器 进入720云官网-点击“开始创作”-选择3D空间漫游-进入到作品创建页面。 二、上传模型及素材&#xff0c;创建生成3D空间漫游模型 1.创建3D空间作品&#xff1a;您可以选择新建空白作品或使用720云提供的预设空间模板&#xff0c;本篇主要介绍…