C++初阶学习——探索STL奥秘——vector的模拟实现

news2024/9/25 21:30:25

vector的结构比较特殊,成员变量为三个指针

#pragma once
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

#include <string>
using std::string;

namespace Yohifo
{
	template<class T>
	class vector
	{
	public:
		typedef T value_type;
		typedef value_type* pointer;	//指针
		typedef const value_type* const_pointer;
		typedef value_type* iterator;	//迭代器
		typedef const value_type* const_iterator;
		typedef value_type& reference;	//引用
		typedef const value_type& const_reference;
		
	private:
		iterator _start;	//指向起始位置
		iterator _finish;	//指向有效元素的下一个位置
		iterator _end_of_storage;	//指向可用空间的下一个位置
	};
}

 

1、默认成员函数

默认成员函数需要自己设计,因为涉及深浅拷贝问题

//默认构造
vector() :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}

//带参构造
vector(size_t n, const T& value = T())
	:vector()
{
	reserve(n);	//扩容
	while (n--) push_back(value);	//逐个尾插
}
//额外版本,避免匹配上迭代器区间构造
vector(int n, const T& value = T())
	:vector()
{
	reserve(n);	//扩容
	while (n--) push_back(value);	//逐个尾插
}

//迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
	:vector()
{
	//考虑提前计算容量
	InputIterator cur = first;
	int len = 0;
	while (cur != last) ++len, ++cur;
	reserve(len);
	while (first != last) push_back(*first), ++first;
}

注意:

 在设计带参构造函数时,需要再额外提供一个vector(int b,const T& value=T())版本

以防使用vector<int>v(10,6)(构造对象,内容为10个6)优先匹配上迭代器构造,此时会造成非法简介寻址错误

此时多处用到了匿名对象作为缺省值

vector(size_t n, const T& value = T());
vector(int n, const T& value = T());

带参构造、拷贝构造、迭代器区间构造等函数创建新对象前,需要先初始化,有多种初始化方法:

1.在定义成员变量后设置缺省值

2.在创建新对象前手动进行初始化(初

始化列表)

3.d调用默认构造进行初始化

这里采用的是初始化列表调用默认构造函数初始化的方式

匿名对象调用默认构造就是需要写T(),如果匿名对象的无参构造需要写成T(),要是直接写成T,就会被当做是类型T,会出现语法报错

1.2拷贝构造

//拷贝构造-传统写法
vector(const vector<T>& v)
	:vector()
{
	reserve(v.capacity());	//扩容
	size_t pos = 0;
	while (pos < v.size()) 
    *(_start + pos) = *(v.begin() + pos), ++pos;
	_finish = begin() + v.size();
}
拷贝构造-现代写法
//vector(const vector<T>& v)
//	:vector()
//{
//	vector<T> tmp(v.begin(), v.end());	//构造临时对象
//	swap(tmp);	//直接交换
//}

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


比如 T为 string 类型,实际调用时是这样的 this[pos]= v[pos](string 对象,调用对应的赋值
重载函数)


注意: vector 的拷贝构造函数必须自己写,默认生成的是浅拷贝


现代写法着重交换思想,利用选代器区间构造出临时对象,再将临时对象“交换”给当前对象即可
这种方式有点窃取劳动成果的感觉-

1.3赋值重载

//赋值重载-传统写法
vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		reserve(v.capacity());	//扩容
		size_t pos = 0;
		while (pos < v.size())
         *(_start + pos) = *(v.begin() + pos), ++pos;
		_finish = begin() + v.size();
	}

	return *this;
}
赋值重载-现代写法
void swap(vector<T>& v)
{
	//交换三个内置类型,效率要高得多
	//std::swap(_start, v._start);
	//std::swap(_finish, v._finish);
	//std::swap(_end_of_storage, v._end_of_storage);
//}
//vector<T>& operator=(vector<T> tmp)
//{
//	swap(tmp);

//	return *this;
//}

赋值重载的传统写法与拷贝构造基本一致,赋值重载中不需要新建对象,因为是“赋值”。注意: 赋值前,可以先判断两个对象是否为同一个,如果是,则不需要进行操作

1.4构析函数

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

start 指向已开辟空间的首位置,可以直接进行空间释放


注意:空间申请时,使用的是 new[],因此释放时需要使用 delete []


1.5经典问题:深度拷贝


众多构造函数都离不开空间调整函数 reserve ,所以这里提前进行学习,并且 reserve 在实现时会出现一个经典问题:深浅拷贝

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();	//需要先保存 _start 与 _finish 间的距离
		pointer tmp = new value_type[n];	//开辟新空间
		if (begin())
		{
			//memcpy(tmp, begin(), size() * sizeof(T));	//不能直接移动
			size_t pos = 0;
			while (pos < sz)
			{
				//调用自定义类型的赋值重载函数,完成深拷贝
				*(tmp + pos) = *(begin() + pos);	//深拷贝
				pos++;
			}
			delete[] begin();	//释放原空间
		}

		_start = tmp;	//赋值新空间
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

函数执行步骤:


判断 n是否大于容量,大于才需要进行扩容


保存有效元素数(大小),后面有用


三步走:开辟新空间 ->移动元素至新空间 ->释放旧空间,更改指向


注意: 在将旧空间中的数据移动至新空间时,不能直接通过 memcpy/memmove 的方式进行数据移动,因为这些库函数都是浅拷贝,使用后会造成重复析构问题


举例:使用 memcpy 进行数据迁移

 

 

 

 实际上,拷贝构造、赋值重载、reserve 都需考虑深度拷贝的问题

一句话总结:对于自定义类型来说,在进行拷贝/赋值等操作时,调用对应的赋值重载函数即可


reserve 扩容时,发生了这些事情:

2.迭代器 

 vector的迭代器就是原生指针

typedef T value_type;
typedef value_type* iterator;	//迭代器
typedef const value_type* const_iterator;
		
//=====迭代器设计=====
iterator begin() { return _start; }
iterator end() { return _finish; }

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

3、容量

3.1查看容量

//=====容量相关=====
size_t size() const { return end() - begin();  }
size_t capacity() const { return _end_of_storage - begin();  }
bool empty() const { return begin() == end();  }

3.2、容量调整


可以调整容量( reserve ),也可以调整大小( resize)
reserve 前面已经介绍过了,这里来看看resize 

void resize(size_t n, const_reference val = value_type())
{
	if (n < size())
		erase(begin() + n, end());
	else
		insert(end(), n - size(), val);
}

操作步骤:


判断 n是否大于大小
如果小于,执行删除,区间为[begin()+n,end()]
如果小于或等于,就执行插入,区间为[end(),n-size(),val]


value_type 就是 T,缺省值为默认对象值

4、数据访问

4.1下标访问

有两种方式,分别是[]和at

//=====数据访问=====
reference operator[](size_t pos)
{
	assert(pos >= 0 && pos < size());
	return *(begin() + pos);
}
const_reference operator[](size_t pos) const
{
	assert(pos >= 0 && pos < size());
	return *(begin() + pos);
}

reference at(size_t pos) { return (*this)[pos]; }
const_reference at(size_t pos) const { return (*this)[pos]; }

4.2队尾元素

reference front() { return (*this)[0]; }
const_reference front() const { return (*this)[0]; }
reference back() { return (*this)[size() - 1]; }
const_reference back() const { return (*this)[size() - 1]; }

5.修改 

5.1首尾删减

void push_back(value_type val)
{
	if (size() == capacity())
		reserve(capacity() == 0 ? 4 : capacity() * 2);	//考虑容量为0的情况

	*_finish++ = val;	//在最后一个位置插入
}

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

关于尾插,还有一个类似的拼接函数 assign ,将某段区间或个 val 值,拼接至对象后面 

//=====数据修改=====
template <class InputIterator>
void assign(InputIterator first, InputIterator last)
{
	//迭代器区间拼接
	InputIterator cur = first;
	int len = 0;
	while (cur != last) ++len, ++cur;
	reserve(len);
	while (first != last) push_back(*first), ++first;
}
void assign(int n, const value_type& val)
{
	reserve(n);	//提前扩容
	while (n--) push_back(val);
}

5.2任意位置增删

iterator insert(iterator pos, const_reference val)
{
	//先记录当前迭代器的位置
	size_t sz = pos - begin();
	if (size() == capacity())
		reserve(capacity() == 0 ? 4 : capacity() * 2);	//考虑容量为0的情况

	pos = begin() + sz;	//更新迭代器
	iterator cur = end();
	while (cur != pos) *cur = *(cur - 1), --cur;
	*cur = val;	//插入数据
	++_finish;	//尾向后移动

	return pos;	//返回新迭代器位置
}
iterator insert(iterator pos, size_t n, const_reference val)
{
	while (n--) pos = insert(pos, val), pos++;	//正确写法

	return pos;
}

iterator erase(iterator pos)
{
	assert(pos >= begin() && pos < end());
	iterator cur = pos;
	while (pos != end()) *pos = *(pos + 1), ++pos;
	--_finish;
	return cur;
}
iterator erase(iterator first, iterator last)
{
	//迭代器区间删除
	//两个结束条件:last == _finish
	//while (last != _finish) *first = *(last + 1), ++first, ++last;	//错误写法
	while (last != _finish) *first = *last, ++first, ++last;	//正确写法
	_finish = first;
	return _finish;
}

迭代器区间删除时,区间为左闭右开,在进行数据覆盖时,需要写成 *first = *last 而非 *first = *(last + 1),这样会导致删除出现问题

5.3迭代器失效

insert可能会导致迭代器失效

这是因为当插入数据需要扩容时,返回的pos位置还是原来的那块地址,但是扩容后插入的位置已经发生了变化,所以会导致迭代器失效。

为了解决这个问题,迭代器要返回插入后的位置

 

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

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

相关文章

使用jmeter做性能测试实践过程中需要注意什么

前言 在驾驭Apache JMeter进行性能测试之旅中&#xff0c;深刻理解其特性和限制是至关重要的。以下是提升JMeter效能的关键策略&#xff0c;旨在挖掘其潜力&#xff0c;克服局限&#xff0c;实现精准测试。 1.精确调控线程数 推荐阈值&#xff1a;将线程数控制在300以内&…

模拟视频推到WVP推流列表

效果 1. wvp创建RTMP 2. 使用ffmpeg将本地的视频转为rtmp ffmpeg -re -i F:rtsp\123.mp4 -c copy -f flv rtmp://192.168.1.237:1935/cd/10001?sign=Z4Y3eYeSg

HTML5之canvas绘图

介绍 <canvas> 是 HTML5 引入的一个强大元素&#xff0c;允许直接在网页上进行矢量图形和位图图像的绘制&#xff0c;为网页提供了一个动态图形渲染的平台。这一特性极大丰富了网页的表现力&#xff0c;特别是在数据可视化、游戏开发、交互式图表和动画制作等领域发挥着…

第311题| 超好用!二重积分求旋转体体积公式|武忠祥老师每日一题

第一步&#xff1a; &#xff08;1&#xff09;找渐近线&#xff0c;先看水平渐近线&#xff0c;看x趋于无穷时&#xff0c;y有没有趋于一个有限的值。 , 得出水平渐近线y1。因为左右两边都是水平渐近线&#xff0c;所以没有斜渐近线。 第二步&#xff1a; 画出图像&#…

Spring Boot环境下的学生读书笔记共享

第4章 系统设计 4.1系统结构设计 读书笔记共享平台的设计主要是为了满足用户的实际需求。 因此&#xff0c;它需要通过Internet实现&#xff0c;因此它必须具备硬件和软件基础。该平台最终可以通过科学技术和各种方式达到支持智能化的信息管理的目的。因此&#xff0c;它必须具…

深入理解Python中的生成器:高效迭代与延迟计算的艺术

在处理大量数据时&#xff0c;如何有效地管理内存成为了一个关键问题。Python中的生成器&#xff08;Generator&#xff09;提供了一种优雅的解决方案&#xff0c;它允许你在迭代过程中按需生成数据&#xff0c;而不是一次性加载所有数据到内存中。本文将详细探讨生成器的工作原…

看Threejs好玩示例,学习创新与技术(三)

本文接上篇内容&#xff0c;继续挖掘应用ThreeJS的一些创新算法。 1、获得鼠标移动对应的地理位置 这个算法如果放在几年前&#xff0c;那肯定会难倒一帮人的。因为是三维投影涉及矩阵变换及求逆&#xff0c;而且还是投影模式下的。在Project Texture这个示例中&#xff0c;作…

Sentaurus TCAD的sdevice求解中选择Math求解方法

目录 并行迭代线性求解器&#xff08;ILS&#xff09;并行超节点直接求解器&#xff08;ParDiSo&#xff09;超节点直接求解器&#xff08;Super&#xff09;详细解释1. 并行迭代线性求解器&#xff08;ILS&#xff09;2. 并行超节点直接求解器&#xff08;ParDiSo&#xff09;…

windows安装docker、elasticsearch、kibana、cerebro、logstash

文章目录 1. 安装docker1.1. 两大要点1.1.1. 安装启用hyper-v电脑不存在hyper-v的情况 1.1.2. 下载安装docker 2. 在docker里面安装elasticSearch&#xff0c;kibana&#xff0c;cerebro3. 安装logstash-将数据导入到elasticSearch3.1 安装logstash3.1.1 注意事项3.1.1.1. 等了…

vue3中把封装svg图标为全局组件

在vue3中我们使用svg图标是下面这样子的 <svg style="width:30px;height:30px;"><use xlink:href="#icon-phone" fill="red"></use></svg>第次使用图标都要写这么多重复的代码,很不方便,所以,如果我们把它封装成全局…

“拍照赚钱”的任务定价(2017数学建模国赛b题)

文章目录 题目说明解题思路第一问第二问第三问第四问 部分结果图项目地址 题目 赛题地址 说明 数模国赛前的练手题。其实我个人感觉这道题很散&#xff0c;都是找一些规律进行总结统计&#xff0c;最多结合一些机器学习算法进行预测拟合之类的我刚开始用matlab&#xff0c;后…

[进阶]面向对象之 包 final

文章目录 包什么是包包名的规则:什么时候需要导包 final常量 包 什么是包 包就是文件夹。用来管理各种不同功能的Java类&#xff0c;方便后期代码维护。 包名的规则: 公司域名反写包的作用&#xff0c;需要全部英文小写&#xff0c;见名知意。使用其他类时&#xff0c;需要…

基于R语言的统计分析基础:使用键盘输入数据

在R语言中&#xff0c;键盘输入数据是一种灵活且直接的数据获取方式&#xff0c;适用于处理小数据集或需要即时用户交互的场景。通常用于交互式数据探索和分析、临时数据处理、交互式图形绘制、脚本自动化中的用户交互、特定应用场景下的数据录入中。 比如利用readline()函数根…

分享一些智慧农业数据集

持续更新中》》》 1.葡萄叶片病虫害数据集 数据集信息&#xff1a;yolo格式&#xff0c;适用于直接训练YOLO目标检测模型(yolo5 yolo8 yolo9 yolo10等)。数据集是已经标注好。训练集验证集已划分好&#xff0c;包含类别标签yaml文件&#xff0c;数据集可直接用于模型训练&…

第十二周:机器学习笔记

第十二周周报 摘要Abstract机器学习1. Recurrent Neural Network&#xff08;下&#xff09;1.1 RNN的Loss Function怎么求&#xff1f;1.2 RNN奇怪的特性1.3 如何解决 RNN 梯度消失或者爆炸1.4 RNN 其他应用 Pytorch学习1. 现有的网络模型使用以及其修改1.1 在VGG16模型添加Mo…

AD原理图编译

AD原理图检查项包括&#xff1a; 1.位号重复 2.网络悬浮 3.电源悬浮 4.单端网络 网络悬浮&#xff0c;在多页原理图时会比较明显,大部分是编译范围不是全部原理图&#xff0c;导致出现该情况&#xff0c;解决方法就是修改编译范围&#xff0c;将网络识别符范围改为全部即可。…

Qt常用控件——QComboBox

文章目录 核心属性、方法、信号模拟点餐文件加载 核心属性、方法、信号 QComboBox表示下拉框 核心属性&#xff1a; 属性说明currentText当前选中文本currentIndex当前选中的条目下标editable是否允许修改设置为true时&#xff0c;QComboBox的行为就非常接近于QLineEdit&…

Python | Leetcode Python题解之第406题根据身高重建队列

题目&#xff1a; 题解&#xff1a; class Solution:def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:people.sort(keylambda x: (-x[0], x[1]))n len(people)ans list()for person in people:ans[person[1]:person[1]] [person]return ans

SpringCloudAlibaba:Seata

1. 面试题 2.1 你简历上写用微服务boot/cloud做过项目&#xff0c;你不可能只有一个数据库吧&#xff1f;谈谈多个数据库之间如何处理分布式事务&#xff1f; 2.2 阿里巴巴的Seata-AT模式如何做到对业务的无侵入&#xff1f; 在一阶段&#xff0c;Seata 会拦截“业务 SQL”&a…

Python版《天天酷跑+源码》,详细讲解,手把手教学-python游戏开发

天天酷跑游戏 游戏效果: 游戏主要是躲避障碍物&#xff0c;这里也添加了金币&#xff0c;增加一点积分的娱乐性&#xff0c;人物设置是三条命&#xff0c;障碍物有6种&#xff0c;包括金币&#xff0c;障碍物随机生成&#xff0c;碰到障碍物掉一滴血&#xff0c;没血了结束游戏…