★ C++基础篇 ★ vector 类

news2024/11/14 13:54:22

Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C++基础篇第六章----vector类 ~

目录

 一  vector的介绍及使用

1.1 vector的介绍

1.2 vector的使用

1.2.1 vector的定义

1.2.2 vector iterator 的使用

 1.2.3 vector 空间增长问题

1.2.4 vector 增删查改

二  vector模拟实现

2.1 基础结构

2.2 接口

2.2.1 一些简单接口

2.2.2 push_back & pop_back & reserve

2.2.3 insert & erase

2.2.4  默认构造 & 析构

2.2.5 拷贝构造 & 赋值 

  ​编辑

2.3 迭代器

2.4 打印类

三  vector 迭代器失效问题

3.1 野指针

3.2 位置意义改变


 一  vector的介绍及使用

1.1 vector的介绍

  • vector是表示大小可以变化的数组的序列容器。(顺序表)
  • 就像数组一样,vector对其元素使用连续的存储位置,这意味着也可以使用指向其首元素指针上偏移量来访问其元素,并且与在数组中一样有效。但与数组不同的是,它们的大小可以动态变化,容器会自动处理它们的存储

1.2 vector的使用

1.2.1 vector的定义
构造函数声明接口说明
vector();无参构造
vector(size_type n, const value_type& val = value_type();构造并初始化n个val
vector (const vector& x);拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造
1.2.2 vector iterator 的使用
iterator的使用接口说明
begin()获取第一个数据位置的iterator/const_iterator
end()获取最后一个数据的下 一个位置的iterator/const_iterator
rbegin()获取最后一个数据位置的reverse_iterator
rend()获取第一个数据前一个位置 的reverse_iterator

和string相同的三种遍历方法~:

void test_Vector()
{
	vector<int> v1;
	vector<int> v2(10, 1);
	vector<int> v3(++v2.begin(), --v2.end());

	for (size_t i = 0; i < v3.size(); i++)
	{
		cout << v3[i] << " ";
	}
	cout << endl;

	vector<int>::iterator it = v3.begin();
	while (it != v3.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;
}
 1.2.3 vector 空间增长问题
容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize改变vector的size
reserve改变vector的capacity
  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题(至少开n,可以开的比n大。)
  • 若reserve开的空间比capacity小,string可能缩容可能不缩容,但至少比size大,而vector不会缩容。
  • resize在开空间的同时还会进行初始化,影响size,若比原size小,会删数据,若比capacity大会扩容。
1.2.4 vector 增删查改
vector增删查改接口说明
push_back尾插
pop_back尾删
find查找。(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[] / at像数组一样访问
clear清空
front / back获取首末元素

注意:vector没有流插入流提取。

Q:能否用vector<char>代替string~

A:不行~ string末尾带 ‘ /0 ’,string还有编码和初始化等的需求~

二  vector模拟实现

2.1 基础结构

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

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

2.2 接口

2.2.1 一些简单接口
size_t size() const
{
	return _finish - _start;
}
size_t capacity() const
{
	return _end_of_storage - _start;
}
bool empty()
{
	return _start == _finish;
}
void clear() // 清空
{
	_finish = _start;
}

T& operator[](size_t n)
{
	assert(n < size());
	return _start[n];
}
const T& operator[](size_t n) const
{
	assert(n < size());
	return _start[n];
}
void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}
2.2.2 push_back & pop_back & reserve
void push_back(const T& x) //x可能是int char vector<int>...
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*_finish = x;
	++_finish;
}
void pop_back()
{
	assert(!empty());
	--_finish;
}
void reserve(size_t n)
{
	if (n > capacity())
	{
        // 需记录原size,指向新空间后size会变~
		size_t oldsize = size();

		// 创建并拷贝
		T* tmp = new T[n];
        // 如用memcpy拷贝一些自定义类型会出现浅拷贝问题
		//memcpy(tmp, _start, size() * sizeof(T));
        for (size_t i = 0; i < oldsize; i++) //需要一个个深拷贝
        {
				tmp[i] = _start[i];
        }

		// 释放旧空间,指向新空间~
		delete[] _start;
		_start = tmp;

		// 改变量~
		_finish = tmp + oldsize;
		_end_of_storage = tmp + n;
	}
}

2.2.3 insert & erase

 首先先来看一个错误代码~

void insert(iterator pos, const T& x)
{
    assert(pos >= _start);
    assert(pos <= _finish);
    // 扩容
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	iterator end = _finish - 1;
	while (end >= pos) // 挨个往后挪
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}

以上代码问题是如需要扩容,迭代器pos的位置就会失效,留在原先被释放的vector中~故须在扩容时记录pos的相对位置

// 扩容
if (_finish == _end_of_storage)
{
	size_t len = pos - _start;
	reserve(capacity() == 0 ? 4 : 2 * capacity());
	pos = _start + len;
}

 erase实现~ pos后的挨个往前挪~

void erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	iterator it = pos + 1;
	while (it != end())
	{
		*(it - 1) = *it;
		++it;
	}
	--_finish;                                                                                        
}
2.2.4  默认构造 & 析构
vector()
{}

//C++11 强制生成默认构造
vector() = default;// 也可以这样写~

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

迭代器构造

// 类模板的成员函数,还可以继续是函数模版
template<class InputIterator> // 写成模板就可以传任意类型的迭代器
vector(InputIterator first, InputIterator last)
{
	while (first != last)// 像链表之类的就不能用 first < last
	{
		push_back(*first);
		++first;
	}
}

// 用处
vector<int> v1(v.begin() + 1, v.end() - 1);//不要v首尾的构造

n个val构造

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

 注意:若用10个1之类的构造情况需要加u指定或再增加一个构造,否则编译器会调迭代器构造。

vector<int> v(10u, 1);
vector(int n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

2.2.5 拷贝构造 & 赋值 

不写拷贝构造的情况下,编译器只会浅拷贝,后续操作就会出问题,所以需要手写深拷贝

  
vector(const vector<T>& v)
{
	reserve(v.size());// 先开好空间
	for (auto& e : v)
	{
		push_back(e);// 一个个深拷贝
	}
}

赋值~:

vector<T>& operator=(const vector<T>& v)
{
	if(this != &v)
	{
		clear();
		reserve(v.size());
		for (auto& e : v)
		{
			push_back(e);
		}
	}
	return *this;
}

也可以使用更方便的现代写法 :D

vector<T>& operator=(const vector<T>& v)
{
	swap(v);
	return *this;
}

2.3 迭代器

两组begin & end~

iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator begin() const
{
	return _start;
}
const_iterator end() const
{
	return _finish;
}

2.4 打印类

template<class T>
void print_vector(const vector<T>& v)
{
	// 规定,没有实例化的类模板里面取东西,编译器不能区分
	// 这里const_iterator是类型还是静态成员变量,要加typename
	typename vector<T>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

推广一下~ 可以变成:

template<class Container>
void print_container(const Container& v)
{
	auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

三  vector 迭代器失效问题

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

3.1 野指针

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

再举几个例子~

int main()
{
	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);
	
	return 0;
}

以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。

解决方式就是在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。

3.2 位置意义改变

下程序本意是再2位置插入20,并将2乘10,而在insert后原来指向2的迭代器指向了20,意义改变了,导致程序崩溃。vs下会强制检查,而Linux下不会直接崩溃

int main()
{
	vector<int> v{ 1,2,3,4,5,6 };
	
	int x;
	cin >> x; // 2
	auto p = find(v.begin(), v.end(), x);
	if (p != v.end())
	{
		v.insert(p, 20); // 1 20 2 3 4 5 6
		(*p) *= 10; // 程序崩溃
	}
	return 0;
}

指定位置元素的删除操作--erase的迭代器失效

以下是一个删除所有偶数的函数

void test_class_vector()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	print_vector(v);
	// 删除所有偶数
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			v.erase(it);
		}
		++it;
	}
	print_vector(v);
}

此函数vs下若用库中的vector会直接报错,若用自己实现的则会有不同结果。g++中也会有不同结果,但不会强制检查。erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end 的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。故erase后必须更新迭代器才能继续使用

正确代码:~

void test_class_vector()
{
	std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	print_container(v);
	// 删除所有偶数
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			it = v.erase(it); // erase返回下一个迭代器
		}
		else
		{
			++it;
		}
	}
	print_container(v);
}

~完~

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

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

相关文章

网站配置了https证书,但浏览器访问时却访问了http

是由于缺少强制将 HTTP 请求重定向到 HTTPS 的规则 # HTTP 到 HTTPS 重定向配置 server {listen 80;server_name www.xlqd.site xlqd.site;return 301 https://$host$request_uri; } # 那么你原来的server块就要删除 listen 80;

【学习笔记】A2X通信的协议(十二)- PC5信令协议数据错误处理

目录 10. 处理未知、未预见和错误的PC5信令协议数据 10.1 总则 10.2 消息过短或过长 10.2.1 消息过短 10.2.2 消息过长 10.3 未知或未预见的消息类型 10.4 非语义性强制信息元素错误 10.5 非命令性消息部分中的未知和未预见的IE 10.5.1 消息中未知的IEI 10.5.2 乱序的…

虚幻蓝图 | 游戏开发 Randomize Height

当地图上有无数个收集物【如水晶】&#xff0c;一键随机化高度 应用前 应用后 同理带seed的随机化位置摆放如下&#xff1a; https://www.youtube.com/watch?vkGpsMEMDrjQ

【Hot100】LeetCode—142. 环形链表 II

目录 1- 思路快慢指针推导 2- 实现⭐142. 环形链表 II——题解思路 3- ACM 实现 原题连接&#xff1a;141. 环形链表 1- 思路 快慢指针推导 ① 利用快慢指针&#xff0c;定位环② 根据环&#xff0c;从头出发一个指针&#xff0c;从环处出发一个指针 两者相遇的地方就是环的入…

文献引用数据集分类(GCN)

#基于点的任务 from torch_geometric.datasets import Planetoid from torch_geometric.transforms import NormalizeFeatures import matplotlib.pyplot as plt from sklearn.manifold import TSNE import torch import torch.nn.functional as F from torch.nn import Linear…

Mysql原理与调优-事务与MVCC

目录 1.事务 1.1 什么是事务 1.2 事务隔离级别 1.2.1 事务并发执行可能出现的问题 1.2.2 隔离级别 1.2.3 如何查看和设置事务的隔离级别 1.2.3 快照读和当前读 2.MVCC 2.1 版本链机制 2.2 Read View 2.2.1 Read View读取事务的原则 2.4 Read Committed级别查询 2.5…

ACM MM 2024,复旦腾讯优图等提出MDT-A2G,可根据说话语音同步生成手势

复旦&腾讯优图等提出MDT-A2G&#xff0c;这是一个专门用来生成与语音同步手势的先进模型。想象一下&#xff0c;当我们说话时&#xff0c;身体自然会做出手势。这个模型的目的是让计算机也能像人类一样&#xff0c;根据说话的内容来生成合适的手势。它的运作方式像是一个人…

python的多线程实现高速下载PDB数据集

多线程下载数据 最近在某个网站上写了个shell脚本来下载数据集&#xff0c;内容量不大&#xff0c;但是下载的特别的慢&#xff0c;于是想到用多线程下载&#xff0c;发现快了很多。本文主要让大家清楚python中的几个模块区别和关于程序加速的一些方法&#xff0c;以及多线程下…

YOLOv8_det/seg/pose/obb推理流程

本章将介绍目标检测、实例分割、关键点检测和旋转目标检测的推理原理,基于onnx模型推理,那么首先就需要了解onnx模型的输入和输出,对输入的图片需要进行预处理的操作,对输出的结果需要进行后处理的操作,这部分内容在我的另一个专栏《YOLOv8深度剖析》中也有介绍,如果对YO…

【数学建模备赛】Ep06:多元线性回归分析

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、多元线性回归分析&#xff1a;☀️☀️☀️1. 回归分析的介绍和分类1.1相关性1.2 相关性≠因果性1.3 因变量Y1.4 自变量X 2. 回归分析的三条使命3. 数据的分类以及数据的来源 后序还在更新中~~~三、总结&#xff…

通用定时器 输出pwm原理

这个是 输出pwm 判断条件&#xff0c;比较cnt和arr的值&#xff0c;来判断当前是否输出有效电平 这个是判断pwm的输出条件排列模式&#xff0c;可以选pwm1或者pwm2模式&#xff0c;然后每个模式有递增计数还是递减计数&#xff0c;例如&#xff1a;根据设置的是pwm1模式的 递增…

基于django的双选宠物托管服务平台/python宠物托管系统

摘 要 伴随着社会以及科学技术的发展&#xff0c;互联网已经渗透在人们的身边&#xff0c;网络慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着网络飞速的发展&#xff0c;系统管理这一名词已不陌生&#xff0c;越来越多的双选宠物托管服务等机构都会定制一款属于…

生成式人工智能在无人机群中的应用、挑战和机遇

人工智能咨询培训老师叶梓 转载标明出处 无人机群在执行人类难以或危险任务方面有巨大潜力&#xff0c;但在复杂动态环境中学习和协调大量无人机的移动和行动&#xff0c;对传统AI方法来说是重大挑战。生成式人工智能&#xff08;Generative AI, GAI&#xff09;&#xff0c;凭…

尚品汇-前端面包屑平台属性、排序处理(三十三)

目录&#xff1a; &#xff08;1&#xff09;面包屑处理平台属性 &#xff08;2&#xff09;排序处理 &#xff08;2&#xff09;单点登录业务介绍 &#xff08;1&#xff09;面包屑处理平台属性 前端显示&#xff1a;面包屑显示效果 搜list搜索方法继续添加返回的平台属性…

el-table表格可编辑

需求&#xff1a;使用elementui的表格组件&#xff0c;实现某些列可以输入或者下拉选择修改行数据 //tabClickLabel 只是作为区分是否可以修改列的条件 <el-tableref"table":data"tableData":header-cell-style"{ background: #F5F7FA, height: 3…

理解Java中的for-each循环:为什么有时候不能修改数组元素?

前言&#xff1a; 刚开始学习编程的时候&#xff0c;我们都会老老实实用 for-i循环&#xff0c;后面接触到for-each的时候&#xff0c;发现竟然还可以再省一点代码&#xff0c;慢慢也会开始学会用for-each。但其实&#xff0c;在有些即使是需要从头遍历的场景&#xff0c;for-e…

uva455 输入格式说的不明白多加空格

提要&#xff1a;题目你看原题有输出格式的坑&#xff0c;本来已经写好代码 结果被这**格式整半天才好&#xff01;&#xff01; 那个xuhanx是我错了那么多次主打一个锲而不舍笑死我了。简单讲一下核心代码就一行 x[j] ! pattern[j % i] 这个是原理比较好理解吧。 建议就是…

SpringBoot和Redis的交互数据操作 以及 Redis的持久化/删除策略和缓存问题

一、SpringBoot和Redis/MySQL的数据交互 说明&#xff1a; 在 SpringBoot2.x 之后&#xff0c;原来使用的jedis 被替换为了 lettuce SpringBoot/Spring和Redis之间的交互简称为Spring-data-redis,有两种方式提供选择&#xff1a; jedis &#xff1a;采用的直连&#xff0c;多个…

一个专门用于Java服务端图片合成的工具,支持图片、文本、矩形等多种素材的合成,功能丰富强大(附源码)

前言 在数字化营销的当下&#xff0c;企业对于图片处理的需求日益增长。然而&#xff0c;传统的图片处理方式往往需要复杂的操作和专业的技术&#xff0c;这不仅增加了工作量&#xff0c;也提高了时间成本。 为了处理这一问题&#xff0c;一款能够简化图片合成流程的软件应运…

《书生大模型实战营第3期》进阶岛 第4关: InternVL 多模态模型部署微调实践

文章大纲 写在前面&#xff08;什么是InternVL&#xff09;InternVL 模型总览Dynamic High ResolutionPixel ShuffleInternVL 部署微调实践准备InternVL模型准备环境准备微调数据集InternVL 推理部署攻略使用pipeline进行推理推理后 InternVL 微调攻略准备数据集配置微调参数开…