【C++】vector类:模拟实现(适合新手手撕vector)

news2024/12/24 2:31:13

在实现本文的vector模拟前,建议先了解关于vector的必要知识:【C++】容器vector常用接口详解-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/2301_80555259/article/details/141529230?spm=1001.2014.3001.5501


目录

一.基本结构

二.构造函数(constructor)

三.迭代器(iterator)

四.容量操作(capacity)

1.resize

2.reserve

五. 修改操作(modify)

六.访问操作(access)

七.重载赋值运算符

八.析构函数(destructor)

九.打印(print)


一.基本结构

创建头文件:vector.h 实现文件:vector.cpp 测试文件:test.cpp

当然此处为了简化直接使用了头文件+测试文件,在自定义的命名空间内创建vector类,配上基本结构:

由于vector是顺序表,因此和C语言实现顺序表时一样,至上有三个参数:

  1. 指向一段连续空间的指针
  2. 空间的有效大小
  3. 空间的实际大小

实现vector的迭代器,可以就将其视为指针,因此可以使用三个迭代器(指针)就完成上述三个参数的实现,它们分别是_start,_finish,_end_of_storage,此时

  1. 指向一段连续空间的指针:_start
  2. 空间的有效大小size:_finish - _start
  3. 空间的实际大小capacity:_end_of_storage - _start

template<class T>
class vector
{
public:
    //用指针实现vector的迭代器
	typedef T* iterator;
	typedef const T* const_iterator;
private:
	iterator _start;
	iterator _finish;
	iterator _end_of_storage;
};

二.构造函数(constructor)

//无参构造
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{}
//带参构造,用n个value来初始化vector
vector(int n, const T& value = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(n);
	for (int i = 0; i < n; i++)
	{
		push_back(value);
	}
}
//拷贝构造
vector(const vector& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{
	//易错点:这里并没有进行初始化
	reserve(v.size());
	for (auto e : v)
	{
		push_back(e);
	}
}

//迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

这里单独把这个构造拿出来谈谈, 最开始我只在无参构造中使用初始化列表,没有对其他构造函数的_start等进行初始化,这个bug我找了半天,每一个构造也是构造函数,跟其他构造函数没有关系,是凭借拷贝构造自身创建一个新的对象,因此也要进行初始化序列来初始化。这是最为保险的,否则就要在成员变量声明处加上默认值。

发现这个错误的bug就在使用赋值重载时,临时变量需要调用拷贝构造,但若此时拷贝构造没有去手动写初始化列表,那么其_finish和_end_of_storage不是nullptr,是随机值,这样就导致了错误

然而在有参构造时,尽管没去写初始化列表,对应的_start和_finish等也都是nullptr,此时就没有问题,但是为了保险,我建议所有构造函数都应该去手写一遍初始化列表。

三.迭代器(iterator)

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

四.容量操作(capacity)

容量操作方面的函数有:

  • size
  • capacity
  • empty
  • resize
  • reserve

前三个较为简单,很容易实现:

size_t size()const
{
	return _finish - _start;
}
size_t capacity()const
{
	return _end_of_storage - _start;
}
bool empty()const
{
	return size() == 0;
}

1.resize

resize会改变size的大小,同时也有可能改变capacity的大小,具体要分三种情况:

  1. n大于capacity时:直接套用reserve扩容后再初始化新内容
  2. n大于size小于capacity时:有效值不变的情况下初始化新内容
  3. n小于size时:直接将size缩小到n
void resize(size_t n, const T& value = T()) 
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);
		while (_finish != _start + n)
		{
			*_finish = value;
			_finish++;
		}
	}
}

如何来理解:const T& value = T() ?
 

在C++中内置类型(如int,double,char)特殊处理过,升级为了类,也有默认构造函数,它会将变量初始化为默认值。

此处使用了匿名对象

int tmp1 = int();//0
int tmp2 = int(10);//10

2.reserve

reserve扩容也是同理,只在n大于capacity的时候进行扩容

void reserve(size_t n)
{
	if (n > capacity())
	{
		//记录原本的数据个数
		size_t old_size = size();
		iterator tmp = new T[n];
		memcpy(tmp, _start, old_size * sizeof(T));
		delete[] _start;
		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

五. 修改操作(modify)

//modify
void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*_finish = x;
	_finish++;
}
void pop_back()
{
	assert(size() > 0);
	--_finish;
}
iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	if (_finish == _end_of_storage)
	{
		//注意这里扩容会使原有的pos迭代器失效
		//因此这里使用len计算相对长度,更新迭代器
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = _start + len;
	}
	iterator end = _finish;
	while (end > pos)
	{
		*end = *(end - 1);
		end--;
	}
	*pos = x;
	++_finish;
	return pos;
}
void erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);
	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;
		it++;
	}
	--_finish;
}

六.访问操作(access)

//access
T& operator[](size_t i)
{
	assert(i < size());
	return _start[i];
}
const T& operator[](size_t i)const
{
	assert(i < size());
	return _start[i];
}

七.重载赋值运算符

注意这里的参数不是常量引用,而是按值传递。这是因为之后调用swap函数,按值传递可以使得传入的参数会被复制一份给临时对象,从而避免了对原对象的修改。

//operator =
void swap(vector& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

//在模板内,写vector和vector<T>都行
//因为要交换内部内容,因此不加const 
vector& operator=(vector v)
{
	swap(v);
	return *this;
}

八.析构函数(destructor)

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

九.打印(print)

template<class T>
void print_vector(const vector<T>& v)
{
	//规定,从没有实例化的类模版中取东西
	//编译器不能区分const_iterator是类型还是静态成员变量,因此要加typename
	//当然也可以更简单写为auto it  = v.begin();
	typename vector<T>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

template<class container>
void print_container(const container& v)
{
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

本文结束,下次将会介绍容器中的list,和vector是类似的,但也有不同,欢迎持续关注

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

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

相关文章

【算法】位运算

【ps】本篇有 10 道 leetcode OJ。 目录 一、算法简介 二、相关例题 1&#xff09;位1的个数 .1- 题目解析 .2- 代码编写 2&#xff09;比特位计数 .1- 题目解析 .2- 代码编写 3&#xff09;汉明距离 .1- 题目解析 .2- 代码编写 4&#xff09;只出现一次的数字 .…

3000字带你了解SD提示词用法,一点就通,小白轻松上手(附提示词生成器)(1.4 SD提示词运用)

提示词是什么 提示词是我们向AI模型发出的指令。正确的提示词能让AI准确反馈所需的输出&#xff0c;而优质的提示词则能使AI生成的内容更优质、更符合你的期望。这与编写程序代码颇为相似&#xff0c;准确的代码逻辑是程序正常运行的前提&#xff0c;而优秀的代码则能减少运行…

知识付费小程序源码轻松实现一站式运营,开启知识变现之旅

技术栈&#xff1a; 以下是一个简单的知识付费小程序的示例代码&#xff1a; app.js&#xff1a;小程序的入口文件 App({onLaunch: function () {// 在小程序启动时执行的代码},globalData: {// 存储全局数据userInfo: null // 用户信息} })pages/index/index.js&#xff1…

【学术会议征稿】第四届智能电网与能源互联网国际会议(SGEI 2024)

第四届智能电网与能源互联网国际会议&#xff08;SGEI 2024&#xff09; 2024 4th International Conference on Smart Grid and Energy Internet 为交流近年来国内外在智能电网和能源互联网领域的理论、技术和应用的最新进展&#xff0c;展示最新成果&#xff0c;由沈阳工业…

Visual Studio 2022 下载和安装

文章目录 概述一&#xff0c;下载步骤二&#xff0c;安装过程 概述 Visual Studio 提供 AI 增强功能&#xff0c;例如用于上下文感知代码补全的 IntelliSense 和可利用开源代码中的 AI 模式的 IntelliCode。 集成的 GitHub Copilot 提供 AI 支持的代码补全、聊天辅助、调试建议…

ElasticSearch学习笔记(三)RestClient操作文档、DSL查询文档、搜索结果排序

文章目录 前言5 RestClient操作文档5.4 删除文档5.4 修改文档5.5 批量导入文档 6 DSL查询文档6.1 准备工作6.2 全文检索查询6.3 精准查询6.4 地理坐标查询6.5 复合查询6.5.1 相关性算分6.5.2 布尔查询 7 搜索结果处理7.1 排序7.1.1 普通字段排序7.1.2 地理坐标排序 前言 Elast…

qmt量化交易策略小白学习笔记第59期【qmt编程之期权数据--获取指定期权品种的详细信息--原生Python】

qmt编程之获取期权数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 基于BS模型计算欧式期权理论价格 基于Black-Scholes-Merton模型&#xff0c;输入期权标的价格、期权行权价、无风险利率…

Mac 安装Hadoop教程(HomeBrew安装)

1. 引言 本教程旨在介绍在Mac 电脑上安装Hadoop&#xff0c;便于编程开发人员对大数据技术的熟悉和掌握。 2.前提条件 2.1 安装JDK 想要在你的Mac电脑上安装Hadoop&#xff0c;你必须首先安装JDK。具体安装步骤这里就不详细描述了。你可参考Mac 安装JDK8。 2.2 配置ssh环境…

从腰子的营养成分来分析腰子能否“壮阳”,健康地吃腰子。

文章目录 引言I 腰子的营养优点缺点吃腰子无“壮阳”效果II 健康地吃腰子食用前充分清洗浸泡高尿酸及痛风群体慎吃适量吃引言 很多人认为动物内脏有着“以形补形”的好处,如吃动物腰子,能补肾、壮阳,这让很多人对“腰子”非常热爱。 腰子的营养到底如何?经常吃腰子对身体…

优思学院:FMEA与FTA故障树方法对比:工程师必须知道的关键点!

故障树分析&#xff08;Fault Tree Analysis, FTA&#xff09;以某个不希望发生的产品故障事件或严重的系统风险&#xff08;即顶事件&#xff09;为分析对象&#xff0c;通过自上而下的分层次因果逻辑分析&#xff0c;逐步找出导致故障事件的必要且充分的直接原因&#xff0c;…

日程安排组件DHTMLX Scheduler v7.1 - 支持RFC-5545格式

DHTMLX Scheduler是一个类似于Google日历的JavaScript日程安排控件&#xff0c;日历事件通过Ajax动态加载&#xff0c;支持通过拖放功能调整事件日期和时间&#xff0c;事件可以按天、周、月三个种视图显示。 此版本包括几个备受期待的特性&#xff0c;可以帮助用户增强DHTMLX…

基于php+vue+uniapp的医院预约挂号系统小程序

开发语言&#xff1a;PHP框架&#xff1a;phpuniapp数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;PhpStorm 系统展示 后台登录界面 管理员功能界面 用户管理 医生管理 科室分类管理 医生信息管理 预…

30s到0.8s,记录一次接口优化成功案例!

大家好&#xff0c;我是沐子&#xff0c;推荐一个程序员免费学习的编程网站 我爱编程网&#xff08;www.love-coding.com&#xff09; ** 场景** 在高并发的数据处理场景中&#xff0c;接口响应时间的优化显得尤为重要。本文将分享一个真实案例&#xff0c;其中一个数据量达到…

(5) 归并排序

归并排序 归并排序是一种分治策略的排序算法。它是一种比较特殊的排序算法&#xff0c;通过递归地先使每个子序列有序&#xff0c;再将两个有序的序列进行合并成一个有序的序列。 归并排序首先由著名的现代计算机之父 John_von_Neumann 在 1945 年发明&#xff0c;被用在了 E…

【Python】Python 读取Excel、DataFrame对比并选出差异数据,重新写入Excel

背景&#xff1a;我在2个系统下载出了两个Excel&#xff0c;现在通过对下载的2个Excel数据&#xff0c;并选出差异数据 从新写入一个新的Excel中 differences_url rC:\Users\LENOVO\Downloads\differences.xlsx; //要生成的差异Excel的位置及名称 df1_url rC:\Users\LENOVO\Dow…

终于知道如何简化时间序列的特征工程了!

在处理时间序列数据时&#xff0c;时间特征往往是最基础且独特的要素&#xff0c;我们的目标通常是预测某种未来的响应或结果。 不过在很多情况下&#xff0c;除了时间特征之外&#xff0c;我们还能获取到一系列其他相关的特征或变量。 时间序列数据中的特征工程涉及从原始时…

进程、线程、时间片

1、操作系统中的程序&#xff08;如微信&#xff09;在运行时&#xff0c;系统会产生一个或多个进程&#xff0c;往往是一个 2、进程内可以包含多个线程&#xff0c;有一个主线程&#xff0c;主线程结束时&#xff0c;进程结束&#xff0c;进而程序结束 3、线程是cpu调度执行…

sql日期函数

目录 sql日期函数 1.获取日期时间函数 1.1 获取当前日期时间 1.2 获取当前日期 1.3 获取当前时间 2.datetime数据类型格式化 3.字符串数据类型转换成datetime数据类型 4.增加和减少时间间隔 5. 日期相差天数&#xff08;天&#xff09; 6. 相差时间&#xff08;小时&am…

GitHub Star 数量前 11 的开源内部工具

欢迎回到我们的 GitHub Star 系列文章&#xff01; 在之前的文章中&#xff0c;我们深入探讨了 GitHub 上最受欢迎的开源低代码项目《GitHub Star 数量前 15 的开源低代码项目》和开源无代码工具《GitHub Star 数量前 12 的开源无代码工具》&#xff0c;获得了热烈的反馈。本周…

【嵌入式学习笔记】---- OLED屏幕工作原理

1 驱动芯片SSD1603简介 1.1 SSD1603芯片图 SSD1603是一款点阵显示屏控制器&#xff0c;可嵌入在屏幕中&#xff0c;用于执行接收数据、显示存储、扫描刷新等任务驱动接口&#xff1a;128个SEG引脚和64个COM引脚&#xff0c;对应 128 64 128\times 64 12864像素点阵显示屏内置…