【C++】--模拟实现vector

news2025/8/12 9:52:22

文章目录

  • Constructors(构造函数)
    • myvector()
    • myvector(int n, const T& val = T())
    • myvector(InputIterator first, InputIterator last)
    • 拷贝构造
    • 交换函数
    • myvector< T >& operator=(myvector< T > v)
  • 迭代器
  • 扩容
    • reserve
  • resize
  • 插入和删除
    • push_back
    • pop_back
    • insert
    • erase
  • 全代码
  • 总结

vector和我们学数据结构时候的顺序表差不多,不过STL库里面的vector给我们提供了很多的接口函数,十分方便。对于各接口的调用这里就不详细讲解了,用到的时候不熟悉可以去官网查一下。这篇文章主要讲讲 个人对vector模拟实现的方法

image-20221124160305301

Constructors(构造函数)

vector是一个模板类,可以存储不同类型的数据。先来看看官方的几种构造法方法

image-20221124160734864

因为vector是一个模板类,所以在定义类时要用模板的格式。在vector中可以简单的定义三个类变量,全部用模板类型的指针类型

iterator _start;//首地址
iterator _finish;//最后一位有效数据的后一位地址
iterator _endofstorage;//容量的最后一位地址

template<class T>
	class myvector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
        
	private:
		iterator _start;//首地址
		iterator _finish;//最后一位有效数据地址
		iterator _endofstorage;//容量的最后一位地址
	};

myvector()

定义好变量后,先实现一个无参的构造函数。因为变量都是指针类型,所以一开始我们可以将它们初始化为nullptr

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

myvector(int n, const T& val = T())

再来实现一个跟库里面一样的,可以指定对象开辟的空间大小,并把指定值填入每一个空间中

myvector(int n, const T& val = T()) 
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    reserve(n);
    for (int i = 0; i < n; i++)
    push_back(val);
}

push_back的实现下面再将。

注意:这个的缺省值不能用0或者" ",因为类型T没有确定,有可能会是我们自定义的类型,所以缺省值要使用T类型的默认初始值。参数中的n为什么不用size_t类型呢,下面会谈到

myvector(InputIterator first, InputIterator last)

接下来库里面的第三个构造函数,指定一段区间,并将这段区间的所有数值赋值到对象中。因为是一段区间,所以参数传的是指针,但是由于类型没有确定,所以要使用模板函数

template <class InputIterator>
myvector(InputIterator first, InputIterator last)
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    while (first != last) {
        push_back(*first);
        first++;
    }
}

为什么上面讲的第二个构造不使用size_t类型呢,就是因为第三个区间的构造传的参数是指针类型,如果第二个参数使用的是size_t类型,那么当我们构造传参时,编译器就不知道去匹配哪一个构造函数了。所以为了避免冲突,第二个构造我们使用int类型参数。

拷贝构造

接下来实现拷贝构造函数,因为有了上面的区间构造函数后,我们只需要创建一个临时的对象,然后和原对象交换一下后就可以了。需要注意的是,交换的时候要将地址交换才不会出现野指针的情况

myvector(const myvector<T>& v)
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    myvector<int> tmp(v.begin(), v.end());
    swap(tmp);
}

交换函数

交换地址就可以直接实现了

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

myvector< T >& operator=(myvector< T > v)

按照我们的使用习惯,一般都会有对象给对象赋值,所以肯定少不了实现 =运算符重载了。因为形参的改变不会影响实参,所以我们只需要形参对象和原对象交换一下即可

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

迭代器

在模拟实现时只需要把迭代器看成是指针即可,所以迭代器的实现就非常的简单了。因为库里面也会有const类型的,所以我们也加上const类型的即可

iterator begin() {
	return _start;
}

iterator end() {
	return _finish;
}

const_iterator begin() const {
	return _start;
}

const_iterator end() const {
	return _finish;
}

既然vector的功能和顺序表差不多,那么肯定少不了随机读写的功能了,这就得重载[]运算符了

T& operator[](size_t pos) {
    assert(pos < size());
	
    //因为类变量的本质是数组,所以直接返回即可
    return _start[pos];
}

扩容

reserve

和string一样,如果容量不足肯定是需要扩容的,但是这里和string不同的是,string我们可以直接使用 memcpy 将数据拷贝到新扩容的空间即可,但是memcpy只能浅拷贝数据。vector是可以嵌套vector类型的,也就相当于二维数组那样。如果单单浅拷贝的话,当我们将原有空间释放后,原地址的数据也都没有了,那这个时候新扩容的空间指向的就是一个野指针了。所以为了避免这种情况,我们只能稳稳地将每个数据复制到每一个对应的新地址

void reserve(size_t n) {
    if (n > capacity()) {
        T* tmp = new T[n];
        //记录数据个数
        size_t i = size();

        if (_start) {
            //浅拷贝
            //memcpy(tmp, _start, sizeof(T) * i);
            //深拷贝,将新的每一个对应的地址赋值
            for (size_t k = 0; k < i; k++)
            	tmp[k] = _start[k];
            delete[] _start;
        }
		
        //新的首地址
        _start = tmp;
        //新的finish就是新的首地址加上数据个数
        _finish = _start + i;
        //新的空间尾地址等于新的首地址加上空间容量
        _endofstorage = _start + n;
    }
}

resize

这里的resize和string的是一样的效果,可以参考一下上篇文章

void resize(size_t n, const T& x = T()) {
    if (n > capacity())
    	reserve(n);

    if (n > size())
        while (_finish != _endofstorage) {
            *_finish = x;
            _finish++;
        }

    else
    	_finish = _start + n;

}

一样的缺省参数不可以直接使用0或" ",要用T类型的默认初值

插入和删除

push_back

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

    *_finish = x;
    _finish++;
}

pop_back

void pop_back() {
    assert(!empty());

    --_finish;
}

insert

在指定位置插入一个指定数据,和string一样,不过这里有一个返回值,返回修改的那个位置。除此之外这里还需要注意一个非常重要的问题----迭代器失效

因为既然是插入就意味着可能会需要扩容,但是在C/C++中的扩容并不会保证是原地扩容还是异地扩容,所以总是会有异地扩容的情况,那这个时候传入的参数是一个指针,也就是说如果发生了异地扩容后,该指针就不再指向的是该 vector 对象里的空间了,那这个时候就失效了

image-20221124220637889

所以 如果想让pos指向扩容后的位置,那就需要一开始先记录pos和首地址之间的长度,扩容之后记录的长度加上首地址就是对应的pos应该指向的位置了

iterator insert(iterator pos, const T& x) {
    assert(pos >= _start);
    assert(pos < _finish);

    if (_finish == _endofstorage) {
    	//记录pos和首地址之间的长度
        size_t len = pos - _start;
        size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
        reserve(newcapacity);
        //加回长度
        pos = len + _start;
    }

    //挪动数据,留出pos插入数据
    iterator end = _finish - 1;
    while (end >= pos) {
        *(end + 1) = *end;
        end--;
    }

    *pos = x;
    _finish++;

    return pos;
}

erase

删除指定位置的值,这个同样也会有一个返回值,返回原被删除位置的下一个位置。为什么这个也要有返回值呢,因为这个也会出现迭代器失效的情况,当删除完该位置的数据后,编译器会默认认为该迭代器就失效了,所以如果还需要使用就得更新一下,那就的返回一个迭代器接收之后再使用。

iterator erase(iterator pos) {
    assert(pos >= _start);
    assert(pos < _finish);

    iterator begin = pos;
    while (begin < _finish - 1) {
        *begin = *(begin + 1);
        begin++;
    }
    _finish--;

    return pos;
}

全代码

#include<iostream>
#include<cassert>
using namespace std;

template<class T>
class myvector {
public:
	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;
	}

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

		//因为类变量的本质是数组,所以直接返回即可
		return _start[pos];
	}

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

		return _start[pos];
	}

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

	myvector(int n, const T& val = T()) 
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		reserve(n);
		for (int i = 0; i < n; i++)
			push_back(val);
	}

	template <class InputIterator>
	myvector(InputIterator first, InputIterator last)
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		while (first != last) {
			push_back(*first);
			first++;
		}
	}

	/*myvector(const myvector<T>& v)
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		reserve(v.capacity());
		for (const auto& e : v)
			push_back(e);
	}*/

	myvector(const myvector<T>& v)
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
	{
		myvector<int> tmp(v.begin(), v.end());
		swap(tmp);
	}

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

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

	void reserve(size_t n) {
		if (n > capacity()) {
			T* tmp = new T[n];
			//记录数据个数
			size_t i = size();
				
			if (_start) {
				//浅拷贝
				//memcpy(tmp, _start, sizeof(T) * i);
				//深拷贝
				for (size_t k = 0; k < i; k++)
					tmp[k] = _start[k];
				delete[] _start;
			}

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

	void resize(size_t n, const T& x = T()) {
		if (n > capacity())
			reserve(n);

		if (n > size())
			while (_finish != _endofstorage) {
				*_finish = x;
				_finish++;
			}

		else
			_finish = _start + n;
			
	}

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

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

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

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

		*_finish = x;
		_finish++;
	}

	void pop_back() {
		assert(!empty());

		--_finish;
	}

	iterator insert(iterator pos, const T& x) {
		assert(pos >= _start);
		assert(pos < _finish);

		if (_finish == _endofstorage) {
			//记录pos和首地址之间的长度
			size_t len = pos - _start;
			size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
			reserve(newcapacity);
			//加回长度
			pos = len + _start;
		}

		iterator end = _finish - 1;
		while (end >= pos) {
			*(end + 1) = *end;
			end--;
		}

		*pos = x;
		_finish++;

		return pos;
	}

	iterator erase(iterator pos) {
		assert(pos >= _start);
		assert(pos < _finish);

		iterator begin = pos;
		while (begin < _finish - 1) {
			*begin = *(begin + 1);
			begin++;
		}
		_finish--;

		return pos;
	}

	void clear() {
		assert(!empty());

		_finish = _start;
	}

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

private:
	iterator _start;//首地址
	iterator _finish;//最后一位有效数据地址
	iterator _endofstorage;//容量的最后一位地址
};

总结

STL中的各容器都大同小异

需要掌握常见的接口的使用与作用

学完一种容器就可以去多做点题巩固一下

!!!!!

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

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

相关文章

CDH启用kerberos 高可用运维实战

一、背景说明 在前的文章中介绍过《CDH集成的kerberos迁移实战》,由此也考虑到kerberos单节点可能引发的线上事故&#xff0c;所有考虑到把线上kerberos服务启用高可用。 二、环境介绍 系统版本 CentOS Linux release 7.6.1810 (Core) CM版本 Kerberos版本 三、实操…

桌面应用开发有哪些主流框架?

受益于开源技术的发展&#xff0c;以及响应快速开发的实际业务需求&#xff0c;跨平台开发不仅限于移动端跨平台&#xff0c;桌面端虽然在市场应用方面场景不像移动端那么丰富&#xff0c;但也有市场的需求。 相对于个人开发者而言&#xff0c;跨平台框架的使用&#xff0c;主要…

零基础学习下载FL Studio2023水果编曲软件

FL Studio工具常称水果编曲软件&#xff0c;是一款功能强大的编曲软件&#xff0c;集编曲&#xff0c;录音&#xff0c;剪辑&#xff0c;混音于一身&#xff0c;简单易上手&#xff0c;灵活性高&#xff0c;强大到突破想象。FL Studio&#xff0c;当前版本 FL Studio21&#xf…

Transductive Learning 和 Inductive Learning

简介 在 kipf-GCN 和 GraphSage 中&#xff0c;对 Transductive Learning 和 Inductive Learning 有了比较深刻的认识。 kipf-GCN 在其论文中提到算法属于 transductive node classification&#xff0c;也就是在训练节点embedding的时候要看到全图的节点&#xff0c;这是因为…

Linux——进程间通信(共享内存)

一、共享内存 1、定义 共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间&#xff0c;多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址&#xff0c;就好像它们是由malloc分配的一样。如果某…

47 - 父子间的冲突

---- 整理自狄泰软件唐佐林老师课程 1. 思考 子类中是否可以定义父类中的同名成员&#xff1f; 如果可以&#xff0c;如何区分&#xff1f;如果不可以&#xff0c;why&#xff1f; 1.1 编程实验&#xff1a;同名成员变量 #include <iostream> #include <string>…

操作系统导论--受限制的直接执行

受限直接执行 为了使程序尽可能快地运行&#xff0c;操作系统开发人员想出了一种技术——我们称之为受限的直接执行。 这个概念的“直接执行”部分很简单&#xff1a;只需直接在CPU上运行程序即可。因此&#xff0c;当OS希望启动程序运行时&#xff0c;它会在进程列表中为其创…

Packet Tracer - 排除单区域 OSPFv2 故障

地址分配表 设备 接口 IP 地址 子网掩码 默认网关 R1 G0/0 172.16.1.1 255.255.255.0 不适用 S0/0/0 172.16.3.1 255.255.255.252 不适用 S0/0/1 192.168.10.5 255.255.255.252 不适用 R2 G0/0 172.16.2.1 255.255.255.0 不适用 S0/0/0 172.16.3.2 25…

slam定位学习笔记(六)

学习内容来自这篇文章&#xff0c;主要是将模块进行划分以及根据划分的模块对前面的代码结构进行调整。 一、模块划分 之前的文章主要介绍了这几个功能的实现&#xff1a;传感器时间同步、点云去畸变、实时显示点云、精度评价和前端里程计。将它们区分成三个模块&#xff1a;…

基于JSP的私人牙科医院管理系统【数据库设计、源码、开题报告】

数据库脚本下载地址&#xff1a; https://download.csdn.net/download/itrjxxs_com/86466989 主要使用技术 ServletJSPJSCSSMysql 功能介绍 登录注册模块&#xff1a;填写基本信息注册&#xff08;普通用户&#xff09;登录&#xff1b; 公告管理&#xff1a;公告增删改查&…

预训练模型分词方式

BPE、WordPiece、SentencePiece tokenize的目标是将输入的文本流&#xff0c; 切分成一个个子串&#xff0c;使得每个子串具有相对完整的语义&#xff0c;便于学习embedding表达和后续模型的使用。 tokenize三种粒度&#xff1a;word、subword、char word/词&#xff1a;最自…

Flutter高仿微信-第35篇-单聊-视频通话

Flutter高仿微信系列共59篇&#xff0c;从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。 详情请查看 效果图&#xff1a; 目前市场上第三方视频接口的价格高的吓人 视频通话价格&#xff1a; 标清&…

代码随想录算法训练营day55 | 392.判断子序列,115.不同的子序列

392.判断子序列 参考代码随想录算法训练营第五十五天 |392. 判断子序列、115. 不同的子序列 - 掘金 暴力解法&#xff1a; Time Complexity: O(M*N) Space Complexity: O(N) class Solution:def isSubsequence(self, s: str, t: str) -> bool:start 0 #used to make sur…

[附源码]java毕业设计医院网上预约系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Docker(9)DockerFile

文章目录什么是DockerFile构建过程执行DockerFile大致流程常用保留字DockerFile小案例创建dockerfile文件dockerfile内部脚本执行命令虚悬镜像什么是DockerFile DockerFile 使用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需要的指令和参数组成脚本 构建过程…

CSDN Markdown 编辑器的目录与页内跳转功能

CSDN Markdown 编辑器的目录与页内跳转功能 文章目录CSDN Markdown 编辑器的目录与页内跳转功能 目录 页内跳转 正文内容跳转到标题正文内容跳转到正文内容结语目录 CSDN Markdown 编辑器的目录功能使用[TOC](你想要显示的目录总标题)格式&#xff0c;其中默认可省略括号及括…

2022-11-06 网工进阶(三十五)---PIM-SM工作原理(组播分发树的形成、ASM、SSM)

PIM-DM的局限性 中大型组播网络中由于网络较大&#xff0c;如果依然使用PIM-DM会遇到诸多问题&#xff1a; 1 使用“扩散-剪枝”方式需要全网扩散组播报文&#xff0c;对于网络有一定冲击。 2 所有组播路由器均需要维护组播路由表&#xff0c;即使该组播路由器无需转发组播数…

ARM-A架构入门基础(一)预备知识

14天学习训练营导师课程&#xff1a;周贺贺《ARMv8/ARMv9架构-快速入门》 1. 背景 ARM全称&#xff1a; Advanced RISC Machines。 ARM处理器&#xff1a; 基于ARM公司设计的架构而研发的处理器&#xff0c;包含arm core和外设。ARM公司本身不生产处理器&#xff0c;只出售技…

RNA-seq 详细教程:分析流程介绍(1)

学习目标 了解从 RNA 提取到获取基因表达矩阵&#xff0c; 既RNA-seq 分析的整个流程。 1. workflow 进行差异表达基因分析的前提是&#xff0c;获取代表基因表达水平的矩阵。因此在进行分析前&#xff0c;必须知道基因表达矩阵是如何产生的。 在本教程中&#xff0c;将会简要的…

基于JAVA的鲜花店商城平台【数据库设计、源码、开题报告】

数据库脚本下载地址&#xff1a; https://download.csdn.net/download/itrjxxs_com/86427660 摘要 在互联网不断发展的时代之下&#xff0c;鲜花软件可以为鲜花企业带来更多的发展机会&#xff0c;让企业可以挖掘到更多的潜在用户&#xff0c;同时结合企业的优势就能够为用户…