C++初阶:vector类

news2024/10/6 5:56:55

文章目录

  • 1 vector介绍
  • 2 实现vector
    • 2.1 类的定义
    • 2.2 默认成员函数
      • 2.2.1 构造函数
      • 2.2.2 析构函数
      • 2.2.3 拷贝构造
      • 2.2.4 赋值重载
    • 2.3访问接口
    • 2.4 容量接口
    • 2.5 修改接口
      • 2.5.1 尾插尾删
      • 2.5.2 任意位置插入
      • 2.5.3 任意位置删除
    • 2.6 其他接口

1 vector介绍

1 vector是表示可变大小数组的序列容器
2 就像数组一样,vector也采用连续存储空间的方式来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组为了增加存储空间需要被重新分配大小。其做法是,分配一个新的数组,然后将全部元素移到这个数组。

2 实现vector

在这里插入图片描述

可以看出,vector使用start指向数组的起始位置,finsh指向数组有效元素的下一个位置,end_of_storage指向区间的容量

2.1 类的定义

namespace zbt
{
	template <class T>
		class vector
	{
	public:
			typedef T* iterator;
			typedef const T* const_iterator;
	 private:
		iterator _start;//指向数组的起始位置
		iterator _finsh;//指向数组有效元素的下一个位置
		iterator _end_of_storage;//指向区间的容量

	};

和之前实现string不同的是,把指向元素个数和区间容量的值改为用相应的迭代器实现,但本质是一样的。我们可以看到,vector的迭代器底层实现依旧是指针。

之所以定义为类模板,是因为vector里面不仅可以存int,char,double这样的内置类型,同样也可以存储string,甚至vector这样的自定义类型

2.2 默认成员函数

2.2.1 构造函数

                vector()
				:_start(nullptr)
				,_finsh(nullptr)
				,_end_of_storage(nullptr)
			{

			}

无参的构造函数,直接把三个指针初始化为nullptr。

在这里插入图片描述

除了使用无参的构造函数,我们看到C++的官方文库还支持使用迭代器区间进行构造的方式

//函数模板
			template <class InputIterator>
			vector(InputIterator first, InputIterator last)//给定一段迭代器区间
				:_start(nullptr)
				,_finsh(nullptr)
				,_end_of_storage(nullptr)
            {
				while (first != last)
				{
					push_back(*first);//从头开始将元素一个接一个尾插到vector里面
					first++;
				}

			}

从中可以看出,在类模板里面同样也可以套用函数模板,将此函数实现为模板函数,这样任意类型的迭代器都可以用来构造vector
当然,实现迭代器区间构造的方式前提还需要实现push_back函数

example:

            std::string s("hello");
			//使用迭代器区间构造v
			vector<char>v(s.begin(), s.end());
			for (auto e : v)
			{
				cout << e << " ";
			}
			cout << endl;

用string的迭代器去构造vector

2.2.2 析构函数

 ~vector()
			{
				delete[]_start;//将动态开辟的空间释放掉
				_start = _finsh = _end_of_storage = nullptr;
			}

2.2.3 拷贝构造

//现代写法
			void swap(vector<T>& tmp)
			{
				::swap(_start, tmp._start);
				::swap(_finsh, tmp._finsh);
				::swap(_end_of_storage, tmp._end_of_storage);
			}
			//v2(v3)
			vector(const vector<T>& v)
				:_start(nullptr)
				, _finsh(nullptr)
				, _end_of_storage(nullptr)
			{
				vector<T>tmp(v.begin(), v.end())//利用迭代器区间构造一个tmp,里面存储的值就是对应v的值;
				swap(tmp);//将v2和tmp进行交换

			}

2.2.4 赋值重载

	//v2=v3;
			vector<T>& operator=(vector<T> v)
			{
				//v就是v3的拷贝,然后将v和v2进行交换
				swap(v);
				return *this;
			}

2.3访问接口

访问方式可以是operator[ ]或者迭代器

            iterator begin()
			{
				return _start;
			}
			iterator end()
			{
				return _finsh;
			}
			const_iterator begin()const
			{
				return _start;
			}
			const_iterator end()const
			{
				return _finsh;
			}
			T& operator[](size_t pos)
			{
				assert(pos < size());
				return _start[pos];
			}
			const T& operator[](size_t pos)const
			{
				assert(pos < size());
				return _start[pos];
			}

2.4 容量接口

resize

void resize(size_t n,const T& val = T())
				//匿名对象调用默认构造,
			{
				if (n > capacity())//需要的空间比原有的空间大,先扩容
				{
					reserve(n);
				}
				if (n > size())//需要的空间大于有效数据的个数,在原有有效数据的后面插入val
				{
					while (_finsh != _start + n)
					{
						*(_finsh) = val;
						_finsh++;
					}
				}
				else//需要的空间比原有的空间小,需要删除数据
				{
					_finsh = _start + n;
				}
			}

缺省值采用T()的形式,T()调用的是T类型的默认构造函数,初始化出一个匿名对象,得到的是T类型的空值

reserve

void reserve(size_t n)
			{
				if (n > capacity())
				{
					size_t sz = size();
					T* tmp = new T [n];//动态开辟一块新的空间,大小为n
					if (_start)//如果原来的vector有数据,将数据拷贝到新的空间去
					{
						//memcpy(tmp, _start,sizeof(T)* sz);
						for (size_t i = 0; i < sz; i++)
						{
							tmp[i] = _start[i];
						}
						delete[]_start;
					}
					//更新成员变量的值
					_start = tmp;
					_finsh = _start + sz;
					_end_of_storage = _start + n;
				}
			}

需要注意的是,在拷贝数据的时候,我们并没有使用memcpy直接将数据拷贝过去,而是采用一个一个的赋值,这是为什么呢?这里便涉及更深层次的深拷贝问题

如果vector里面存储的是int,double等内置类型,用memcpy进行浅拷贝是完全没有问题的。但是vector里面也可以存储自定义类型的数据,例如string,vector等,这时如果粗暴的将数据进行浅拷贝,那么原来数组中的数据和和此时新拷贝的数据便会指向同一块空间,析构函数对同一块空间释放两次,程序便会崩溃

在这里插入图片描述

所以在拷贝数据的时候,应使用深拷贝。这里我们采用的是赋值重载,即一个一个赋值。

2.5 修改接口

2.5.1 尾插尾删

void push_back(const T& x)//尾插
			{
				if (_finsh == _end_of_storage)
				{
					reserve(capacity() == 0 ? 4 : capacity() * 2);//满了就扩容
                 }
				*(_finsh) = x;
				_finsh++;
			}
			void pop_back()//尾删
			{
				assert(_finsh > _start);
				_finsh--;
			}

2.5.2 任意位置插入

iterator  insert(iterator pos, const T& x)
			{
				assert(pos >= _start);
				assert(pos <= _finsh);
				if (_finsh == _end_of_storage)//满了扩容
				{
					size_t len = pos - _start;
					reserve(capacity() == 0 ? 4 : capacity() * 2);
					//扩容完之后,_start的地址会发生变化,所以要更新pos地址
					pos = _start + len;
				}
				iterator end = _finsh - 1;
				while (end >= pos)//[pos~_finsh)位置的元素后移一位
				{
					*(end+1) =*end ;
					end--;
				}
				*pos = x;
				_finsh++;
				return pos;

			}

注意:
① 因为扩容会重新开辟一块空间,_start会指向一块新的空间,但pos还是指向之前的位置,没有更新,这时迭代器就会失效,所以在扩容之后要更新pos的值,指向新的位置。
② insert函数内的iterator失效问题解决了,但是函数外的pos并没有改变,仍然指向之前的位置,所以最后要将更新后的pos值作为返回值返回。

2.5.3 任意位置删除

iterator erase(iterator pos)
			{
				assert(pos >= _start);
				assert(pos < _finsh);
				iterator begin = pos + 1;
				while (begin < _finsh)//pos位置之后的元素统一向前移动
				{
					*(begin - 1) = *begin;
					begin++;
				}
				_finsh--;
				return pos;
			}

任意位置删除是否有迭代器失效的问题呢?

答案是有的,举个栗子
删除数组元素中的偶数

第一种写法

vector<int>v3;
			v3.push_back(1);
			v3.push_back(2);
			v3.push_back(4);
		     v3.push_back(3);
			v3.push_back(4);
			v3.push_back(5);
			vector<int> ::iterator it = v3.begin();
			while (it != v3.end())
			{
				if (*it % 2 == 0)
				{
					 v3.erase(it);
				}
				it++;
			}
			for (auto e : v3)
			{
				cout << e << " ";
			}
			cout << endl;

在这里插入图片描述

很显然,答案错误,偶数并没有删除完,这种写法是有bug的

在这里插入图片描述

在删除元素2之后,由于2后面的元素前移,所以此时pos指向的是4,pos++刚好越过了4,所以没有删除掉4这个元素。此时迭代器便失效

第二种写法

vector<int>v3;
			v3.push_back(1);
			v3.push_back(2);
			v3.push_back(4);
			v3.push_back(3);
			v3.push_back(4);
			v3.push_back(5);
			vector<int> ::iterator it = v3.begin();
			while (it != v3.end())
			{
				if (*it% 2 == 0)
				{
					it = v3.erase(it);
				}
				else
				{
					it++;
				}
			}
			for (auto e : v3)
			{
				cout << e << " ";
			}
			cout << endl;

在这里插入图片描述

答案正确,在删除完元素后返回当前的pos,保证pos的位置正确(有些STL版本可能会缩容,导致pos为野指针),并且删除之后不能++pos的值。

综上所述,insert/erase pos位置的值后不要直接访问pos,因为此时迭代器已经失效。

2.6 其他接口

size_t capacity()const//返回容量大小
			{
				return _end_of_storage - _start;
			}
size_t size()const//返回有效元素的个数
			{
				return _finsh - _start;
			}

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

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

相关文章

每日学术速递1.26

CV - 计算机视觉 今天带来的是北航IRIP实验室被国际人工智能联合会议IJCAI-ECAI 2022接收的3篇论文。 IJCAI 是人工智能领域中最主要的学术会议之一&#xff0c;原为单数年召开&#xff0c;自2015年起改为每年召开&#xff0c;本次IJCAI与ECAI一起召开。IJCAI官网显示&#xf…

【Linux】冯诺依曼体系结构与操作系统概念理解

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;Linux 文章目录一、前言二、冯诺依曼体系结构1、体系简述2、内存的重要性3、硬件方案解释软件行为4、体系结构中的数据流动5、拓展三、操作系统简述…

ch1 操作系统启动

lab1 实验准备 按照实验解压后进入oslab中&#xff0c;按照make编译。 cd /home/shiyanlou/oslab/ tar -zxvf hit-oslab-linux-20110823.tar.gz \-C /home/shiyanlou/ ./run cd ./linux-0.11/ make all make clean ..... make all运行脚本即可启动内核 调试 汇编级调试和C语…

贪心算法的题目

每一步都做出一个局部最优的选择&#xff0c;最终的结果就是全局最优 只有一部分问题才能用贪心算法&#xff08;严格来讲&#xff0c;一个问题能不能用贪心算法需要证明的&#xff09; 2022.8.30 蔚来笔试题&#xff1a; 有a个y,b个o,c个u,用这些字母拼成一个字符串&#xf…

Anaconda软件中的 Environments 及 Jupyter Lab使用方法介绍

来源&#xff1a;投稿 作者&#xff1a;助教-Frank 编辑&#xff1a;学姐 本篇是打造舒适的AI开发环境系列-软件篇1 上期内容&#xff1a;学人工智能电脑&主机八大件配置选择指南 本文的重点&#xff1a; (1)Environments使用中如何安装python包.; (2)Jupyter Lab如何在…

Kettle(6):表输入组件——mysql转mysql

1 需求 前面我们已经将Excel中数据抽取到了MySQL的t_user表中。 现在有了新需求&#xff0c;要将MySQL数据库中的 t_user 表中的数据抽取出来&#xff0c;装载到另外一张表 t_user1中。 2 构建Kettle数据流图 2.1 从核心对象的输入组件中&#xff0c;将「表输入」组件拖拽到中…

电脑下载软件用什么软件好?安卓手机下载软件用哪个软件好?IDM下载器说:在做的都是弟弟

大年初五&#xff0c;迎财神&#xff0c;先祝大家新的一年财源滚滚&#xff0c;接下来为大家分享超级经典的IDM下载器&#xff0c;电脑端毫无争议的下载工具&#xff0c;安卓平台idm也是力压群雄&#xff0c;下面就为大家详细分享下&#xff1a; 1&#xff1a;1DM下载器&#x…

微服务统一登陆认证怎么做

[微服务统一登陆认证怎么做}&#xff1f;JWT 无状态登录原理 1.1.什么是有状态&#xff1f; 有状态服务&#xff0c;即服务端需要记录每次会话的客户端信息&#xff0c;从而识别客户端身份&#xff0c;根据用户身份进行请求的处理&#xff0c;典型的设计如tomcat中的session…

notepad++在行首行尾添加字符 | 选中列

目录 1、首行/尾行添加字符 1【使用快捷键 CtrlH】 2【^为行首、$为行尾】 3、查找模式选中正则表达式 2、Notepad中列选(竖选&#xff09; 1、首行/尾行添加字符 1【使用快捷键 CtrlH】 或者鼠标 2【^为行首、$为行尾】 3、查找模式选中正则表达式 2、Notepad中列选(竖…

深度学习入门(一)感知机

该文将介绍感知机A&#xff08;perceptron&#xff09;这一算法。感知机是由美国学者Frank Rosenblatt在1957年提出来的。为何我们现在还要学习这一很久以前就有的 算 法 呢 &#xff1f; 因 为 感 知 机 也 是 作 为 神 经 网 络&#xff08;深 度 学 习&#xff09;的起源的算…

详解Windows通过命令行查看电脑连接过的WIfI密码

CONTENT打开命令行进入命令行下的netsh工具查看连接过的WiFi名称指定WiFi名称查看密码在Windows操作系统中&#xff08;PS&#xff1a;Windows Vista及以后的Windows系统&#xff09;可以通过命令行工具netsh查看和更改电脑的无线连接设置&#xff0c;包括WiFi。本篇博客将详细…

C语言进阶——文件管理

每当我们写好一段代码运行结束之后&#xff0c;再次运行的时候就会发现&#xff0c;之前在终端上输入的数据都会消失&#xff0c;那么如何把之前输入的数据保存下来呢&#xff1f; 我们一般把数据持久化的方式有把数据存放在磁盘文件中、存放到数据库。打印等方式进行保存。 …

Java---微服务---elasticsearch安装部署

elasticsearch安装部署1.部署单点es1.1.创建网络1.2.加载镜像1.3.运行2.部署kibana2.1.部署2.2.DevTools3.安装IK分词器3.1.在线安装ik插件&#xff08;较慢&#xff09;3.2.离线安装ik插件&#xff08;推荐&#xff09;1&#xff09;查看数据卷目录2&#xff09;下载并解压缩分…

RocketMQ源码本地搭建调试

1 GitHub源码 git clone https://github.com/apache/rocketmq.git导入IDEA&#xff0c;可在命令行执行mvn compile一下&#xff0c;保证源码能够正确编译。本次我使用的master分支的版本-4.8.0。下面我们开始准备启动Namesrv。 2 启动Namesrv 到namesrv模块找到NamesrvStart…

web游戏---canvas基础图形

基础 canvas标签 canvas是H5中新推出的标签&#xff0c;这个提供一块画布&#xff0c;可以在上面绘制图案&#xff0c;通过这种方式制作web游戏带来的性能消耗比操作DOM要小的多。 如果知做浏览器游戏&#xff0c;为了保证性能最好使用画布来制作。 坐标系 画布的坐标系和…

ThinkPadE540重装系统

过年这段时间&#xff0c;帮家里人重装了一下win10系统&#xff0c;在这里记录一下&#xff0c;方便今后还要使用。 先准备两个U盘&#xff0c;一个存储电脑的文件&#xff08;以防文件丢失&#xff09;&#xff0c;一个空u盘&#xff08;制作重装系统的&#xff09; 一.下载镜…

【5-卷积神经网络】北京大学TensorFlow2.0

课程地址&#xff1a;【北京大学】Tensorflow2.0_哔哩哔哩_bilibiliPython3.7和TensorFlow2.1六讲&#xff1a;神经网络计算&#xff1a;神经网络的计算过程&#xff0c;搭建第一个神经网络模型神经网络优化&#xff1a;神经网络的优化方法&#xff0c;掌握学习率、激活函数、损…

Junit单元测试框架【基础篇】

Junit单元测试框架【基础篇】&#x1f34e;一.Junit单元测试框架&#x1f352;1.1 注解&#x1f352;1.2 断言&#x1f352;1.3 用例执行顺序&#x1f352;1.4 测试套件&#x1f349;1.4.1 指定类&#x1f349;1.4.1 指定包&#x1f352;1.5 参数化&#x1f349;1.5.1 单参数&a…

VBA提高篇_07 Goto跳转 / Exit退出 /VBA错误处理

文章目录使用逻辑变量控制循环使用Goto语句任意跳转捷径:使用Exit语句跳出结构保险: 使用错误处理改善用户体验On Error Goto Lablex:On Error Resume Next使用逻辑变量控制循环 使用Goto语句任意跳转 经常在错误处理时使用 捷径:使用Exit语句跳出结构 注意: 避免使用while…w…

【C++】AVL树(插入)

文章目录AVL树的概念平衡化旋转右单旋转左单旋转先左后右双旋转先右后左双旋转AVL树的插入根据BST树规则进行节点插入平衡化处理重新连接节点完整的插入函数代码AVL树的验证AVL树的性能AVL树的概念 二叉搜索树虽然可以提高查找的效率&#xff0c;但是二叉搜索树有其自身的缺陷&…