【C++练级之路】【Lv.7】【STL】vector类的模拟实现

news2024/12/25 10:30:31



快乐的流畅:个人主页


个人专栏:《C语言》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、成员变量
  • 二、默认成员函数
    • 2.1 constructor
    • 2.2 destructor
    • 2.3 copy constructor
    • 2.4 operator=
  • 三、迭代器
    • 3.1 begin
    • 3.2 end
  • 四、元素访问
    • 4.1 operator[ ]
  • 五、容量
    • 5.1 size
    • 5.2 capacity
    • 5.3 reserve
    • 5.4 resize
    • 5.5 empty
  • 六、修改
    • 6.1 push_back
    • 6.2 pop_back
    • 6.3 insert
    • 6.4 erase
    • 6.5 swap
  • 总结

引言

关于STL容器的学习,我们来到了运用最广泛、最常见的vector。有了之前关于string的学习,我们对容器设计有了一个大概的了解,而今天在熟悉的基础上去探求vector相比于string有哪些异同,同时迎来更多的新挑战……

一、成员变量

vector类中包含了

  • _start(指向有效空间的头)
  • _finish(指向有效空间的尾)
  • _end_of_storage(指向可用空间的尾)

细节:

  1. 三个成员变量均迭代器(此刻即指针)
  2. 使用缺省值,不必写多份初始化列表
template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;
private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;
};

二、默认成员函数

2.1 constructor

无参构造

vector()
{}

带参构造

细节:

  1. 分别重载 size_t 和 int 类型,防止参数匹配时,匹配到迭代器区间构造,从而导致间接寻址错误
  2. 初始化的val的缺省值,是匿名构造的对象
vector(size_t n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; ++i)
	{
		_start[i] = val;
	}
	_finish = _start + n;
}

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

迭代器区间构造

细节:

  1. 使用类模板,可以传任意类型的迭代器
  2. 迭代器访问,条件最好使用不等于(!=)
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

2.2 destructor

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

2.3 copy constructor

近代写法

细节:

  1. 先开辟一维空间
  2. 再用赋值重载,进行深拷贝(不能用memcpy,它是浅拷贝)
vector(const vector<T>& x)
{
	_start = new T[x.capacity()];
	for (size_t i = 0; i < x.size(); ++i)
	{
		_start[i] = x._start[i];
	}
	_finish = _start + x.size();
	_end_of_storage = _start + x.capacity();
}

现代写法

细节:

  1. 迭代器区间构造,构造出临时对象
  2. 再使用vector中的swap,交换*this和tmp的值,完成拷贝构造
vector(const vector<T>& x)
{
	vector<T> tmp(x.begin(), x.end());
	swap(tmp);
}

2.4 operator=

近代写法

细节:大体与拷贝构造相同

vector<T>& operator=(const vector<T>& x)
{
	if (this != &x)
	{
		_start = new T[x.capacity()];
		for (size_t i = 0; i < x.size(); ++i)
		{
			_start[i] = x._start[i];
		}
		_finish = _start + x.size();
		_end_of_storage = _start + x.capacity();
	}
	return *this;
}

现代写法

细节:

  1. 传参变成传值,这样就会拷贝构造出一个临时对象
  2. 再使用vector中的swap,交换*this和tmp的值,完成赋值重载
vector<T>& operator=(vector<T> x)
{
	swap(x);
	return *this;
}

三、迭代器

3.1 begin

迭代器的实现和编译器有关,不同的编译器有不同的实现方式。这里用指针来实现迭代器

同时,重载了普通迭代器和const迭代器。

iterator begin()
{
	return _start;
}

const_iterator begin() const
{
	return _start;
}

3.2 end

迭代器遵循左闭右开的原则,begin指向首元素,end指向末元素的下一位。

iterator end()
{
	return _finish;
}

const_iterator end() const
{
	return _finish;
}

悄悄告诉你范围for的底层实现,就是运用了迭代器。

四、元素访问

4.1 operator[ ]

为了方便的访问元素,我们重载了[ ]运算符。同时,也分为普通版本和const版本,对应不同vector类的权限。

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

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

五、容量

5.1 size

获取当前有效数据个数

细节:const修饰,保证普通和const类型vector类都能访问

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

5.2 capacity

获取当前最大有效容量

细节:同上

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

看了上面size和capacity的实现,是不是就瞬间明白_start、_finish和_end_of_storage的含义了?

悄悄告诉你:其实当你不懂成员变量的含义时,可以先看看size和capacity的实现


5.3 reserve

改变当前最大容量

细节:

  1. 只扩容,不缩容
  2. 赋值重载,进行深拷贝
  3. 更新成员变量时(如果按照顺序更新),先保存size的大小,防止_finish失效。因为如果为_finish = tmp + size(),等价于_finish = tmp + _finish - _start,而_start已经更新了,所以size()计算的大小失效,最终_finish并没有更新。
void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		if (_start)
		{
			for (size_t i = 0; i < size(); ++i)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}

		size_t sz = size();
		_start = tmp;
		_finish = tmp + sz;
		_end_of_storage = tmp + n;
	}
}

5.4 resize

改变当前有效数据个数

细节:

  1. 如果n<size,则减少有效个数,如果n>size,则填充指定值,直至达到n个
  2. 运用赋值重载,实现深拷贝
void resize(size_t n, T val = T())
{
	if (n > size())
	{
		reserve(n);
		for (size_t i = size(); i < n; ++i)
		{
			_start[i] = val;
		}
	}
	_finish = _start + n;
}

5.5 empty

判断是否为空

细节:const修饰,保证普通和const类型vector类都能访问

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

六、修改

6.1 push_back

尾插

细节:需要扩容时,判断容量是否为空

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

	*_finish = val;
	++_finish;
}

6.2 pop_back

尾删

细节:断言vector不为空,才进行删除

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

6.3 insert

指定位置插入

细节:

  1. 断言判断pos的合法性
  2. 扩容前,先保存pos的相对位置,扩容后,刷新pos,防止迭代器失效
  3. 返回指向新插入元素的迭代器,防止迭代器失效
iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos <= _finish);

	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = _start + len;
	}

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

	*pos = val;
	++_finish;
	return pos;
}

6.4 erase

指定位置删除

细节:

  1. 断言判断pos的合法性
  2. 返回指向删除元素的后一位的迭代器,防止迭代器失效
iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

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

	--_finish;
	return pos;
}

上述有两种迭代器失效:

  1. 野指针
  2. 指向含义改变

关于迭代器失效,我们统一认为,进行过插入或删除操作的迭代器pos,已经失效,不能再使用。只有接收其返回值,刷新pos,才能重新使用。


6.5 swap

交换两个vector类的值

细节:使用std库中的swap函数,交换各个成员变量的值

void swap(vector<T>& x)
{
	std::swap(_start, x._start);
	std::swap(_finish, x._finish);
	std::swap(_end_of_storage, x._end_of_storage);
}

总结

我们在有了学习string的基础后,学习vector的成本降低了不少,函数名和用法大体相同。但是,我们依旧遇到了新的问题与挑战,如多层深拷贝,迭代器失效等。我与C++的故事,仍在无声地诉说……


真诚点赞,手有余香

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

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

相关文章

Vue3+TS+dhtmlx-gantt实现甘特图

实现样式 因为只做展示&#xff0c;所以实现很简单 实现功能 自定义列头增加斑马线&#xff0c;实际结束时间&#xff08;自定义实现&#xff09;自定义进度展示&#xff0c;根据层级让进度背景颜色变浅marker标记今天自定义提示框内容 实现 import { gantt } from "d…

某顺cookie逆向

目标网站:aHR0cHM6Ly9xLjEwanFrYS5jb20uY24v 这个网站是对cookie进行反爬虫的&#xff0c;可以看到cookie中有一个加密参数v 二、分析参数 可以使用hook方法&#xff0c;来hook住cookie中v生成的位置&#xff0c;可以直接在控制台中输入hook函数 (function () {use strict;v…

【详细解释深度学习图像分类检测评价指标】准确率Accuracy、精确率Precision、召回率Recall、mAP等(一文搞懂,建议收藏)

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要是记录工作中、学习中关于AI(Deep Learning)相关知识并分享。 &#x1f60a;&#x1f60a;&#x1f…

Parallels Desktop 19 mac 虚拟机软件 兼容M1 M2

Parallels Desktop 19 for Mac 是一款适用于 macOS 的虚拟机软件。无需重启即可在 Mac 上运行 Windows、Linux 等系统&#xff0c;具有速度快、操作简单且功能强大的优点。包括 30 余种实用工具&#xff0c;可简化 Mac 和 Windows 上的日常任务。 软件下载&#xff1a;Parallel…

大模型时代的计算机系统革新:更大规模、更分布式、更智能化

编者按&#xff1a;2023年是微软亚洲研究院建院25周年。借此机会&#xff0c;我们特别策划了“智启未来”系列文章&#xff0c;邀请到微软亚洲研究院不同研究领域的领军人物&#xff0c;以署名文章的形式分享他们对人工智能、计算机及其交叉学科领域的观点洞察及前沿展望。希望…

搭建网站使用花生壳的内网穿透实现公网访问

目录 一 搭建网站 二 使用花生壳进行内网穿透 1、创建内网映射 2、linux系统安装花生壳客户端 3、重新打开浏览器&#xff0c;输入http://b.oray.com&#xff0c;完成账户登录&#xff0c;激活&#xff08;SN登录&#xff09; 一 搭建网站 准备工作&#xff1a; [rootse…

主播产品对比话术

—、价格对比 主播产品A︰这款产品定价相对较高&#xff0c;但是其品质和功能都是一流的&#xff0c;对于追求高端体验的消费者来说&#xff0c;物有所值。 主播产品B∶这款产品的价格相对较低&#xff0c;性价比很高&#xff0c;对于预算有限的消费者来说&#xff0c;是个不…

基于Java SSM框架现图书馆借阅管理系统项目【项目源码+论文说明】

基于java的SSM框架实现图书馆借阅管理系统演示 摘要 以往的图书馆管理事务处理主要使用的是传统的人工管理方式&#xff0c;这种管理方式存在着管理效率低、操作流程繁琐、保密性差等缺点&#xff0c;长期的人工管理模式会产生大量的文本借书与文本数据&#xff0c;这对事务的…

控制项目风险

一、风险预算 暴雪公司经理艾莉森&#xff0c;暴雪公司是一家小型工业企业&#xff0c;该公司的高管为了降低生产成本&#xff0c;决定搬迁工厂。项目经理明白实际情况与初始计划之间常常会有很大的出入。项目经理需要事先为一些事情做好准备&#xff0c;并在项目运作或预算方面…

Vue基础-Computed-Watch

一、computed计算属性使用 1.复杂data的处理方式 我们知道&#xff0c;在模板中可以直接通过插值语法显示一些data中的数据。 但是在某些情况&#xff0c;我们可能需要对数据进行一些转化后再显示&#xff0c;或者需要将多个数据结合起来进行显示&#xff1b; 比如我们需要…

ORA-12528: TNS: 监听程序: 所有适用例程都无法建立新连

用了网上的办法&#xff1a; 1、修改listener.ora的参数,把动态的参数设置为静态的参数,红色标注部分 位置D:\oracle\product\10.2.0\db_1\NETWORK\ADMIN SID_LIST_LISTENER (SID_LIST (SID_DESC (SID_NAME PLSExtProc) (ORACLE_HOME D:\oracle\produ…

微信小程序(十)表单组件(入门)

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.type 属性指定表单类型 2.placeholder 属性指定输入框为空时的占位文字 源码&#xff1a; form.wxml <!-- 提前准备好的布局结构代码 --> <view class"register"><view class"…

【LangChain学习之旅】—(10) 用RouterChain确定客户意图

【【LangChain学习之旅】—&#xff08;10&#xff09; 用RouterChain确定客户意图 任务设定整体框架具体步骤如下&#xff1a; 具体实现构建提示信息的模板构建目标链 Reference&#xff1a;LangChain 实战课 任务设定 首先&#xff0c;还是先看一下今天要完成一个什么样的任…

Pyro —— DOP Nodes

目录 Smoke Object —— 创建smoke对象及相关场 Smoke Solver —— Smoke解算器 Color Relationships Advanced Pyro Solver —— Pyro解算器 Smoke Object (Sparse) —— 创建smoke对象及相关场 Smoke Solver (Sparse) —— Sparse Smoke解算器 Simulation Advanced …

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-1最优控制问题与性能指标 1. 最优控制问题与性能指标2. 动态规划 Dynamic Programming2.1 基本概念2.2 代码详解2.3 简单一维案例 3. 线性二次型调节器&#xff…

SourceTree修改仓库密码

1、找到 SourceTree缓存文件目录&#xff1a; passwd 目录保存账号对应的密码&#xff08;已加密&#xff09; 2、删除密码 删除passwd文件即可。重启 SourceTree 软件&#xff0c;进行操作&#xff0c;就会有输入密码的弹窗&#xff0c;输入即可。

高标准农田气象站

在当今社会&#xff0c;科技的发展正在深刻地改变着我们的生活。特别是在农业领域&#xff0c;科技的运用已经成为了保障粮食安全、提高农业生产效率的重要手段。其中&#xff0c;高标准农田气象站作为现代农业的重要组成部分&#xff0c;正在发挥着越来越重要的作用。 TH-NQ14…

【原生小程序-分包】

1.创建分包-文件夹 subPackages app.json中写入subPackges对象&#xff0c;在里面写分包路径 {"pages": ["pages/index/index"],"subPackages": [{"root": "subPackages","name": "分包A","pag…

【强化学习】QAC、A2C、A3C学习笔记

强化学习算法&#xff1a;QAC vs A2C vs A3C 引言 经典的REINFORCE算法为我们提供了一种直接优化策略的方式&#xff0c;它通过梯度上升方法来寻找最优策略。然而&#xff0c;REINFORCE算法也有其局限性&#xff0c;采样效率低、高方差、收敛性差、难以处理高维离散空间。 为…

leetcode—课程表 拓扑排序

1 题目描述 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示如果要学习课程 ai 则 必须 先学习课程 …