vector你得知道的知识

news2024/9/28 9:31:59

vector的基本使用和模拟实现

在这里插入图片描述

一、std::vector基本介绍

1.1 常用接口说明

在这里插入图片描述

std::vector是STL中的一个动态数组容器,它可以自动调整大小,支持在数组末尾快速添加和删除元素,还支持随机访问元素

以下是std::vector常用的接口及其说明:

  1. push_back(): 在容器末尾添加元素
std::vector<int> vec{1, 2, 3};
vec.push_back(4);
  1. pop_back(): 删除容器末尾的元素
std::vector<int> vec{1, 2, 3};
vec.pop_back();
  1. size(): 返回容器中元素的个数
std::vector<int> vec{1, 2, 3};
std::cout << vec.size() << std::endl; //输出3
  1. empty(): 判断容器是否为空

  2. reserve(): 分配容器的内部存储空间,但不改变元素个数

std::vector<int> vec{1, 2, 3};
vec.reserve(10);	// 分配10个int大小的空间

其中的reserve接口,你说是分配容器空间,这是在堆上还是栈上开辟空间?那如果重新分配的空间比现有空间小,会发生什么?以及重新分配的空间大于现有空间,是在现有的基础上直接扩容,还是舍弃现有空间,将现有数据拷贝到新的空间上?

新分配的内存空间位于堆上。如果重新分配的空间比现有空间小,std::vector 会舍弃多余的元素。如果新分配的空间比现有空间大,std::vector 会重新分配内存,并将原有数据复制到新的内存空间中。

需要注意的是,重新分配内存并将原有数据复制到新的内存空间中,可能会导致性能问题。因此,如果您能够预估存储的数据量,建议在创建 std::vector 时就预分配足够的内存空间,以避免频繁地重新分配内存。

  1. resize(): 改变容器的元素个数
std::vector<int> vec{1, 2, 3};
vec.resize(5);
  1. clear(): 删除容器中的所有元素

  2. at(): 返回指定位置的元素

std::vector<int> vec{1, 2, 3};
std::cout << vec.at(1) << std::endl; //输出2
  1. front(): 返回容器中第一个元素

  2. back(): 返回容器中最后一个元素

  3. begin(): 返回指向容器中第一个元素的迭代器,end(): 返回指向容器中最后一个元素之后位置的迭代器

std::vector<int> vec{1, 2, 3};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " ";
}
  1. reverse():反转vector

std::vector::reverse 不是重新分配容器空间的接口,它是用于反转容器中元素的顺序。也就是将容器中第一个元素和最后一个元素交换,第二个元素和倒数第二个元素交换,以此类推。

1.2 代码示例

在这里插入图片描述

1.2.1 遍历vector的几种方式

以下示例中分别提到了:下标+[], 迭代器, 范围for, 反向迭代器

void test_vector1()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	for (size_t i = 0; i < v.size(); i++)		//1、下标+[]
	{
		cout << v[i] << " ";
	}

	vector<int>::iterator it = v.begin();		//2、迭代器
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}

	for (auto e : v)			//3、范围for
	{
		cout << e << " ";
	}

	vector<int>::reverse_iterator rit = v.rbegin();	// 4、反向迭代器
	while (rit != v.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	// 5 4 3 2 1
}

不难发现,用范围for访问的元素可以直接输出,而通过for循环,从v.begin()开始的迭代器it,却需要解引用。这是因为,在范围for循环中,迭代器是被自动解引用的。

在范围for循环中,循环变量的类型是根据容器元素的类型自动推导出来的,而不是容器的迭代器类型。对于std::vector<int>容器,其元素类型是int,因此在范围for循环中,循环变量的类型是int,而不是std::vector<int>::iterator

1.2.2 使用迭代器区间构造对象

vector<int> v2(++v.begin(), --v.end());		//利用迭代器区间构造对象————区间左闭右开 
// 2 3 4
string s("hello world");
vector<char> v3(s.begin(), s.end());		//其它容器的迭代器只要类型匹配同样适用

vector<int> v4;
v4.assign(s.begin(), s.end());				//assign接口类似————中文意思为分配

以上示例中,提到了利用迭代器区间构建对象这种方式是左闭右开的,例如:(v.begin() + 1, v.begin() + 4),这代表下标为[2, 5)的元素。

只要迭代器的类型与vector所存储的元素类型相同,就可以使用迭代器区间构建新的vector对象。例如上面提到的char类型的vector和char类型的string是可以匹配的。

在这里插入图片描述

1.2.3 vector的初始化

void test_vector2()
{
	vector<int> v;
	v.reserve(10);//开空间改变容量,但不初始化
	//错误访问——————下标引用操作符会检查插入位置是否合法,即小于_size
	//for (size_t i = 0; i < 10; i++)
	//{
	//	v[i] = i;
	//}
	//正确访问
	for (size_t i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

	v.resize(20);//开空间+初始化
}

上述示例中,提到了我在OJ题中经常弄糊涂的内容,使用reserve进行初始化是最好的,因为不会像resize那样,擅自向vector中填入值,但使用后记得不要使用下标引用操作符去访问,而是使用push_back

1.2.4 insert\查找\排序

void test_vector3()
{
	int a[] = { 1,2,3,4,5 };
	vector<int> v(a, a + 5);
	//头插
	v.insert(v.begin(), 0);			//第一个参数传入的是迭代器
	//在2前面插入
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);		//find函数位于算法库中algorithm
	if (pos != v.end())		//查找失败会返回end位置的迭代器
	{
		v.insert(pos, 20);
	}
  // 0 1 20 2 3 4 5  
	//sort排序
	sort(v.begin(), v.end());	// 0 1 2 3 4 5 20 
	sort(v.begin(), v.end(), greater<int>());		//greater<int>是一个仿函数类,需要调用库函数是functional
  // 20 5 4 3 2 1 0
}

上述例子注释写得很清楚,印象最深的是二叉树后序遍历可以巧用头插获取返回结果。下面主要讲解sort的第三个参数:

sort函数的第三个参数是可选的比较函数,用于指定排序时的元素比较规则。当不指定比较函数时,默认使用小于号进行比较,即升序。

sort(v.begin(), v.end(), greater<int>())中,greater<int>()是一个函数对象,用于指定降序排序的比较规则。greater<int>()是一个模板类,它重载了函数调用运算符operator(),实现了比较规则。对于两个元素x和y,如果greater<int>()(x, y)返回true,则x会被排在y之前。

因此,sort(v.begin(), v.end(), greater<int>())实现了对容器v进行降序排序的操作,greater<int>()是用于指定比较规则的函数对象,它实现了元素的比较运算。

下面是一个简单的实现:

template<typename T>
struct greater
{
    bool operator()(const T& x, const T& y) const
    {
        return x > y;
    }
};

这个定义了一个模板类greater,它有一个函数调用运算符operator()operator()接受两个参数xy,表示要比较的两个元素,它的返回值是一个bool类型,表示x是否应该排在y之前。在这个实现中,operator()的比较规则是x大于y,即从大到小排序。

1.2.5 erase删除

void test_vector4()
{
	int a[] = { 1,2,3,4,5 };
	vector<int> v(a, a + 5);
	//头删
	v.erase(v.begin());		//参数传入下标位置的迭代器,或迭代器区间
	//删除2
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		v.erase(pos);
	}
}

上述例子中主要写了erase的使用方式,参数是一个vector的迭代器。

二、vector模拟实现

在这里插入图片描述

这个 vector 类中包含了:构造函数、拷贝构造、使用迭代器区间初始化的构造函数、析构。

实现了普通迭代器和只读迭代器,及其对应的begin()end()函数。

这个类中的成员变量为私有类型的普通迭代器类型的。记录了vector开始位置、最后一个数据的下一个位置、最大容量的下一个位置。

size()capacity()能返回容器的元素个数和已开辟空间大小。

reserve()用于为容器重新分配空间,如果分配的空间小于现有空间容量,则不处理。否则,会重新分配空间,拷贝现有数据到新空间,并修改成员变量。

insert()用于向容器中插入元素,插入时需要将所在位置及其以后的元素全部向后移动,移动时是从后往前。

erase()用于删除某个元素,参数为要删除元素的迭代器。删除后还需要从前往后开始,逐一将元素向前移动。

resize()用于调整空间大小,并将未初始化的位置赋予指定的值。当调整的空间小于现有元素个数,会舍弃掉多出的元素。当调整的大小介于元素个数和有效空间之间时,会初始化这些多出来的部分。当调整的大小大于有效空间时,会重新开辟空间,并将现有数据拷贝过去,然后初始化多余部分。

其他接口比较简单,就不再单独描述。

	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(v.capacity());
			for (const auto e : v) {
				push_back(e);
			}
		}
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last) {
				push_back(*first);
				first++;
			}
		}
		~vector() {
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}
		iterator begin() {
			return _start;
		}
		iterator end() {
			return _finish;
		}
		const_iterator begin()const {
			return _start;
		}
		const_iterator end()const {
			return _finish;
		}
		size_t capacity() const{
			return _endofstorage - _start;
		}
		size_t size() const{
			return _finish - _start;
		}
		void reserve(size_t num) {
			if (num > capacity()) {
				size_t sz = size();
				T* tmp = new T[num];
				memcpy(tmp, _start, sz * sizeof(T));
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + num;
			}
		}
		iterator insert(iterator pos, const T& num){
			assert(pos >= begin() && pos <= end());
			if (_finish == _endofstorage) {
				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 = num;
			_finish++;
			return pos;
		}
		iterator erase(iterator pos) {
			assert(pos >= begin() && pos < end());
			//删除指定下标的数据,并把其后的数据依次向前挪动
			iterator it = pos + 1;
			while (it != end()){
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
		}
		void push_back(const T& num){
			insert(end(), num);
		}
		T& operator[](size_t i) {
			assert(i < size());
			return *(_start + i);
		}
		void swap(vector<T>& v) {
			std::swap(v._start, _start);
			std::swap(v._finish, _finish);
			std::swap(v._endofstorage, _endofstorage);
		}
		vector<T>& operator=(vector<T> v) {
			swap(v);
			return *this;
		}
		void resize(size_t n, const T& val = T()) {
			//开的空间小于size(把超出范围的舍弃)介于size和capacity(初始化_finish以后的空间)
			//大于capacity(要重新开空间,并且初始化_finish以后的空间)
			if (n <= size()){
				_finish = _start + n;
			}
			else{
				if (n > capacity()){
					reserve(n);
				}
				while (_finish < _start + n) {
					*_finish = val;
					_finish++;
				}
			}
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};

在这里插入图片描述

三、reverse浅拷贝Bug

不难发现,在上述模拟实现的vector::reverse()中,如果模版类型T为std::string,那string中含有一些指针成员变量,通过memcpy将其浅拷贝到新空间后,又进行了一次delete,在生命周期结束时,也会进行delete,就会造成崩溃。

3.1 解决方案

既然要销毁原有空间,那为何不通过std::move将左值转换为右值,然后拷贝到新空间去。

下面通过伪代码进行举例:

template <typename T>
void MyVector<T>::reserve(size_t newCapacity)
{
    if (newCapacity <= m_capacity)
        return;

    T* newData = new T[newCapacity];
    for (size_t i = 0; i < m_size; i++)
    {
        newData[i] = std::move(m_data[i]); // 深复制
    }

    delete[] m_data;
    m_data = newData;
    m_capacity = newCapacity;
}

上述代码没有考虑使用迭代器成员变量,容量和有效数据数量均为size_t类型,数据类型为T*

在这个实现中,我们使用了 std::move 函数将 m_data[i] 的内容移动到 newData[i] 中,从而进行深复制,避免了多个 std::string 对象共享同一块内存空间的问题。同时,在析构函数中也只需要简单地使用 delete[] 删除 m_data 指向的内存即可。

3.2 move函数

std::move 是一个 C++11 中引入的函数,它能够将一个对象的值转移到另一个对象中,同时将原对象置于一种“移动状态”,从而避免不必要的对象复制和销毁。

std::move 本质上是将一个左值引用转换成右值引用。在 C++ 中,左值引用是指向左值的引用,右值引用是指向右值的引用。左值是可以取地址的、有持久性的、具名的、具有明确定义的生命周期的值,而右值则是无法取地址的、临时的、没有名称的、生命周期不确定的值。在 C++11 中,我们可以通过使用 && 运算符来声明右值引用。

具体来说,当我们调用 std::move 函数时,它将接受一个左值引用,并返回一个右值引用,表示该对象的值可以被移动。通常情况下,我们会将返回的右值引用绑定到另一个对象上,从而将原对象的值移动到新对象中。例如:

std::vector<int> v1{1, 2, 3};
std::vector<int> v2 = std::move(v1); // 将 v1 的值移动到 v2 中

需要注意的是,在使用 std::move 移动对象时,只会移动对象的值,也就是说会将对象的成员变量的值复制到新的内存位置,但不会复制对象的状态,比如对象的引用计数、对象的资源句柄等等。移动完成后,原对象的值会被置为“移后”的状态,这个状态下对象的行为是未定义的,我们不能再对其进行读取或修改。在移动一个对象之后,如果我们需要继续使用该对象,就必须重新对其进行赋值或初始化。

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

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

相关文章

品牌软文怎么写?教你几招

软文是什么&#xff1f;软文的本质就是广告&#xff0c;当然不是明晃晃的推销&#xff0c;而是自然隐晦地植入产品信息&#xff0c;引导更多用户自愿下单。 品牌软文对于写手的经验、内容的质量要求都相对较高&#xff0c;否则写出来的软文无法达到预期的效果。品牌软文怎么写…

一个古老的html后台的模板代码

效果图下&#xff1a; css部分代码&#xff1a;/* CSS Document / body{font-family:“宋体”, Arial,Verdana, sans-serif, Helvetica;font-size:12px;margin:0;background:#f4f5eb;color:#000;} dl,ul,li{list-style:none;} a img{border:0;} a{color:#000;} a:link,a:visit…

[css]通过网站实例学习以最简单的方式构造三元素布局

文章目录二元素布局纵向布局横向布局三元素布局b站直播布局实例左右-下 布局左-上下 布局上下-右 布局方案一方案二后言二元素布局 在学习三元素布局之前&#xff0c;让我们先简单了解一下只有两个元素的布局吧 两个元素的相对关系非常简单&#xff0c;不是上下就是左右 纵向布…

Anaconda配置Python科学计算库SciPy的方法

本文介绍在Anaconda环境中&#xff0c;安装Python语言SciPy模块的方法。 SciPy是基于Python的科学计算库&#xff0c;用于解决科学、工程和技术计算中的各种问题。它建立在NumPy库的基础之上&#xff0c;提供了大量高效、易于使用的功能&#xff0c;包括统计分析、信号处理、优…

用一个例子告诉你 怎样在spark中创建累加器

目录 1.说明 1.1 什么是累加器 1.2 累加器的功能 2. 使用累加器 3. 累加器和reduce、fold算子的区别 1.说明 1.1 什么是累加器 累加器是Spark提供的一个共享变量(Shared Variables) 默认情况下&#xff0c;如果Executor节点上使用到了Driver端定义的变量(通过算子传…

Redis常用命令及数据类型参数

1. 针对于string SET key value / GET key SET k1 v1 GET k1 // v1String是二进制安全的&#xff0c;是可变长度的&#xff0c; 底层类似于ArrayList 是可扩容的&#xff0c;最大存储内存为 512MB。 2. 判断key中是否存在某个内容 EXISTS key SET k1 v1 EXISTS k1 // …

Noah-MP陆面过程模型建模方法与站点、区域模拟

陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用 熟悉模型的发展历程&#xff0c;常见模型及各自特点&#xff1b; Noah-MP模型的原理 Noah-MP模型所需的系统环境与编译环境的搭建方法您都了解吗&#xff1f;&#xff1f; linux系统操作环境您熟悉吗&…

Linux驱动中的fasync(异步通知)和fsync

一、fsync用来同步设备的写入操作&#xff0c;考虑把一块设局写入到硬盘的操作&#xff0c;如果使用write函数&#xff0c;函数返回后只能保证数据被写入到驱动程序或者内核管理的数据缓存中&#xff0c;而无法保证数据被真正写入到硬盘的存储块里。但是fync可以做到这一点&…

查找、排序、二叉树的算法,统统记录于此。

文章目录一、查找1. 无序表的顺序查找2. 折半查找3. 分块查找4. 二叉排序树BST5. 哈希表查找二、排序1. 不带哨兵的直接插入排序2. 带哨兵的直接插入排序3. 带哨兵、折半查找的直接插入排序4. 希尔排序5. 冒泡排序6. 快速排序7. 选择排序8. 堆排序9. 归并排序二叉树1. 递归先序…

八,iperf3源代码分析:状态机及状态转换过程--->运行正向TCP单向测试时的客户端代码

本文目录一、测试用命令二、iperf3客户端状态机中各个状态解析状态机迁移图运行正向TCP单向测试时的客户端的状态列表三、iperf3客户端状态机迁移分析A-初始化测试对象&#xff08;NA--->初始化状态&#xff09;:B-建立控制连接&#xff0c;等待服务端PARAM_EXCHANGE的指令&…

西电机试数据结构核心算法与习题代码汇总(机考真题+核心算法)

文章目录前言一、链表问题1.1 反转链表1.1.1 题目1.1.2 代码1.2 多项式加减法1.2.1 题目1.2.2 代码二、队列和栈2.1 学生退学2.1.1 问题2.1.2 代码三、矩阵和串题目3.1 矩阵对角线求和3.1.1 问题3.1.2 代码四、排序问题4.1 多元素排序4.1.1 问题4.1.2 代码五、二叉树5.1 相同二…

synchronize优化偏向锁

偏向锁 轻量级锁在没有竞争时&#xff08;只有自己一个线程&#xff09;&#xff0c;仍然会尝试CAS替换mark word&#xff1b; 会造成一定的性能的损耗&#xff1b; JDK6之中引入了偏向锁进行优化&#xff0c;第一次使用时线程ID注入到Mark word中&#xff0c;之后重入不再进…

旅游预约APP开发具有什么优势和功能

旅游活动目前正在作为广大用户休闲娱乐的一个首选内容&#xff0c;不仅是公司团建活动可以选择旅游&#xff0c;而且一些节假日也可以集结自己的亲朋好友来一次快乐有趣的旅游活动&#xff0c;随着当代人对于旅游的需求呈现上升的趋势&#xff0c;也让旅游预约APP开发开始流行并…

大家都在用哪些研发流程管理软件?

全球知名的10款流程管理软件分享&#xff1a;1.IT/研发项目流程管理&#xff1a;PingCode&#xff1b;2.通用项目流程管理&#xff1a;Worktile&#xff1b;3.销售流程管理&#xff1a;Salesforce Workflow&#xff1b;4.合同流程管理&#xff1a;Agiloft&#xff1b;5.IBM Bus…

20230308 APDL Lsdyna结构学习笔记

可以用鼠标右键进行结构的旋转视图。 一、编辑材料 输入参数分别为: 密度; 弹性模量; 泊松比; 屈服应力; 切线模量 由于模型是分块建立的,这里需要把模型进行粘接 点击booleans(布尔工具) 点击Glue、areas,结构物是由面单元构成的

ReactDOM.render函数内部做了啥

ReactDOM.render函数是整个 React 应用程序首次渲染的入口函数&#xff0c;它的参数是什么&#xff0c;返回值是什么&#xff0c;函数内部做了什么&#xff1f; ReactDOM.render(<App />, document.getElementById("root")); 前序 首先看下首次渲染时候&…

二叉树OJ题目详解

根据二叉树创建字符串 采用前序遍历的方式&#xff0c;将二叉树转换成一个由括号和数字组成的字符串。 再访问每一个节点时&#xff0c;需要分情况讨论。 如果这个节点的左子树不为空&#xff0c;那么字符串应加上括号和左子树的内容&#xff0c;然后判断右子树是否为空&#x…

VBA小模板,跨表统计的2种写法

目标 1 统计一个excel 文件里&#xff0c;多个sheet里的内容2 有的统计需求是&#xff0c;每个表只单表统计&#xff0c;只是进行批量操作3 有的需求是&#xff0c;多个表得某些行列累加等造出来得文件 2 实现方法1 &#xff08;可能只适合VBAEXCEL&#xff0c;不太干净的写法…

一文带你了解,前端模块化那些事儿

文章目录前端模块化省流&#xff1a;chatGPT 总结一、参考资料二、发展历史1.无模块化引出的问题:横向拓展2.IIFE3.Commonjs(cjs)4.AMD引出的问题&#xff1a;5.CMD6.UMD7.ESM往期精彩文章前端模块化 省流&#xff1a;chatGPT 总结 该文章主要讲述了前端模块化的发展历史和各个…

css伪类和伪元素的区别

文章目录什么是css伪类和伪元素css伪类和伪元素有什么用&#xff1f;css伪类的具体使用常见的伪类伪元素的具体使用常见的伪元素什么是css伪类和伪元素 伪类和为元素是两个完全不同且重要的概念&#xff0c;它们的作用是给元素添加一些特殊的效果或样式 伪类用于选择某个元素的…