C++【STL】之vector模拟实现

news2024/11/27 4:19:34

C++ vector类模拟实现

上一篇讲解了vector的使用,这一篇接着介绍vector的模拟实现,这里依然是讲解常用接口的模拟实现,话不多说,正文开始!

文章目录:

  • C++ vector类模拟实现
    • 1. 成员变量
    • 2. 默认成员函数
      • 2.1 构造和析构
      • 2.2 拷贝和赋值
    • 3. 容量操作
      • 3.1 查看容量大小和判空
      • 3.2 reserve方法
      • 3.3 resize方法
    • 4. 数据访问
      • 4.1 下标访问
      • 4.2 迭代器
    • 5. 数据修改
      • 5.1 push_back方法
      • 5.2 pop_back方法
      • 5.3 insert方法
      • 5.4 erase方法
    • 6. 完整代码

1. 成员变量

vector的成员变量是三个指针

namespace sakura	//命名空间
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator; //迭代器
		typedef const T* const_iterator;
	private:
    	iterator _start; //指向已开辟空间起始位置
    	iterator _finish; //指向有效元素的下一个位置
    	iterator _end_of_storage; //指向已开辟空间的下一个位置
	};
}		

我们使用vector存储的数据类型会是int、char等不同的类型,使用模板可以更好的实例化

2. 默认成员函数

2.1 构造和析构

构造函数

//默认构造
vector()
    :_start(nullptr)
    ,_finish(nullptr)
    ,_end_of_storage(nullptr)
{}
//带参构造
vector(size_t n, const T& val = T()) //const T&修饰匿名对象->起别名延长生命周期
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
{
    reserve(n);
    for (size_t i = 0; i < n; ++i)
    {
        push_back(val);
    }
}
//此版本避免匹配迭代器区间构造
vector(int n, const T& val = T()) //const T&修饰匿名对象->起别名延长生命周期
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
{
    reserve(n);
    for (int i = 0; i < n; ++i)
    {
        push_back(val);
    }
}
//迭代器区间构造 左闭右开[first, last)
template<class InputIterator>
vector(InputIterator first, InputIterator last) 
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
{
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}

这里vector(size_t n, const T& val = T())使用了匿名对象做缺省值

  • 匿名对象的生命周期只在本行有效,但其被const修饰后生命周期会延长
  • 内置类型也能创建匿名对象,如int()char()

*注意: 这里需要额外提供一个 vector(int n, const T& value = T()) 版本,避免使类似vector<int> v(6, 8) 构造对象会优先匹配上迭代器构造,造成 非法间接寻址错误

析构函数

~vector()
{
    delete[] _start;
    _start = _finish = _end_of_storage = nullptr;
}

_start 指向的是已开辟空间的首位置,可以直接对其释放,由于申请空间用的是new [],所以释放时需要对应使用delete []

2.2 拷贝和赋值

拷贝构造

拷贝构造对象前先进行扩容,避免空间浪费,这里采用逐个数据赋值拷贝的方式,因为 T 有可能是自定义类型,这样可以避免浅拷贝问题

vector(const vector<T>& v)
    :vector() //使用默认构造初始化
{
    reserve(v.capacity());	//扩容
    _start = new T[v.capacity()];
    //memcap(_start, v._start, sizeof(T) * v.size()); //浅拷贝,err
    for (size_t i = 0; i < v.size(); ++i)
    {
        _start[i] = v._start[i];
    }
    _finish = _start + v.size();
    _end_of_storage = _start + v.capacity();
}

这里不能采用memcpy来进行拷贝操作,因为memcpy是浅拷贝

vector的拷贝构造必须自己实现,默认生成的是浅拷贝

赋值重载

赋值重载的实现和拷贝构造差不多,只是不需要新对象,只需进行赋值操作即可

vector<T>& operator=(const vector<T>& v)
{
    if (this != &v)
    {
        reserve(v.capacity());	//扩容
        size_t pos = 0;
        for (size_t i = 0; i < v.size(); ++i)
        {
            *(_start + i) = *(v.begin() + i)
        }
        _finish = begin() + v.size();
    }
    return *this;
}

3. 容量操作

3.1 查看容量大小和判空

可以直接通过迭代器操作获取所需值

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

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

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

3.2 reserve方法

操作步骤:

  • 判断是否需要扩容
  • 拷贝数据,保存有效元素大小
  • 开辟新空间,将拷贝的数据移动至新空间,然后释放旧空间,更改指针指向
void reserve(size_t n)
{
    if (n > capacity())
    {
        size_t sz = size(); //先保存有效元素大小
        T* tmp = new T[n]; //开辟新空间
        if (_start)
        {
            for (size_t i = 0; i < sz; ++i) //深拷贝
			{
				tmp[i] = _start[i];
			}
            delete[] _start; //释放原空间
        }
        _start = tmp;
        _finish = _start + sz;
        _end_of_storage = _start + n;
    }
}

先保存有效元素大小这一步很重要!!!

因为size() = _finish - _start,所以_finish = _start + size(),再代入size()就造成了_finish = _start + _finish - _start --> _finish = _finish恒等式

3.3 resize方法

操作步骤:

  • 判断nsize()的大小
  • 如果n < size()就删除超出数据,反之就插入
void resize(size_t n, T val = T())
{
    if (n < size())
    {
        //删除数据
        _finish = _start + n;
    }
    else
    {
        if (n > capacity())
            reserve(n);
        while (_finish != _start + n)
        {
            *_finish = val;
            ++_finish;
        }
    }
}

4. 数据访问

4.1 下标访问

下标访问就是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];
}

4.2 迭代器

vector中的迭代器使用的是原生指针,如 begin() == _startend() == _finish,这里需要提供普通版本和const版本的两种迭代器

typedef T* iterator; //迭代器
typedef const T* const_iterator; //const迭代器
        iterator begin()
{
    return _start;
}

iterator end()
{
    return _finish;
}

const_iterator begin() const
{
    return _start;
}

const_iterator end() const
{
    return _finish;
}

5. 数据修改

5.1 push_back方法

尾插要注意首先判断容量是否足够,然后插入数据改变_finish指向即可

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

5.2 pop_back方法

尾删需要先判断容器中是否有数据,然后直接该变_finish指向即可

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

5.3 insert方法

iterator inseret(iterator pos, const T& val)
{
    assert(pos >= _start);
    assert(pos <= _finish);
    if (_finish == _end_of_storage)
    {
        size_t len = pos - _start; //记录当前迭代器的位置(pos和_start间的距离)
        reserve(capacity() == 0 ? 4 : capacity() * 2);
        //扩容后更新pos,解决pos失效问题
        pos = _start + len;
    }
    iterator end = _finish - 1;
    while (end >= pos)
    {
        *(end + 1) = *end;
        --end;
    }
    *pos = val;
    ++_finish;
    return pos;
}

注意:

insert()的实现中,一定要先记录pos的位置,因为在扩容后迭代器pos的位置会指向已释放的旧空间,导致迭代器失效!

迭代器失效演示:

改进方法:

5.4 erase方法

iterator erase(iterator pos)
{
    assert(pos >= _start);
    assert(pos < _finish);
	//迭代器区间删除
    iterator start = pos + 1;
    while (start != _finish)
    {
        *(start - 1) = *start;
        ++start;
    }
    --_finish;
    return pos;
}

visual studio2019中的erase机制是一定会pos迭代器失效

6. 完整代码

#pragma once

#include <iostream>
#include <vector>
#include<assert.h>
#include <functional>
#include<algorithm>
#include<vector>
using namespace std;

namespace sakura
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//默认构造
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}
		//带参构造
		vector(size_t n, const T& val = T()) //const T&修饰匿名对象->起别名延长生命周期
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}
		//此版本避免匹配迭代器区间构造
		vector(int n, const T& val = T()) //const T&修饰匿名对象->起别名延长生命周期
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}
		//迭代器区间构造 左闭右开[first, last)
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		vector(const vector<T>& v)
			:vector() //使用默认构造初始化
		{
			reserve(v.capacity());	//扩容
			_start = new T[v.capacity()];
			//memcap(_start, v._start, sizeof(T) * v.size()); //浅拷贝,err
			for (size_t i = 0; i < v.size(); ++i)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size(); //先保存有效元素大小
				T* tmp = new T[n]; //开辟新空间
				if (_start)
				{
					for (size_t i = 0; i < sz; ++i) //深拷贝
					{
						tmp[i] = _start[i];
					}
					delete[] _start; //释放原空间
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

		void resize(size_t n, T val = T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
					reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

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

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

		iterator inseret(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start; //记录当前迭代器的位置(pos和_start间的距离)
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//扩容后更新pos,解决pos失效问题
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			//迭代器区间删除
			iterator start = pos + 1;
			while (start != _finish)
			{
				*(start - 1) = *start;
				++start;
			}
			--_finish;
			return pos;
		}

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

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

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

		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				reserve(v.capacity());	//扩容
				size_t pos = 0;
				for (size_t i = 0; i < v.size(); ++i)
				{
					*(_start + i) = *(v.begin() + i)
				}
				_finish = begin() + v.size();
			}
			return *this;
		}

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

		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	void func(const vector<int>& v)
	{
		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		vector<int>::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}
}

C++【STL】之vector模拟实现,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!

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

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

相关文章

使用lcov生成覆盖率报告

使用lcov生成覆盖率报告 1- 需要准备的东西1.1 工具lcov1.2 需要用到中间脚本 gcno gcda1.3 源文件 2- 生成覆盖率报告2.1 step1: 编译阶段2.2 step2: 数据收集与提取阶段2.3 step3: 报告形成阶段2.4 step4: lcov生成覆盖率报告结果info文件2.5 step5: genhtml 命令生成网页版的…

给定一个字符串比如“abcdef”,要求写个函数变成“defabc”,位数是可变的。

首先可以使用字符串切片的方法来实现这个需求。 具体做法是&#xff1a;① 定义一个整数变量 n 表示要切割的位置&#xff0c;本实例中为 3 。 ② 将字符串按照 n 分割成两个字串&#xff0c;即 “abc” 和 “def”。 ③ 将两个字符串颠倒顺序&#xff0c;即 “cba” 和 “fed…

数据结构 栈和队列

栈和队列基本概念 栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;都是常见的数据结构&#xff0c;用于存储和操作一组元素。它们在结构和操作方式上有所不同。 栈的基本概念&#xff1a; 栈是一种线性数据结构&#xff0c;具有后进先出&#xff08;L…

CentOS GCC 离线升级 编译安装 8.3.0

从系统自带的 gcc-4.8.5 版本升级至 gcc-8.3.0 版本 目录 下载源代码&#xff1a; 下载依赖&#xff1a; 编译&#xff08;约一个小时&#xff09; 重开控制台确认是否生效 下载源代码&#xff1a; https://ftp.gnu.org/gnu/gcc/gcc-8.3.0/gcc-8.3.0.tar.gzhttps://ftp.gn…

Nacos和Feign

Nacos配置管理 统一配置管理实现 1.引入Nacos的配置管理客户端依赖 <!--nacos的配置管理依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency…

国产开源中文大语言模型再添重磅玩家:清华大学NLP实验室发布100亿参数规模的开源可商用大语言模型CPM-Bee

5月27日&#xff0c;OpenBMB发布了一个最高有100亿参数规模的开源大语言模型CPM-BEE&#xff0c;OpenBMB是清华大学NLP实验室联合智源研究院成立的一个开源组织。该模型针对高质量中文数据集做了训练优化&#xff0c;支持中英文。根据官方的测试结果&#xff0c;其英文测试水平…

Python零基础入门(二)——IDE介绍以及Python+PyCharm的安装

系列文章目录 个人简介&#xff1a;机电专业在读研究生&#xff0c;CSDN内容合伙人&#xff0c;博主个人首页 Python入门专栏&#xff1a;《Python入门》欢迎阅读&#xff0c;一起进步&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; 码字不易&#xff0c;如果觉得文章不…

docker容器介绍及安装

Docker介绍 Docker 起源于2013年。 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go语言开发&#xff0c;Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的系统。 优点&#xff1a; 可以用来快速交付应用。加…

SQL 的window开窗函数简单使用

背景&#xff1a; 开窗函数不论是spark的还是clickhouse的在日常的查询中是一个很常用的功能&#xff0c;特别是他想要解决的问题和group by的很类似&#xff0c;这两种容易引起混淆&#xff0c;本文就简单的描述下开窗函数的简单用法 使用详解 首先窗口函数和group by是完全…

caj文件在线转换成pdf方法,看这个就会了!

当需要将Caj文件转换为PDF格式时&#xff0c;有多种方法可供选择。本文将介绍三种常用的方法&#xff0c;以帮助您完成这个任务。 第一种方法&#xff1a;使用记灵在线工具 一种常用的方法是利用记灵在线工具&#xff0c;它是一款提供免费文件转换服务的在线工具。以下是使用…

消息队列RabbitMQ

1. 消息队列 RabbitMQ 消息队列是一种在应用程序之间发送和接收消息的方法&#xff0c;可以实现异步通信、解耦应用、提高系统性能等效果。RabbitMQ 是一款常用的开源消息中间件&#xff0c;它实现了 AMQP 协议规范&#xff0c;并提供了可靠性、灵活性、易用性等优秀特性。本文…

DBSyncer安装_配置postgresql和mysql_sqlserver_oracel全量增量同步---数据全量增量同步之DBSyncer001

国内做开源的大神做的,用了一下还可以,就是不能和Phoenix这种操作hbase等数据库一起用, https://gitee.com/ghi/dbsyncer#postgresql 这个是官网,下载安装非常简单,官网也有中文详细说明. 直接下载安装包: 然后解压到某个地方,主要要用unzip dbsyncer.zip -d /opt/module这样…

干翻Mybatis源码系列之第十篇:Mybatis拦截器基本开发、使用和细节分析

给自己的每日一句 不从恶人的计谋&#xff0c;不站罪人的道路&#xff0c;不坐亵慢人的座位&#xff0c;惟喜爱耶和华的律法&#xff0c;昼夜思想&#xff0c;这人便为有福&#xff01;他要像一棵树栽在溪水旁&#xff0c;按时候结果子&#xff0c;叶子也不枯干。凡他所做的尽…

微信小程序 method传参 和 页面传参

method传参 标签&#xff1a; <image src"/img/b1.jpg" classbannerImg mode"widthFix" bindtap"gotoMessage" data-flag"msg"></image> 使用data-参数Key 指定参数值 method: gotoMessage(e){ let flagName e.targe…

9. 子查询

9.1 概述 ​ 子查询指一个查询语句嵌套在另一个查询语句内部&#xff0c;这个特性从 MySQL 4.1 开始引入。 ​ 从相对位置来说&#xff0c;子查询又被称为内查询&#xff0c;主查询又被称为外查询 9.1.1 子查询的结构 子查询的结构如下所示&#xff1a; SELECT select_lis…

Apache Zeppelin系列教程第九篇——SQL Debug In Zeppelin

SQL Debug介绍 首先介绍下什么是SQL Debug&#xff1f; 但是经常有这样一个需求&#xff0c;一大段sql 跑出来之后&#xff0c;发现不是自己想要的结果&#xff1f;比如&#xff1a; demo 1: select id,name from ( select id,name from table1 union all select id,name fr…

数据库(第五章)数据库的完整性

1.数据库的正确性和相容性 正确性&#xff1a;符合现实逻辑 相容性&#xff1a;两个表中的同一对象要完全相同 如何实现数据库的完整性&#xff1f; 1.定义完整性约束条件 2.提供完整性检查方法 3.进行违约处理 完整性我们之前学过。包括三个 1.实体完整性 2.参照完整性 3.用户…

Fiddler中 AutoResponder 使用

Fiddler的 AutoResponder &#xff0c;即URL重定向功能非常强大。不管我们做URL重定向&#xff0c;还是做mock测试等&#xff0c;都可以通过该功能进行实践。 下面&#xff0c;小酋就来具体讲下该功能的用法。 如果你想学习Fiddler抓包工具&#xff0c;我这边给你推荐一套视频…

【C++进阶】带你手撕AVL树

文章目录 一、什么是AVL树二、AVL树的定义三、AVL树的插入1.理论讲解2.代码实现 四、AVL树的旋转1.左单旋2.右单旋3.左右双旋4.右左双旋 五、 AVL树的验证六、完整源码 一、什么是AVL树 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为…

Kafka集群部署

Kafka是一个高吞吐量、基于ZooKeeper&#xff08;ZooKeeper维护Kafka的broker信息&#xff09;的分布式发布订阅信息系统&#xff0c;它可以处理消费者在网站中的所有动作&#xff08;网页浏览&#xff0c;搜索和其他用户的行动&#xff09;流数据。通常情况下&#xff0c;使用…