C++vector常用接口和模拟实现

news2025/3/30 15:44:46

C++中的vector是一个可变容量的数组容器,它可以像数组一样使用[]进行数据的访问,但是又不像C语言数组空间是静态的,它的空间是动态可变的。

在日常中我们只需要了解常用的接口即可,不常用的接口查文档即可。

1.构造函数

//空构造
vector()

//拷贝构造
vector(const vector<T>& v)

//构造并初始化n个val
vector(size_t n,const T& val = T())

//使用迭代器初始化,这里写成模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)

2.迭代器

对于vector的迭代器也可以看作是指针

//获取第一个位置数据的普通迭代器和const迭代器
iterator begin();
const_iterator begin() const;

//获取最后一个位置数据的普通迭代器和const迭代器
iterator end();
const_iterator end() const;

3.空间管理

//获取元素个数
size_t size() const;

//判断是否为空
bool empty() const;

//改变大小并且初始化
void resize (size_type n, value_type val = value_type());

//改变容量
void reserve (size_type n);

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问 题。 resize在开空间的同时还会进行初始化,影响size。

4.增删改查

//尾插
void push_back (const value_type& val);

//尾删
void pop_back();

//在任意位置插入
iterator insert (iterator position, const value_type& val);

//在任意位置删除
iterator erase (iterator position);

//交换两组数据空间
void swap (vector& x);

//[]重载
T& operator[](size_t pos)

这里需要了解一个问题就是迭代器失效的问题,对于vector而言,它的迭代器底层就是原生指针。因此迭代器失效的原因就是指针所指向的空间被销毁了,指向了一个已经被释放的空间。

    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);
    

例如上面这些例子,他们都引起了底层空间的改变,就会导致it失效,如果在后面的代码中使用失效的迭代器就会导致程序崩溃。

要解决这个问题的方法也很简单,就是在修改之后重新赋值即可。

下面进行vector的模拟实现

	template<class T>
	class vector
	{
	public:
        //vector的迭代器就是原生指针,这里写成模板的形式
		typedef T* iterator;
		typedef const T* const_iterator;

        //begin()相当于直接返回头指针
		iterator begin()
		{
			return _start;
		}

		const_iterator begin() const
		{
			return _start;
		}
        
        //end()相当于直接返回尾指针
		iterator end()
		{
			return _finish;
		}

		const_iterator end() const
		{
			return _finish;
		}

		//左闭右开
        //迭代器构造函数,这里写成模板支持更多类型
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while(first != last)
			{
				push_back(*first);
				++first;
			}
		}
        
        //空构造函数
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

        //拷贝构造
        //不能使用memcpy,因为这是浅拷贝,很可能会造成内存泄漏
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T) * v.size());
            //这里与下面reserve是相同的问题
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
                //如果是自定义类型,这里事实上调用的是自定义类型的赋值操作
			}
            //这里由于是原生指针,并且是顺序存储因此可以直接相加
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
		}
   
        //初始化n个val的vector,这里可以复用resize()
		vector(size_t n,const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			resize(n, val);
		}
        
        //交换
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
        
        //现代写法
        //这里使用传值方式传参是因为能够生成临时拷贝不会影响原数据
        //这里使用传引用返回是因为this出了作用域会销毁
		vector<T>& operator=(vector<T> v)
		{
			swap(v);//相当于this->swap(v)
			return *this;
		}
        
        //析构
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endofstorage = nullptr;
			}
		}
        
        //reserve()空间管理,同样不能使用memcpy(),会导致内存泄漏
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* temp = new T[n];
				if (_start)
				{
					//memcpy(temp, _start, sizeof(T) * size());
					for (size_t i = 0; i < sz; i++)
					{
						temp[i] = _start[i];
                        //如果是自定义类型,这里事实上调用的是自定义类型的赋值操作
					}
					delete[] _start;
				}
				_start = temp;
				//_finish = _start + size()这样写是错的。
                //由于_start的改变会导致size()报错,这就是迭代器失效。
				//因为_start指向了新空间,但是_finish还是指向旧空间
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}
        
        //申请n个空间初始化为val,这里复用reserve()
		void resize(size_t n,const T& val = T())//匿名对象
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
        
        //尾插,这里自己实现需要注意扩容,但是也可以考虑直接复用insert()
		void push_back(const T& x)
		{
			//if (_finish == _endofstorage) 
			//{
			//	//扩容
			//	size_t newcapacity = capacity() == 0 ? 4 : capacity() *2;
			//	reserve(newcapacity);
			//}
			//*_finish = x;
			//++_finish;
			insert(end(), x);
		}
        
        //这里与尾插是一样的思路
		void pop_back()
		{
			erase(end());
		}
        
        //获得当前容器的容量
		size_t capacity() const
		{
			return _endofstorage - _start;
		}
        
        //获取当前容器中有效数据的个数
		size_t size() const
		{
			return _finish - _start;
		}
        
        //重载[],这里事实上相当于(*_start + pos)
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}
        
        //任意位置插入,需要注意迭代器失效的问题
		void insert(iterator pos ,const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			if (_finish == _endofstorage)
			{
				//扩容
				//这里如果直接扩容会引发迭代器失效,因为pos还是指向原来的位置
				//因此需要更新pos的位置,这里保存的时相对位置
				size_t len = pos - _start;

				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);

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

		//iterator erase(iterator pos)
		//{
		//	 //检查 pos 是否在合法范围内
		//	if (pos < _start || pos >= _finish)
		//	{
		//		throw std::out_of_range("Iterator out of range");
		//	}

		//	 //从删除位置开始,将后续所有元素向前移动一位
		//	iterator it = pos;
		//	while (it < _finish - 1)
		//	{
		//		*it = *(it + 1);
		//		++it;
		//	}

		//	 //更新 _finish,减少容器大小
		//	--_finish;

		//	 //返回删除位置的下一个有效迭代器
		//	return pos;  // 注意:这里返回的是删除位置的下一个迭代器
		//}
        
        //只要把pos位置后面的元素向前挪动覆盖即可,也不需要考虑迭代器失效的问题
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);
			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;
			return pos;
		}

	private:
        //定义头指针和尾指针以及一个管理空间的指针
		iterator _start;
		iterator _finish;
		iterator _endofstorage;

	};

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

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

相关文章

oracle查询归档日志使用量

1.统计最近30天的数据 SELECT TRUNC(first_time, DD) "日期", SUM(blocks * block_size) / 1024 / 1024 / 1024 "大小(GB)" FROM v$archived_log WHERE first_time > SYSDATE - 30 -- 统计最近30天的数据 GROUP BY TRUNC(first_time, DD) ORDER BY 1 D…

2025-03-26 学习记录--C/C++-PTA 6-3 求链式表的表长

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 6-3 求链式表的表长 本题要求实现一个函数&#xff0c;求链式表的表长。 函数接口定义&#xff1a; &…

PHP框架 ThinkPHP 漏洞探测分析

目录 1. PHP历史利用最多的漏洞有哪些&#xff1f; 2. 如何在信息收集的过程中收到框架信息&#xff1f;有什么根据&#xff1f; 3. ThinkPHP框架漏洞扫描有哪些工具&#xff1f;红队攻击有哪些方式&#xff1f; 漏洞扫描工具 红队攻击方式 4. TPscan工具的主要作用及实际…

SylixOS 中 select 原理及使用分析

1、select接口简介 1.1 select接口使用用例 select 是操作系统多路 I/O 复用技术实现的方式之一。 select 函数允许程序监视多个文件描述符&#xff0c;等待所监视的一个或者多个文件描述符变为“准备好”的状态。所谓的”准备好“状态是指&#xff1a;文件描述符不再是阻塞状…

软考笔记——软件工程基础知识

第五章节——软件工程基础知识 软件工程基础知识 第五章节——软件工程基础知识一、软件工程概述1. 计算机软件2. 软件工程基本原理3. 软件生命周期4. 软件过程 二、软件过程模型1. 瀑布模型2. 增量模型3. 演化模型&#xff08;原型模型、螺旋模型)4. 喷泉模型5. 基于构建的开发…

FastGPT原理分析-数据集创建第二步:处理任务的执行

概述 文章《FastGPT原理分析-数据集创建第一步》已经分析了数据集创建的第一步&#xff1a;文件上传和预处理的实现逻辑。本文介绍文件上传后&#xff0c;数据处理任务的具体实现逻辑。 数据集创建总体实现步骤 从上文可知数据集创建总体上来说分为两大步骤&#xff1a; &a…

STM32学习笔记之存储器映射(原理篇)

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

如何通过数据可视化提升管理效率

通过数据可视化提升管理效率的核心方法包括清晰展示关键指标、及时发现和解决问题、支持决策优化。其中&#xff0c;清晰展示关键指标尤为重要。通过数据可视化工具直观地呈现关键绩效指标&#xff08;KPI&#xff09;&#xff0c;管理者能快速、准确地理解业务现状&#xff0c…

数据结构:利用递推式计算next表

next 表是 KMP 算法的核心内容&#xff0c;下面介绍一种计算 next 表的方法&#xff1a;利用递推式计算 如图 6.3.1 所示&#xff0c;在某一趟匹配中&#xff0c;当对比到最后一个字符的时候&#xff0c;发现匹配失败&#xff08;s[i] ≠ t[j]&#xff09;。根据 BF 算法&…

每日算法-250326

83. 删除排序链表中的重复元素 题目描述 思路 使用快慢指针遍历排序链表。slow 指针指向当前不重复序列的最后一个节点&#xff0c;fast 指针用于向前遍历探索。当 fast 找到一个与 slow 指向的节点值不同的新节点时&#xff0c;就将 slow 的 next 指向 fast&#xff0c;然后 …

trino查询mysql报Unknown or incorrect time zone: ‘Asia/Shanghai‘

问题 trino查询mysql时报Error listing schemas for catalog mysql: java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.&#xff0c;trino的日志中看到Unknown or incorrect time zone…

java学习笔记7——面向对象

关键字&#xff1a;static 类变量 静态变量的内存解析&#xff1a; 相关代码&#xff1a; public class ChineseTest {public static void main(String[] args) {System.out.println(Chinese.nation); //null 没赋值前System.out.println(Chinese.nation); //中国 静态变量赋值…

C++三大特性之继承

1.继承的概念及定义 回忆封装 C Stack类设计和C设计Stack对比。封装更好&#xff1a;访问限定符类的数据和方法放在一起 -> 避免底层接口的暴露&#xff0c;数据更加的安全&#xff0c;程序的耦合性更高迭代器的设计&#xff0c;封装了容器底层结构&#xff0c;在不暴露底层…

解决Vmware 运行虚拟机Ubuntu22.04卡顿、终端打字延迟问题

亲测可用 打开虚拟机设置&#xff0c;关闭加速3D图形 &#xff08;应该是显卡驱动的问题&#xff0c;不知道那个版本的驱动不会出现这个问题&#xff0c;所以干脆把加速关了&#xff09;

ctfshow做题笔记—栈溢出—pwn73、pwn74

目录 一、pwn73(愉快的尝试一下一把梭吧&#xff01;) 二、pwn74(噢&#xff1f;好像到现在为止还没有了解到one_gadget?) 前言&#xff1a; 抽空闲时间继续学习&#xff0c;记录了两道题&#xff0c;pwn74卡了几天哈哈。 一、pwn73(愉快的尝试一下一把梭吧&#xff01;) …

026-zstd

zstd 以下为Zstandard&#xff08;zstd&#xff09;压缩算法从原理到代码实现的技术调研报告&#xff0c;结合流程图、结构图及完整C代码实现&#xff1a; 一、核心原理与技术架构 1.1 算法原理 Zstd基于LZ77衍生算法与熵编码&#xff08;FSE/Huffman&#xff09;的混合架构&…

AF3 quat_to_rot函数解读

AlphaFold3 rigid_utils 模块的 quat_to_rot 函数的功能是把四元数转换为旋转矩阵,函数利用预定义的四元数到旋转矩阵的转换表 _QTR_MAT 来简化计算。 理解四元数到旋转矩阵的转换 源代码: _quat_elements = ["a", "b", "c", "d"]…

Elasticsearch 的搜索功能

Elasticsearch 的搜索功能 建议阅读顺序&#xff1a; Elasticsearch 入门Elasticsearch 搜索&#xff08;本文&#xff09; 1. 介绍 使用 Elasticsearch 最终目的是为了实现搜索功能&#xff0c;现在先将文档添加到索引中&#xff0c;接下来完成搜索的方法。 查询的分类&…

CSS+JS 堆叠图片动态交互切换

结合DeepSeek提供的代码&#xff0c;终于实现了堆叠两张图片动态循环切换&#xff0c;以下是代码&#xff1a; 通过绝对定位放了两张图片 <div class"col-lg-5" style"z-index: 40; position: relative;"><img src"images/banner_1.png&quo…