C++初阶 | [八] (下) vector 模拟实现

摘要:vector 模拟实现讲解(附代码示例),隐藏的浅拷贝,迭代器失效

在进行 vector 的模拟实现之前,我们先粗略浏览一下 stl_vector.h 文件中的源码来确定模拟实现的大体框架。


这里提供一些粗略浏览源码的技巧:
1.不要一行一行地看

2.先不要研究细节,先看整体的框架(类:成员变量+成员函数)

3.理解的时候连蒙带猜,再验证自己的猜测

ps.在已经模拟实现过 string类的前提下,vector 的模拟实现的讲解在一些非必要的地方不多赘述,直接给出代码示例。

框架:👇

template<class T>
class vector
{
private:
	iterator _start = nullptr; // 指向数据块的开始
	iterator _finish = nullptr; // 指向有效数据的尾(指向最后一个有效数据的下一个)
	iterator _endOfStorage = nullptr; // 指向存储容量的尾
};

首先,同 string 类的匿名实现一样,我们先创建一个自己的命名空间,将 vector 的模拟实现在这个自定义的命名空间中以区分库中的vector。

1. Constructor and Destructor

不多赘述。示例如下。

#pragma once

namespace Bottle
{
	template<class T>
	class vector
	{
	public:

		// construct and destroy

		vector()
		{}


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


	private:
		iterator _start; // 指向数据块的开始
		iterator _finish; // 指向有效数据的尾(指向最后一个有效数据的下一个)
		iterator _endOfStorage; // 指向存储容量的尾
        
	};

}

2. push_back

(1)这里需要顺便实现 size_t capacity()函数 size_t size()函数

(2)思路:检查容量 → 插入数据
(注意:检查容量时,如果是遵循两倍扩容的思路,就需要注意容量为0的情况,如果此时直接按两倍扩容会导致错误)

代码示例:

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

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

//push_back
		void push_back(const T& x)
		{
			if (_finish == _endOfStorage)
			{
				//check capacity
				size_t new_capacity = capacity() == 0 ? 4 : (capacity() * 2);
				reserve(new_capacity);
			}
			*(_finish) = x;//push back
			++_finish;
		}

3. reserve

思路:开新空间 → 拷贝数据到新空间 → 指向新空间(ps.原则上只扩容不缩容)

  • 关于指向新空间这个操作需要注意的问题:不同于 string类 size数据是由成员变量存储起来的,vector size是通过算两个指针之间的偏移量得出的。
    如图所示,当 _start 发生改变之后_finish ≠ _start + size(),而应该提前记录下 _finish 相对于 _start 的偏移量。

代码示例: 

//reserve	
        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n + 1];//new
				size_t sz = size();

				memcpy(tmp, _start, sizeof(T) * size());//copy

				delete[]_start;
				_start = tmp;
				_finish = tmp + sz;
				_endOfStorage = tmp + n;
				tmp = nullptr;
			}
		}

4. Access

1)operator[]

代码示例:

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

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

2)iterator

(迭代器实现之后就可以使用范围for了)

代码示例:

	public:

		// Vector的迭代器是一个原生指针

		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}


		const_iterator cbegin()const
		{
			return _start;
		}

		const_iterator cend()const
		{
			return _finish;
		}

5. resize

有模板(template)之后,C++对内置类型进行了升级,即一切都是对象,以前在C语言里面只有变量,而在之后的C++中是变量,也是对象。
e.g. int vi = int(2);//该语句可看作调用默认构造函数用 ‘2’ 来构造一个 int 类型的对象 vi . (内置类型也会调用构造)

代码示例:

//resize
		void resize(size_t n, const T& value = T())
		{
			if (n <= size())
			{
				//delete
				_finish = _start + n;
			}
			else
			{
				reserve(n);//check capacity

				size_t len = n - size();
				while (len--)
				{
					*_finish = value;
					++_finish;//改变finish的指向就是改变size的大小
				}

			}
		}

6. insert

iterator insert(iterator pos, const T& x){……}

  • string 模拟实现 insert 

    思路:①检查 pos 位置的有效性;②检查容量;③挪动数据;④插入数据(strncpy)。

    特殊情况:头插(pos==0)

  • vector
    思路:①检查 pos 位置的有效性;②检查容量;③挪动数据;④插入数据。

    特殊情况:头插(pos==0)

  • 区别
    string中的挪动数据时比较的是下标和下标,即 size_t 数据类型的比较,头插时比较特殊,下标不断变小的过程中可能会出现“-1>0” 的情况(详情见string类模拟实现的文章)
    vector中挪动数据时比较的是迭代器和迭代器,即 T* (因为这里迭代器的底层实现用的是原生指针)数据类型的比较,所以这里头插不属于特殊情况。但是,这也由此引发了另外的问题——迭代器失效

迭代器失效

iterator pos 底层是指针。reserve 扩容之后原空间被释放,而 pos 还指向原空间。所以我们需要获取 pos 相对于 _start 的偏移量。

② iterator pos 是传值传参。如下图所示。(提醒💡:这里也不适合用 传引用传参 (iterator& pos),要引用只能是 const 引用 (const iterator& pos),因为在类似 v.insert(v.begin(),数据) 的函数调用中,begin()函数是传值返回,则它的返回值具有常属性,只能用 const 引用,但 const 引用会使 pos 无法被修改,则对于下图所示的迭代器是没有意义的)

代码示例:

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

			size_t len = pos - _start;//偏移量

			//check capacity
			if (_finish == _endOfStorage)
			{
				reserve(capacity() == 0 ? 4 : (capacity() * 2));
				pos = _start + len;//reserve之后更新pos
			}

			for (iterator end = _finish; end > pos; --end)
			{
				*end = *(end - 1);//move data
			}

			*pos = x;//pos位置insert data
			++_finish;

			return pos;
		}

7. erase

思路:依次挪动数据覆盖

注意:erase 同样会导致迭代器失效

  • 关于erase导致的迭代器失效:

    如下图,删除数据中偶数的行为出错是因为 erase 导致的迭代器失效,删除符合条件的元素之后,数据的内容发生了改变,就导致原本指向该元素(被删除的这个元素)的迭代器的指向是未知的,当我们在 erase 之后再去使用这个迭代器,行为也是位置的。

    库里面的做法是 erase 函数会返回要删除的指定 pos 位置的元素的下一个元素的位置。
     

代码示例:

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

			//move data
			for (iterator it = pos + 1; it < _finish; ++it)
			{
				*(it - 1) = *(it);
			}

			--_finish;
			return pos;
		}

8. 隐藏的浅拷贝_reserve

memcpy:隐藏的浅拷贝 ( from reserve)(如下图所示,tip.如果这里运用 引用计数的浅拷贝就会很高效)

由上图可知,delete 释放空间,对于自定义类型 string 会自动调用析构函数,导致新空间指向已经被析构掉的空间。因此,拷贝数据最好通过赋值操作来实现,赋值会自动调用拷贝构造进行深拷贝

代码示例:

//reserve
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n + 1];//new
				size_t sz = size();
				for (size_t i = 0; i < sz; ++i)//copy
				{
					tmp[i] = *(_start + i);
				}

				//memcpy(tmp, _start, sizeof(T) * size());//copy

				delete[]_start;
				_start = tmp;
				_finish = tmp + sz;
				_endOfStorage = tmp + n;
				tmp = nullptr;
			}
		}

9. Copy Constructor

思路:
1)初始化成员变量
2)reserve
3)拷贝数据(push_back

代码示例:

//copy constructor
        vector(const vector<T>& v)//copy constructor
			:_start(nullptr)
			,_finish(nullptr)
			,_endOfStorage(nullptr)
		{
			reserve(v.capacity());
			for (size_t i = 0; i < v.size(); ++i)
			{
				push_back(v[i]);
			}
		}

10. 赋值重载

现代写法同 string类 的模拟实现,详情见 string类 模拟实习的文章。

代码示例:

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


//赋值重载
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

11. 其他构造函数重载

注意:自己写了构造函数之后,编译器不会在生成再生成默认构造,所以在构造函数中必须要有一个默认构造函数

1)template <class InputIterator>
  vector (InputIterator first, InputIterator last);
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			size_t len = last - first;
			reserve(len);
			for (size_t i = 0; i < len; ++i)
			{
				*(_start + i) = *(first + i);
			}
			_finish = _start + len;
			_endOfStorage = _finish;
		}
2)vector (size_t n, const T& val = T())

T 为 int 类型时,会出现类型匹配的问题,如下图:因此,对于 int 类型需要专门实现的一个函数重载。

		vector(size_t n, const T& value = T())
		{
			reserve(n);
			while (n--)
			{
				push_back(value);
			}
		}

		vector(int n, const T& value = T())
		{
			reserve(n);
			while (n--)
			{
				push_back(value);
			}
		}

 

ps.默认构造函数:

		vector()
		{}

完整代码链接:My_vector/My_vector/My_vector.h · fantansy-13-07/Cpp - 码云 - 开源中国 (gitee.com)


END

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用GAP-Burp-Extension扫描潜在的参数和节点

关于GAP-Burp-Extension GAP-Burp-Extension是一款功能强大的Burp扩展&#xff0c;该工具在getAllParams扩展的基础上进行了升级&#xff0c;该工具不仅可以帮助广大研究人员在安全审计过程中扫描潜在的参数&#xff0c;而且还可以搜索潜在的链接并使用这些参数进行测试&#…

HarmonyOS—代码Code Linter检查

Code Linter代码检查 Code-Linter针对ArkTS/TS代码进行最佳实践、编程规范方面的检查&#xff0c;目前还会检查ArkTS语法规则。开发者可根据扫描结果中告警提示手工修复代码缺陷&#xff0c;或者执行一键式自动修复&#xff0c;在代码开发阶段&#xff0c;确保代码质量。 检查…

Linux之项目部署与发布

目录 一、Nginx配置安装&#xff08;自启动&#xff09; 1.一键安装4个依赖 2. 下载并解压安装包 3. 安装Nginx 4. 启动 nginx 服务 5. 对外开放端口 6. 配置开机自启动 7.修改/etc/rc.d/rc.local的权限 二、后端部署tomcat负载均衡 1. 准备2个tomcat 2. 修改端口 3…

【Vue3】学习watch监视:深入了解Vue3响应式系统的核心功能(上)

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

基于Java的艺培管理解决方案

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

抖音小程序获取手机号

1、* 手机号获取和登录需要分开 &#xff08;规定&#xff09; 2、 抖音小程序首先得先通过试运营 没有通过试运营的 会提示没有权限 getPhoneNumber:fail auth deny 3、上代码 <button class"phone toutiaoSq" v-if"!userInfo.phone && isLogin&…

[AutoSar]BSW_Com03 DBC详解 (一)

目录 关键词平台说明一、DBC 定义1.1 相关工具 二、主要组成部分介绍2.1 Networks2.2 ECUs2.3 Network nodes2.4 messages2.5 signal2.6 Value Tables 三、主要组成部分关系图 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueOSautosar OSautosar厂商vector &am…

本地项目如何上传到gitee

文章目录 一、在gitee上新建远程仓库二、初始化本地仓库三、执行git命令上传代码 一、在gitee上新建远程仓库 仓库名称必填&#xff0c;路径自动跟仓库名称保持一致 解释说明&#xff1a; 仓库名称&#xff1a;必填&#xff0c;每个仓库都需要有一个名称&#xff0c;同一个码…

tkinterFrame框架+标签框架LabelFrame+Toplevel窗口的使用

1.在tkinter中&#xff0c;Frame是一个容器小部件用于组织和管理其他小部件。它可以作为一个独立的可见区域&#xff0c;也可以作为其他小部件的父容器。 import tkinter as tk import tkinter.ttk as ttk import tkinter.messagebox as mbm tk.Tk() m.title("tkinter L…

Vue事件处理之v-on

1. 使用及定义 定义方法 function 方法名称(接受的event或是什么都不写) {//不管方法后括号内写与不写event,都可以接受到方法内表达式 }//定义一个接受参数的方法,此时也会在传入event function 方法名称(传入参数) {//可接受传入参数与event方法内表达式 } //定义一个接受参…

postgresql远程连接问题

修改pg_hba.conf文件&#xff0c;在文件末尾追加 host all all 0.0.0.0/0 md5

查看mysql数据库的版本

要查看MySQL数据库的版本&#xff0c;可以使用以下几种方法&#xff1a; 命令行&#xff08;已连接到MySQL服务器&#xff09;&#xff1a; 登录到MySQL服务器后&#xff0c;在MySQL提示符下执行&#xff1a; mysql> SELECT VERSION(); 或者&#xff0c;也可以执行 STATUS; …

啤酒:精酿啤酒与烧烤的热烈碰撞

在夏日的傍晚&#xff0c;烧烤与啤酒总是绝配。当Fendi Club啤酒遇上烧烤&#xff0c;它们将为我们带来一场热烈的美味碰撞。 Fendi Club啤酒&#xff0c;以其醇厚的口感和淡淡的麦芽香气而著称。这款啤酒在酿造过程中采用了特别的工艺&#xff0c;使得酒体呈现出诱人的金黄色&…

《业务建模驱动的企业架构转型白皮书》

当前&#xff0c;我国金融等国民经济重点行业和企业的数字化转型&#xff0c;仍存在战略落地难、业务技术协同难以及投入产出匹配难等问题&#xff0c;亟需通过实施企业架构&#xff0c;从顶层设计出发&#xff0c;制定符合自身需要的转型战略&#xff1b;从全局视角出发&#…

【JavaSE】集合框架

目录 程序场景分析 Java集合框架包含的内容List接口ArrayListLinkedListList接口的常用方法ArrayList案例背景分析代码示例扩展以下功能代码示例 LinkedList案例背景分析代码示例LinkedList的特殊方法 ArrayList与LinkedList对比 Set接口HashSet 集合的特点常用方法案例背景分析…

日志系统项目(2)项目实现(实用工具类、日志等级类、日志消息类、日志格式化输出类)

前面的文章中我们讲述了日志系统项目的前置知识点&#xff0c;再本文中我们将开始日志项目的细节实现。 日志系统框架设计 本项目实现的是一个多日志器日志系统&#xff0c;主要实现的功能是让程序员能够轻松的将程序运行日志信息落地到指定的位置&#xff0c;且支持同步与异…

成都直播基地作为产业重要载体,引领直播行业健康、多元发展

近年来&#xff0c;我国网络直播行业呈现出井喷式的发展态势。众多直播平台如雨后春笋般涌现&#xff0c;直播内容丰富多样&#xff0c;涵盖游戏、电竞、美食、旅游、教育等多个领域。同时&#xff0c;成都直播产业园规模持续扩大&#xff0c;产业不断完善&#xff0c;整体呈现…

常见的音频与视频格式

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

蓝桥杯算法 一.

分析&#xff1a; 本题记录&#xff1a;m个数&#xff0c;异或运算和为0&#xff0c;则相加为偶数&#xff0c;后手获胜。 分析&#xff1a; 369*99<36500&#xff0c;369*100>36500。 注意&#xff1a;前缀和和后缀和问题

TABR: TABULAR DEEP LEARNING MEETS NEAREST NEIGHBORS IN 2023 阅读笔记

TABR: TABULAR DEEP LEARNING MEETS NEAREST NEIGHBORS IN 2023 论文地址&#xff1a;https://arxiv.org/abs/2307.14338 源代码&#xff1a;https://github.com/yandex-research/tabular-dl-tabr 摘要 针对表格数据问题&#xff08;例如分类、回归&#xff09;的深度学习&a…
最新文章