【vector的模拟实现】

news2024/12/23 13:54:31

目录

1 类的成员变量

2 常用成员函数和迭代器

3 增删查改

3.1 reverse

3.2 push_back

3.3 resize

3.4 insert && erase

4 默认成员函数

4.1 构造函数

4.2 拷贝构造

4.3 赋值运算符重载

4.4 析构函数


前面我们详细介绍了string类的使用,vector的使用与string相差不大,大家可以自己到官网上查询:vector的使用

(注意下面的实现是参考stl SGI版本3.0)

1 类的成员变量

namespace grm
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
    private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

 这里面与之前我们实现的顺序表有所不同的是里面的成员变量全是指针,_start是开始数据的地址,_finish是有效数据下一位的地址,_endofstorage是数据最大容量的下一位地址。


 2 常用成员函数和迭代器

        
        size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _endofstorage - _start;
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}
		const T& operator[](size_t i)
		{
			return _finish[i];
		}
		T& operator[](size_t i)const
		{
			return _finish[i];
		}

这些都很简单,就不在多说了。


3 增删查改

3.1 reverse

大家看看下面这种写法有没有问题?

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}
				
				_start = tmp;
				_finish = _start + size();
				_endofstorage = _start + n;
			}
		}

大家回顾一下size()我们是用_finish - _start实现的,这样的话使用size()的时候_finish还没有更新,而_start已经被更新了,那不就g了吗,正确的处理方式有两种,一种是先更新_start,另外一种是先保存size()。

方法1:

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}
				
				//写法1:
				_finish = tmp + size();
				_start = tmp;
				_endofstorage = _start + n;
			}
		}

 写法2:

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t sz = size();//先保存size()
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}
				
				//写法2:
				_start = tmp;
				_finish = tmp + sz;
				_endofstorage = _start + n;
			}
		}

 这样无论先更新还是后更新都可。

大家仔细看看上面这种写法还有没有问题?

顺便问一句,这里能够用memcpy吗?

如果T是内置类型的话那还好没啥问题,那如果T是一个自定义类型就会出大问题,因为memcpy进行的是浅拷贝,那析构的话就又会将同一块空间析构两次而出错。

正确的做法是一个一个赋值拷贝:

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t sz = size();
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//浅拷贝,如果是自定义类型就会发生重复析构
					for (int i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				//错误写法
				/*_start = tmp;
				_finish = _start + size();//使用size()时_finish还没有更新
				_endofstorage = _start + n;*/

				//写法1:
				_finish = tmp + size();
				_start = tmp;
				_endofstorage = _start + n;

				//写法2:
				/*_start = tmp;
				_finish = tmp + sz;
				_endofstorage = _start + n;*/
			}
		}
结论:如果对象中涉及到资源管理时,千万不能使用 memcpy 进行对象之间的拷贝,因为 memcpy 是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

3.2 push_back

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

3.3 resize

        void resize(size_t n, const T& x = T())//用T类型创造出来的匿名对象
		{
			if (n > capacity())
			{
				reserve(n);
			}
			while (_finish != _endofstorage)
			{
				*_finish = x;
				_finish++;
			}
		}

3.4 insert && erase

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

			if (_finish >= _endofstorage)
			{
				//扩容了要更新pos
				size_t gap = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + gap;
			}
			iterator end = _finish ;
			while (pos < end)
			{
				*end = *(end - 1);
				--end;
			}
			*pos = val;
			++_finish;
			return pos;
		}

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

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

大家想想为啥扩容了要更新pos?假如不更新pos,当扩容了后pos的值肯定会发生改变,那么我们就不能够使用原先的pos来访问数据了,否则就非法越界了。这也是迭代器失效的问题。大家想想string会迭代器失效吗?答案也是会的,不过由于string我们常用的是下标访问所以一般来说不会失效。

插入可以用返回值接受,为啥不用引用呢?

有些场景下可能不适用:

        //有些场景下不适用,像下面这里:
		v.insert(v.begin(), -1);//这里v.begin()是用一个具有常属性的临时变量保存的,不能够修改,与形参类型矛盾了。
		for (auto e : v)
		{
			cout << e << " ";
		}

 大家再思考一下下面这段程序:

    void test5()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(2);
		v.push_back(5);
		//删除所有为2的数字
		vector<int>::iterator it = find(v.begin(), v.end(), 2);
		while (it != v.end())
		{
			//vector<int>::iterator ret = find(it, v.end(), 2);
			auto ret = find(it, v.end(), 2);
			if (ret != v.end())
			{
				it =ret;
				v.erase(it);
			}
			++it;
		}
		for (auto& e : v)    cout << e << " ";
    }

这种使用方法肯定是错误的,因为it在不断的往下面走,正确应该修改为:

            if (ret != v.end())
			{
				it =ret;
				v.erase(it);
			}
			else
			{
				++it;
			}


4 默认成员函数

4.1 构造函数

           vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

4.2 拷贝构造

传统写法:

        vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
		}

这里也是一样,正确的做法是一个一个赋值拷贝:

//传统写法
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T) * v.size());//这里也是浅拷贝
			for (int i = 0; i < v.size(); i++)
				_start[i] = v._start[i];
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
		}

我们知道现代写法就是不用我们自己造轮子,通过构造函数来帮助我们实现,但是我们又发现是无法用我们刚才实现的构造函数来完成tmp对象的创建的,所以我们又得重新再写一个构造函数:

        template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

这里的InputIterator取名C++是有默认规定的,迭代器分类:

input_iterator(只写)  output_iterator(只读)没有实际对应的类型
forward_iterator(单向)<forward_list> <unordered_map> <unordered_set>
bidirectional_iterator<list> <map> <set>
randomaccess_iterator<vector> <deque> <string>

有了这个构造函数后就能够用现代写法了:

        //现代写法
		//void swap(vector& v)//可以这么写,但不推荐,可读性不高
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		vector(const vector<T>& v)
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> tmp(v.begin(),v.end());
			swap(tmp);
		}

4.3 赋值运算符重载

传统写法:

        //传统写法
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				T* tmp = new T[v.capacity()];
				//memcpy(tmp, v._start, sizeof(T) * v.size());浅拷贝
				for (int i = 0; i < v.size(); i++)
					tmp[i] = v._start[i];
				delete[] _start;
				_start = tmp;
				_finish = _start + v.size();
				_endofstorage = _start + v.capacity();
			}
			return *this;
		}

现代写法:

        //现代写法1:
		vector<T>& operator=(const vector<T>& v)
		{
			vector<T> tmp(v);
			swap(tmp);
			return *this;
		}

		//现代写法2:
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

这里与之前string的类似。

4.4 析构函数

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

有需要的老铁可以在博主的码云中获取源码:https://gitee.com/monday-sky/text_cpp/commit/6521a7399bdf5ebccf028e5fa130cccbd57d4dcchttps://gitee.com/monday-sky/text_cpp/commit/6521a7399bdf5ebccf028e5fa130cccbd57d4dcc

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

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

相关文章

关于 JSON 数据格式的完全使用指南

前言 本文将对 JSON 的语法、解析、序列化进行详细的说明&#xff0c;帮助大家掌握 JSON 的使用方式。 如果文中有不对、疑惑或者错字的地方&#xff0c;欢迎在评论区留言指正&#x1f33b; 一、JSON简介 在 JSON 之前&#xff0c;XML 曾经一度成为互联网上传输数据的事实标…

【论文阅读】【剪枝】Learning Efficient Convolutional Networks through Network Slimming

摘要 深度卷积神经网络&#xff08;CNN&#xff09;在许多实际应用中的部署在很大程度上受到其高计算成本的阻碍。在本文中&#xff0c;我们提出了一种新的神经网络学习方案&#xff0c;以同时1&#xff09;减小模型大小&#xff1b;2&#xff09; 减少运行时内存占用&…

Kali Linux- 社会工程及压力工具教程

在本章中&#xff0c;我们将了解 Kali Linux 中使用的社会工程工具。 文章目录社会工程Kali Linux - 压力工具SlowhttptestinvitefloodTHC-SSL-DOS总结社会工程 社会工程师工具包 &#xff08;SET&#xff09; 是一个专为社会工程设计的开源渗透测试框架。SET具有许多自定义攻…

第九章(12):STL之常用查找算法

文章目录前情回顾常用查找算法findfind_ifadjacent_findbinary_searchcountcount_if下一座石碑&#x1f389;welcome&#x1f389; ✒️博主介绍&#xff1a;一名大一的智能制造专业学生&#xff0c;在学习C/C的路上会越走越远&#xff0c;后面不定期更新有关C/C语法&#xff0…

读懂用户之用户调研怎么做?(内附模板教程)

随着互联网的发展&#xff0c;不管是做产品设计、运营&#xff0c;还是市场推广&#xff0c;我们都需要思考的是“用户真正想要的是什么&#xff1f;”。这时候&#xff0c;用户调研的重要性就凸显出来了。 一、什么是用户调研 用户调研&#xff0c;指通过各种方式得到受访者的…

Redis常用指令

3. 常用指令 在这部分中呢&#xff0c;我们家学习两个知识&#xff0c;第一个是key的常用指令&#xff0c;第二个是数据库的常用指令。和前面我们学数据类型做一下区分&#xff0c;前面你学的那些指令呢&#xff0c;都是针对某一个数据类型操作的&#xff0c;现在学的都是对所…

#14环形链表#

环形链表 1题目链接 链接 2思路 slow和fast指向链表的开始 slow一次走一步 fast一次走两步 不带环 fast就会为空 带环 fast就会在环里追上slow 3实现 bool hasCycle(struct ListNode* head) {struct ListNode* slow head, * fast head;while (fast && fast->ne…

微信小程序学习第3天——网络数据请求

一、小程序网络请求限制 1、必须https类型的接口 2、必须将接口的域名添加到信任列表中 二、配置request合法域名 配置步骤&#xff1a;登录微信小程序管理后台 -> 开发 -> 开发设置 -> 服务器域名 -> 修改 request 合法域名 点击修改request合法域名&#xf…

【自动化测试】从0开始玩转docker—— 01软件安装

目的 CI / CD在目前各类互联网企业中已然成为推动软件开发行为的重要基础设施服务。同样的对于测试团队来说更是有着举足轻重的重大意义&#xff0c;无论是测试左移的具象化提现亦或是持续测试的顺利开展&#xff0c;掌握这一技能已是广大软件测试工程师的必修课。分享这一技术…

Springboot+Vue+Uniapp自媒体视频系统

简介&#xff1a;本项目采用了基本的springbootvueuniapp设计的自媒体系统。详情请看主要截图。经测试&#xff0c;本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 项目描述 项目名称SpringbootVueUniapp自媒体视频系统源码作者LHL项目类型Java EE项目 …

C++vector容器

目录 1.vector基本概念 2.构造函数 3.vector赋值操作&#xff0c;&#xff0c;assign 4.vector容量和大小 ,empty,capacity,size,resize 5.vector的插入和删除&#xff0c;push_back,pop_back,insert,erase,clear 6.vector数据存取&#xff0c;at,[],遍历 7.vector互换…

C++之引用(上)

文章目录前言一、引用二、引用的写法三、引用特性1.引用在定义时必须初始化2. 一个变量可以有多个引用3. 引用一旦引用一个实体&#xff0c;再不能引用其他实体三、引用的权限&#xff08;含例子&#xff09;四、常引用总结前言 今天要介绍的是C中的一个新概念——引用。 一、…

MobaXterm使用指南

MobaXterm使用指南 1. 介绍 通俗的来讲&#xff0c;MobaXterm就是一款SSH客户端&#xff0c;它帮助我们在Windows操作系统下去连接并操作Linux服务器。MobaXterm 又名 MobaXVT&#xff0c;是一款增强型终端、X 服务器和 Unix 命令集(GNU/ Cygwin)工具箱。MobaXterm 可以开启多…

Django网页+Yolov5垃圾识别系统

Django网页Yolov5垃圾识别系统如需安装运行环境或远程调试&#xff0c;见文章底部个人微信或QQ名片&#xff0c;由专业技术人员远程协助&#xff01;前言这篇博客针对<<Django网页Yolov5垃圾识别系统>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易…

代码随想录NO33 |Leetcode 860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球

贪心LeetCode_860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球今天是贪心第四天的题了&#xff0c;快开始dp了&#xff01;大头啊&#xff01; 860.柠檬水找零 在柠檬水摊上&#xff0c;每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品&#xff0c;&am…

day28|1005.K次取反后最大化的数组和、134. 加油站、135. 分发糖果

1005.K次取反后最大化的数组和 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数组 可能的…

若依框架实现多级联动下拉

最近也是用到了若依的多级联动&#xff0c;效果如下&#xff08;可多级&#xff09; 若依有已经封装好的一套js&#xff0c;难点在于后端数据封装&#xff0c;前端按照封装好的代码引用即可。 这里主要分享下关于后端数据如何封装多层。后端代码直接奉上。 记得要把集合转j…

SpringBoot+Vue+Wx新冠疫苗预约系统

简介&#xff1a;本项目采用了基本的springbootvue微信小程序设计的新冠疫苗预约系统。详情请看主要截图。经测试&#xff0c;本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 项目描述 项目名称SpringBootVueWx新冠疫苗预约系统源码作者LHL项目类型Java…

python爬虫正则表达式

博主简介&#xff1a;博主是一个大二学生&#xff0c;主攻人工智能领域研究。感谢缘分让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;C&#xff0c;python&#xff0c;爬虫等方面的知识分享。如果有需要的小伙伴&#xff0c;可以关注博主&#xff…

零碎的算法笔记(1)

From算法竞赛入门经典 第2版1.判断 n 是否为完全平方数2. 比较大的数组应尽量声明在 main 函数外&#xff0c;否则程序可能无法运行 3.开灯问题1.判断 n 是否为完全平方数 可以先求出其平方根&#xff0c;然后看它是否为整数&#xff0c;即用一个 int 型变量 m 存储 sqrt(n&am…