【C++】——vector的介绍及模拟实现

news2025/1/9 1:57:09

文章目录

  • 1. 前言
  • 2. vector的介绍
  • 3. vector的常用接口
    • 3.1 vector对象的常见构造函数
    • 3.2 iterator的使用
    • 3.3 vector的空间管理
    • 3.4 vector的增删查改
  • 4. vector迭代器失效的问题
    • 4.1 底层空间改变的操作
    • 4.2 指定位置元素的删除操作
  • 5. vector模拟实现
  • 6. 结尾

1. 前言

上一篇文章我们学习了C++中string类的使用和模拟实现,string是一种表示字符串的字符串类今天我们来继续学习C++中的另一种容器:vector。

2. vector的介绍

1.vector是表示可变大小数组的序列容器。
2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3.本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4.vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5.因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6.与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

3. vector的常用接口

3.1 vector对象的常见构造函数

函数名称功能说明
vector()无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x)拷贝构造
vector (InputIterator first, InputIterator last)使用迭代器进行初始化构造
void Test1()
	{
		vector<int> v1;
		vector<int> v2(10, 1);
		vector<int> v3(v2);
	}

在这里插入图片描述

3.2 iterator的使用

函数名称功能说明
begin+end获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin+rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator
void Test1()
	{
		vector<int> v1;
		vector<int> v2(10, 1);
		vector<int> v3(v2);
		//迭代器
		vector<int>::iterator it = v2.begin();

		while (it != v2.end())
		{
			(*it)++;
			cout << *it << ' ';
			it++;
		}
		cout << endl;

	}

在这里插入图片描述

3.3 vector的空间管理

函数名称功能说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize改变vector的size
reserve改变vector的capacity

注意
1.capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
2.reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问
题。
3.resize在开空间的同时还会进行初始化,影响size。

void TestVectorExpand()
	{
		size_t sz;
		vector<int> v;
		//v.resize(100);
		//v.reserve(100);

		sz = v.capacity();
		cout << "making v grow:\n";
		for (int i = 0; i < 100; ++i)
		{
			v.push_back(i);
			if (sz != v.capacity())
			{
				sz = v.capacity();
				cout << "capacity changed: " << sz << '\n';
			}
		}
	}

	void Test3()
	{
		vector<int> v1;
		cout << v1.max_size() << endl;
	
		TestVectorExpand();
	
	}

在这里插入图片描述

void TestVectorExpand()
	{
		size_t sz;
		vector<int> v;
		//v.resize(100);
		v.reserve(100);

		sz = v.capacity();
		cout << "making v grow:\n";
		for (int i = 0; i < 100; ++i)
		{
			v.push_back(i);
			if (sz != v.capacity())
			{
				sz = v.capacity();
				cout << "capacity changed: " << sz << '\n';
			}
		}
	}

	void Test3()
	{
		vector<int> v1;
		cout << v1.max_size() << endl;
	
		TestVectorExpand();
	
	}

在这里插入图片描述

3.4 vector的增删查改

函数名称功能说明
push_back尾插
pop_back尾删
find查找
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[]像数组一样访问
void Test4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end())
		{
			v1.insert(pos, 30);
		}
		for (size_t i = 0; i < v1.size(); i++)
		{
			cout << v1[i] << ' ';
		}
		cout << endl;

		pos = find(v1.begin(), v1.end(), 300);
		if (pos != v1.end())
		{
			v1.erase(pos);
		}

		for (auto e : v1)
		{
			cout << e << ' ';
		}
		cout << endl;
	}

在这里插入图片描述

void Test5()
	{
		vector<int> v1;
		v1.push_back(10);
		v1.push_back(1);
		v1.push_back(44);
		v1.push_back(223);
		v1.push_back(32);
		v1.push_back(56);
		v1.push_back(15);
		v1.push_back(90);
		for (auto e : v1)
		{
			cout << e << ' ';
		}
		cout << endl;

		sort(v1.begin(), v1.end());
		
		for (auto e : v1)
		{
			cout << e << ' ';
		}
		cout << endl;
	
		//less<int> ls;
		//greater<int> gt;
		//sort(v1.begin(), v1.end(), gt);
		sort(v1.begin(), v1.end(), greater<int>());
		for (auto e : v1)
		{
			cout << e << ' ';
		}
		cout << endl;
	}

在这里插入图片描述

4. vector迭代器失效的问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
可能导致迭代器失效的操作有很多,下面我们来一一介绍。

4.1 底层空间改变的操作

会引起其底层空间改变的操作,都有可能导致迭代器失效,比如:resize、reserve、insert、assign、push_back等。

void Test6()
	{
		vector<int> v{ 1,2,3,4,5,6 };

		auto it = v.begin();

		// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
		// v.resize(100, 8);

		// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
		// v.reserve(100);

		// 插入元素期间,可能会引起扩容,而导致原空间被释放
		// v.insert(v.begin(), 0);
		// v.push_back(8);

		// 给vector重新赋值,可能会引起底层容量改变
		v.assign(100, 8);

		
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

在这里插入图片描述

出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。
解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。

4.2 指定位置元素的删除操作

void Test7()
	{
	
		int a[] = { 1, 2, 3, 4 };
		vector<int> v(a, a + sizeof(a) / sizeof(int));
		// 使用find查找3所在位置的iterator
		vector<int>::iterator pos = find(v.begin(), v.end(), 3);
		// 删除pos位置的数据,导致pos迭代器失效。
		v.erase(pos);
		cout << *pos << endl; // 此处会导致非法访问
	
	}

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

总结
迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

5. vector模拟实现

namespace fiora
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin()const
		{
			return _start;
		}

		const_iterator end()const
		{
			return _finish;
		}

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

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

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

		void resize(size_t n, const T& val = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}
		
			if (n > size())
			{
				while (_finish < _start + n)
				{
					*_finish = val;
					_finish++;
				}

			}
			else
			{
				_finish = _start + n;
			}
		}


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

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = len + _start;
			}
			//向后挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
		
			*pos = x;
			_finish++;
			return pos;
		}

		//erase返回删除位置的下一个位置的迭代器
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

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

		T& front()
		{
			assert(size() > 0);

			return *_start;
		}

		T& back()
		{
			assert(size() > 0);

			return *(_finish - 1);
		}

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

		void pop_back()
		{
			assert(_finish > _start);
			_finish--;
		}

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

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

		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> v)
		{
			swap(v);
			return *this;
		}

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

		/*vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			reserve(v.size());
			for (const auto& e : v)
			{
				push_back(e);
			}
		}*/

		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}


		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

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

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


	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;

	};
}

6. 结尾

到这里,关于vector的介绍和常见功能我们就学习结束了,vector在实际中非常的重要,但在实际中我们只要熟悉常见的接口就可以了,最重要的是理解他的底层原理,要能够自己模拟实现出一个简单的vector。
最后,感谢各位大佬的耐心阅读和支持,觉得本篇文章写的不错的朋友可以三连关注支持一波,如果有什么问题或者本文有错误的地方大家可以私信我,也可以在评论区留言讨论,再次感谢各位。

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

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

相关文章

K210入门-环境搭建与点灯测试(一)

目录 1、简介 2、资质查找 3、IDE下载安装 4、测试程序 4.1 测序复制 4.2 开发板选择 4.3 链接 4.4 效果展示 1、简介 本文主要针对小白使用K210进行入门&#xff0c;以及自己学习的总结与笔记使用。本文主要进行环境搭建与点灯测试。 2、资质查找 首先去官网进行资料下…

Flume系列:Flume数据监控Ganglia

目录 Apache Hadoop生态-目录汇总-持续更新 安装说明 1&#xff09;安装 ganglia 2&#xff09;在 worker213 修改配置文件 3&#xff09;在 所有服务器 修改配置文件/etc/ganglia/gmond.conf 4&#xff09;启动 ganglia 5&#xff09;打开网页浏览 ganglia 页面 6&…

《UVM 实战》 代码下载, 无需注册

法一&#xff1a; https://www.hzcourse.com/web/refbook/detail/5651/229 法二&#xff1a; https://www.hzcourse.com/oep/resource/access/L29wZW5yZXNvdXJjZXMvdGVhY2hfcmVzb3VyY2UvZmlsZS8yMDE3LzEwL2IyMDE0OTFmMmUxMjdkNTM2YjhmMjBmNWUzMTRhMjE3Lmd6JGV4YW1wbGVfYW5kX3…

如何在华为OD机试中获得满分?Java实现【报数游戏】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述4. Java算法源码5. 测试6.解题思路1. 题目描述 100个人围成一圈,每个人…

Redis数据库简介

1.Redis数据库介绍 Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 2.Redis数据库特性 Redis支持数据的持久化&#xff0c;可以将内存中的数据保存在磁盘中&#xff0c;重启的时候可以再次加…

2023 华为 Datacom-HCIE 真题题库 09--含解析

单项选择题 1.[试题编号&#xff1a;190485] &#xff08;单选题&#xff09;华为交换机MAC地址表的老化时间默认是多少秒? A、500 B、5 C、300 D、400 答案&#xff1a;C 解析&#xff1a;无 2.[试题编号&#xff1a;190484] &#xff08;单选题&#xff09;如图所示&#…

数据分析之Pandas--数据检索

数据分析之Pandas&#xff08;03&#xff09;--数据检索 pandas的数据检索功能是其最基础也是最重要的功能之一。 pandas中最常用的几种数据过滤方式如下&#xff1a; 1. 行列过滤&#xff1a;选取指定的行或者列 2. 条件过滤&#xff1a;对列的数据设置过滤条件 3. 函数过…

提升PostGIS大范围、大数据量分区几何裁剪统计查询速度技巧

PostGIS是在GIS系统开发中常用的开源空间数据库&#xff0c;使用PostGIS进行大范围、大数据量的几何裁剪操作时&#xff0c;耗时较长。 当我遇到需要按区县或选中的乡镇&#xff0c;计算展示林规、土地报批等多个规划数据的面积等&#xff0c;此时需要使用规划数据叠加行政界线…

几句命令搞定一个es:docker安装elasticsearch+可视化kibana

docker安装elasticsearch可视化kibana 写在前面es安装&#xff1a;docker安装elasticsearches搜索&#xff1a;安装elasticsearch插件IK分词器es可视化&#xff1a;docker安装kibana最后 写在前面 从自己知道es开始到写这篇文章差不多也有5年左右的时间了吧&#xff0c;之前总…

FastReport.Net FastReport.Core 2023.2.15 Crack

快速报告.NET .NET 7 的报告和文档创建库 FastReport.Net & FastReport.Core适用于 .NET 7、.NET Core、Blazor、ASP.NET、MVC 和 Windows 窗体的全功能报告库。它可以在 Microsoft Visual Studio 2022 和 JetBrains Rider 中使用。 快速报告.NET 利用 .NET 7、.NET Core、…

C++程序设计基础【一】

C程序设计基础【一】 一、一个程序的开发步骤1.编辑程序2.编译程序3.链接程序4.执行程序5.测试 2.基础代码解读1.预处理指令(#include <iostream>)2.块注释(/* */)3.行注释(//)4.using namespace std5.int main()6.{}7.std::cin、std::cout、std::endl8.return 0 二、变量…

云上高校导航 开发指引 与 注意事项

&#x1f52c; 注意事项 大部分数据存储在utils.js中的&#xff0c;页面通过引入utils.js方式渲染数据 图标全部存储在项目images文件夹里,均下载自 iconfont网站&#xff08;自行替换&#xff09; 部分图片引用自 免费图床 - CDN加速图床&#xff08;自行替换&#xff09; …

七年程序员的三四月总结:三十岁、准备婚礼、三次分享

你好&#xff0c;我是 shixin&#xff0c;一名工作七年的安卓开发。 每两个月我会做一次总结&#xff0c;记下这段时间里有意义的事和值得反复看的内容&#xff0c;为的是留一些回忆、评估自己的行为、沉淀有价值的信息。 一转眼 2023 年过去了三分之一&#xff0c;这两个月经…

响应式编程实战:Spring WebFlux集成MongoDB和Swagger

1 缘起 新的项目&#xff0c;快速迭代&#xff0c; 技术选型&#xff1a;Spring WebFlux&#xff0c; 非Spring MVC&#xff0c; 之前没有接触过Spring WebFlux&#xff0c;项目中都是使用Spring MVC&#xff0c; 这次学到了新的知识Spring WebFlux&#xff0c;记录下。 2 Sp…

C++实现哈希表

文章目录 前言1.哈希表的相关介绍2.哈希表的实现1.开放定址法实现哈希表1.插入2.查找3.删除 2.链地址法(开链法)实现哈希表1.插入节点2.查找3.删除4.相关的一些补充 3.封装unordered_map与unordered_set1.封装前的改造2.迭代器的实现3.unordered_map和unordered_set复用 前言 …

60题学会动态规划系列:动态规划算法第一讲

坚持就是胜利 - - 文章目录 1.第N个泰波那切数 2.三步问题 3.使用最小花费爬楼梯 4.解码方法 1.第N个泰波那切数 力扣链接&#xff1a;力扣 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c…

多线程 -- 线程安全问题(3)

本篇重点: 总结线程安全问题的原因以及解决办法 目录 synchronized 加锁关键字join 和 synchronized 的区别volatile 关键字 在上一篇中我们介绍了Thread类的基本使用方法, 本篇将会介绍有关于线程的安全问题 线程不安全的原因: 抢占式执行(罪魁祸首, 万恶之源) 多个线程修改同…

搜索推荐系统[10]项目实战系列Z5:汽车说明书跨模态智能问答系统,针对汽车说明书(可自定义文档)进行自动问答,采用了OCR、RocketQA等技术

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

小程序之页面通信派发通知

文章目录 1. 介绍小程序页面通信的概念解释小程序页面通信的意义和必要性介绍小程序页面通信的方法 2. 小程序页面通信的实现示例通过事件传递数据实现页面之间通信通过全局变量实现页面之间通信 3. 实现小程序页面之间的消息通知介绍小程序发布订阅模式的概念使用事件订阅-发布…

网络通信IO模型-BIO

承接上文网络通信IO模型上 BIO的Java代码 服务端创建一个ServerSocket&#xff0c;绑定了端口号8090&#xff0c;目的是让客户端和服务端建立连接后进行通信&#xff0c;然后进入死循环&#xff0c;死循环里面会调用server.accept得到一个socket客户端&#xff0c;打印客户端的…