C++vector类 (带你一篇文章搞定C++中的vector类)

news2024/12/29 10:35:32

感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步

数据结构习题_LaNzikinh篮子的博客-CSDN博客

初阶数据结构_LaNzikinh篮子的博客-CSDN博客

收入专栏:C++_LaNzikinh篮子的博客-CSDN博客

其他专栏:c语言基础_LaNzikinh篮子的博客-CSDN博客

个人主页:LaNzikinh-CSDN博客

文章目录

  • 前言
  • 一.vector的介绍及使用
  • 二.vector的底层和实现
  • 总结

前言

我们前面讲解了标准库里的string类,讲解了他的实现,用法,现在我们来讲解另一个在标准库中的类,vector类


一.vector的介绍及使用

介绍及补充

vector就像数组一样,向量对其元素使用连续的存储位置,这意味着也可以使用指向其元素的常规指针上的偏移量来访问它们的元素,并且与在数组中一样高效。但与数组不同的是,它们的大小可以动态变化,其存储由容器自动处理。可以把vector理解成C语言中,学过的数据结构中的顺序表

vector这个类,我们把它看成顺序表,那么存放顺序表的东西不像是我们以前C语言中只能存放整形的数据,他还可以存放各种各样的数据,我甚至都可以存放一个类在里面,比如说存放,字符型浮点型等各种数据,所以说为了保证可以存放的多样性,那么他肯定是用一个那模板去完成的这个结构,所以说我们如果要创建一个这样的对象,我们用内模板的实例化,注意内模板只有显示实例化,所以我们要提前在括号内标注好它的类型

int main()
{
	vector<int> v1;
	vector<char> a;
	vector<vector<int>> a2;
	return 0;
}

很多人就会有一个疑问这个vector<vector<int>> a2,到底表示什么。

先说结果:这个是初始化了一个二维数组

初始化

最为常用的5种初始化的方式。

1.vector<int> v; //默认初始化,最常用
2.vector<int> v = {1,2,3.0,4,5,6,7};
3.vector<int> v(7,3);
4.vector<int> v(v.begin()+2,v.end()-1);

vector<vector<int>> a2

也可以用下标去证明,可以访问到

也可以来看一下底层代码

注意:因为是引用返回,所以返回的是对象的本身,所以是可以修改的

使用

其实经过前面string的学习,我们也大致了解了一些接口,在C++中这些标准库中的类模板的接口都是大体相同的,所以很多东西的接口的用法,其实都跟string是一样的,但是也有细微的区别。

两个扩容函数reserve,resize

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

	vector<int> v(10, 1);
	v.reserve(20);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.resize(15, 2);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.resize(25, 3);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.resize(5);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

对于reserve来说不存在缩容,但是在string中,他是会缩容的,对于resize来说,capacity是不改变的,size会变,若n>size(resize(n,1);),扩容插入数据,如果n<size,size会缩,但是capacity是不改变。

补充:可以用vector<char> v;来代替string s吗?

不可以,因为在string中,存在\0.

接口函数的使用就不再做过多的展示了和前面的string的用法差不多,实在不会的可以自己去看一下文档一些参数的使用变化,这里主要还是讲底层和一些细节

下面这些是一些重要的接口函数,会使用就可以了

二.vector的底层和实现

我们讲底层之前可能跟我们之前讲的很多东西有变化,但其实本质是一样的,通过观察这个底层的图片来看,vector的底层跟我们之前实现的数据结构的底层不一样,他好像不在由size,capacity来控制的,而是有三个指针去控制的,但实际上本质是一模一样的,只不过是变成了三个指针控制

iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

_finish就是我们的size, _end_of_storage就是我们的capacity

完成模拟实现时,记得先typedef我们的迭代器

typedef T* iterator;
typedef const T* const_iterator;

2.1构造,析构,拷贝构造,赋值,初始化函数

每次模拟实现的时候都先来一些比较经典的函数,先内敛函数再其他成员函数

构造函数

这里的构造函数,我们直接调用编译器器的即可,无需自己实现,这里要注意一个东西,不要写错了,我自己就犯了这样的错误。=default是用编译器生成的,=delete才是删除,要注意单词。

vector() = default;

拷贝构造

扩容函数还没有实现,但是是这个原理

vector(const vector<T>& v)
{
	reserve(v.size());
	for (auto& e : v)
	{
		push_back(e);
	}
}

初始化

他其实还存在很多种的初始化方式,我们前面也列举了,他还可以初始化一个类,所以这些我们都要实现

无论你是什么样的数据,即便你给或者没给我都可以这样用,const T& val = T();给了一个缺省值,你给任意数据给我,如果你什么都不传的话,我就用这个来初始化,如果你传了的话,那我就接收

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

类初始化,类模板的成员函数,还可以继续是函数模版,随便那个类型的迭代器都可以

这个地方用了一个迭代器类型,你要把他的迭代器传过来就可以了,无论你是哪个地方的迭代器都可以

	template <class InputIterator>
	vector(InputIterator first, InputIterator last)
	{
		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

赋值

operator=,和之前的string差不多的思想,如果不相等的话就清除,相等的话就循环,但是这样子写,感觉有一点繁琐和麻烦,所以现在有一种全新的写法插进来就可以了。

	// v1 = v3
	vector<T>& operator=(const vector<T>& v)
	{
		if (this != &v)
		{
			clear();

			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

		return *this;
	}

我直接写一个交换函数,把他们直接把它复制来,只要一个函数即可,也很省事,注意这个地方也涉及到了一个深浅拷贝的问题,我们后面会用到的。

	void swap(vector<T>& v)
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_end_of_storage, v._end_of_storage);
	}

	// v1 = v3
	//vector& operator=(vector v)
	vector<T>& operator=(vector<T> v)
	{
		swap(v);

		return *this;
	}

析构

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

2.2扩容函数reserve,resize

reserve

先用一个变量来记录一下我的size大小,然后开辟一个你给我大小的空间,之后再依次赋值,然后删去最开始的空间,注意是三个指针,然后再依次给予就可以了,思想并不难。

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

		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

为什么memcpy不可以?

因为memcpy是浅拷贝,我们之前的赋值调用了交换函数,他是深拷贝,所以这个题目我们用循环赋值的方法来解决,前拷贝对于内置类型可以,但是对于自定义类型是不行的,自定类型只能用深拷贝,拷贝

resize

如果你给了我,我就帮你初始化,没给我那么就是你那个类型的自动去初始化,我调用你的构造函数,T val = T(),让你这个类型去帮我初始化,注意因为在vector中,resize会存在缩容,所以这个地方我们实现的时候也要体现

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

2.3insert和erase(难,存在迭代器失效问题)

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

例子1:野指针

这个insert的实现,就是一个明显的迭代器失效

	void insert(iterator pos, const T& x)
	{
		assert(pos >= _start);
		assert(pos <= _finish);
		// 扩容
		if (_finish == _end_of_storage)
		{	
			reserve(capacity() == 0 ? 4 : capacity() * 2);
		}

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

他的思想是没有任何问题的和string中的insert实现是一样的,但是问题就在扩容那个地方和返回值,我们先说扩容来看一个图片就知道了

这个扩容他是创造了一个新的空间,但是我的pos这个迭代器还只向着原空间,你释放了之前那个空间迭代器也释放了,这就是一个野指针,也是造成迭代器失效的一个原因,还一个原因就是返回值,因为我们知道形参的改变不能影响实参,如果你不去设置返回值的话,我出了这个函数还是野指针,所以说我需要一个返回值来接收,这样子才是真正的改变

	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 = _start + len;
		}

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

这样就对了

例子2:位置改变

vector<int> v = { 1,2,3,4 };
v.insert(p, 20);
(*p) *= 10;

由于挪动的数据,已经不是指向原来的位置了,所以我们可以认定insert以后的迭代器全部失效了,不用再访问了

解决办法,要用返回值来更就可以了

p = v.insert(p, 20);

erase

他的实现思路和string中的erase是一样的,但是他也存在迭代器失效问题

	void erase(iterator pos)
	{
		assert(pos >= _start);
		assert(pos < _finish);

		iterator it = pos + 1;
		while (it != end())
		{
			*(it - 1) = *it;
			++it;
		}

		--_finish;
	}

如果我要删除一个数据,我的pos也会被删除,所以我们erase的迭代器失效,要用返回值来更就可以了

it = s.erase(it);

2.4头插,尾插函数

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

	*_finish = x;
	++_finish;
}

void pop_back()
{
	assert(!empty());
	--_finish;
}

2.5迭代器函数

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

2.6简单的内联函数

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

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

bool empty() const
{
	return _start == _finish;
}

2.7下标访问函数

	T& operator[](size_t i)
	{
		assert(i < size());

		return _start[i];
	}

2.8输出函数

注意:在vector中,没有流插入和流提取,所以说要自己写一个非成员函数来完成输出的工作

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

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

如果是vector<T>::const_iterator,这里不能确定const_iterator是类中重定义的类型还是静态变量,编译期间无法确定,所以需要显式用typename指定这个const_iterator是一个类型。或者直接用auto

因为vector也可以存放模板,所以

	template<class Container>
	void print_container(const Container& v)
	{

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

总结

关于vector的讲解,到这里就没有了,下次将继续推进标准库中的容器,文章的难点主要还是在于这个迭代器失效问题,把容器讲得差不多了之后会专门做一个迭代器的章节

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

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

相关文章

代码随想录27期|Python|Day54|​单调栈|​42. 接雨水|84. 柱状图中最大的矩形

42. 接雨水 根据常识可以归纳出&#xff0c;对于每一列所能够存住的水的高度 Height min(LeftMax, RightMax) - height 也就是&#xff0c;当前列的存水高度 左侧和右侧柱子的最大高度的较小值&#xff0c;减去当前列的柱子高度&#xff0c;所得到的差值。 可以验证第4列&…

spring常用注解(10)@Order

一、 1、作用 加Order()注解&#xff0c;在注解中加入数字&#xff0c;数字越小&#xff0c;优先级越高&#xff0c;最先执行。 2、使用方法 &#xff08;1&#xff09;自定义顺序 Component Order(1) public class XxxFilter extends OncePerRequestFilter{}Component Or…

什么品牌的宠物空气净化器性价比最高?352/希喂/霍尼韦尔/有哈/IAM实测对比

我开着一家猫咪咖啡馆&#xff0c;我们店貌美小猫可没少给我带来回头客~先给大家看看我的招财猫们 开了三年了&#xff0c;也是前不久店里才开始有点盈利&#xff0c;开始那段时间没少收到投诉&#xff0c;差点就干不下去了。店里养着的猫多&#xff0c;平时鱼油、冻干也没稍微…

foc入门

FOC(Field Oriented Control)磁场定向控制 foc,磁场定向控制&#xff0c;通过控制无刷电机三相电流&#xff0c;来实现对无刷电机的角度扭矩的控制。 首先先要理解无刷电机的原理&#xff0c;我们以三槽两极内转子电机进行讲解&#xff0c; 相较于有刷电机&#xff0c;无刷电…

乱弹篇(47)渔友絮语

中秋节连着国庆节长假很快就要来临&#xff0c;这对于久未远行旅游的人来说&#xff0c;真是拉满的幸福日子&#xff0c;因此本“人民体验官”特意推广人民日报官方微博文化产品《收藏&#xff01;中秋假期博物馆看展攻略》。 截图&#xff1a;来源“人民体验官”推广平台 人民…

Linux编译内核选项说明

内核功能选择 编译内核时出现的提示信息是在描述内核配置界面中的导航和操作方式。具体解释如下&#xff1a; Arrow keys navigate the menu: 使用箭头键可以在菜单中上下左右移动。 <Enter> selects submenus ---> (or empty submenus ----): 按下回车键可以选择一个…

【白话树】之 树的基本知识、存储结构和二叉树转换

快速导航 一、树的基础概念1. 树的定义&#xff1a;2. 树的特点:3. 树的常用术语&#xff1a;4. 树的简单分类&#xff1a; 二、树的存储结构1.顺序存储1) 双亲表示法2) 孩子表示法3) 双亲孩子表示法 2.链式存储1) 孩子链表表示法2) 孩子兄弟表示法 三、树、森林和二叉树的转换…

根据NVeloDocx Word模板引擎生成Word(五)

前面几篇基本上介绍完了NVeloDocx的基础用法&#xff0c;绝大部分的需求其实都是这些基础的东西&#xff0c;本篇将介绍2个不常用但是实际的业务场景&#xff1a; 1、图片列表输出&#xff1b; 比如在E6开发平台生成的客户端中&#xff0c;图片列表往往是这样显示的&#xff…

【数据结构】第八节:链式二叉树

个人主页&#xff1a; NiKo 数据结构专栏&#xff1a; 数据结构与算法 源码获取&#xff1a;Gitee——数据结构 一、二叉树的链式结构 typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left; // 左子树根节点struct BinaryT…

2024年沈阳都市圈电竞大赛 暨TGA腾讯电竞运动会辽宁省选拔赛盛大开赛

去年&#xff0c;由沈阳市体育局主办的“2023年沈阳都市圈首届电竞大赛暨TGA浑南之夏辽宁省英雄联盟选拔赛”成功举办。通过搭建赛事平台&#xff0c;营造了沈阳都市圈电竞氛围&#xff0c;促进了电子竞技全业态发展。 今年&#xff0c;“2024年沈阳都市圈电竞大赛暨TGA腾讯电…

Day24_0.1基础学习MATLAB学习小技巧总结(24)——图形对象属性值的设置和查询

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a;《MATLAB基础教程 (第三版) (薛山)》 之前的章节都是…

vue3项目实现全局国际化

本文主要梳理vue3项目实现全项目格式化&#xff0c;例如在我前面文章使用若依创建vue3的项目中&#xff0c;地址&#xff1a;若依搭建vue3项目在导航栏中切换&#xff0c;页面中所有的组件的默认语言随之切换&#xff0c;使用的组件库依旧是element-plus&#xff0c;搭配vue-i1…

LeetCode --- 414周赛

题目列表 3280. 将日期转换为二进制表示 3281. 范围内整数的最大得分 3282. 到达数组末尾的最大得分 3283. 吃掉所有兵需要的最多移动次数 一、将日期转换成二进制表示 题目本质就是将数字转成二进制字符串&#xff0c;可以类比将十进制数字的每一位拆开拼成字符串&#x…

【Redis】redis5种数据类型(list)

目录 基本介绍 命令 LPUSH LPUSHX RPUSH RPUSHX LRANGE LPOP RPOP LINDEX LINSERT LLEN LREM LTRIM LSET 阻塞版本的命令 BLPOP 内部编码 基本介绍 list相当于c的双端队列deque 区分获取和删除的区别 lindex能获取到元素的值lrem也能返回被删除元素的值 命…

一条SQL实现GPT大模型【完全看不懂】

用一条SQL实现GPT大模型&#xff0c;简直让人不可思议&#xff0c;但是俄罗斯一位名叫Quassnoi的SQL牛人做到了&#xff0c;Quassnoi每年只写一条SQL&#xff0c;但是每条SQL都非常复杂&#xff1a; 2021年&#xff0c;用SQL绘制新冠病毒的3D图片 2022年&#xff0c;用SQL模拟…

为何初创数字影像企业纷纷选择入驻孵化基地?

在当今数字化时代&#xff0c;数字影像行业正蓬勃发展&#xff0c;越来越多的初创数字影像企业如雨后春笋般涌现。而这些充满活力与创新的初创企业&#xff0c;为何纷纷选择入驻数字影像企业孵化基地呢&#xff1f; 首先&#xff0c;数字影像企业孵化基地为初创数字影像企业提供…

再次进阶 舞台王者 第八季完美童模全球赛形象大使【邱玳莹】赛场秀场超燃合集!

7月20-23日&#xff0c;2024第八季完美童模全球总决赛在青岛圆满落幕。在盛大的颁奖典礼上&#xff0c;一位才能出众的少女——邱玳莹&#xff0c;迎来了她舞台生涯的璀璨时刻。 形象大使——邱玳莹&#xff0c;以璀璨童星之姿&#xff0c;优雅地踏上完美童模盛宴的绚丽舞台&am…

基于图像级监督和自训练的跨模态肿瘤分割转换器模型|文献速递--Transformer架构在医学影像分析中的应用

Title 题目 Image-level supervision and self-training for transformer-basedcross-modality tumor segmentation 基于图像级监督和自训练的跨模态肿瘤分割转换器模型。 01 文献速递介绍 深度学习在各种医学图像分析应用中展现了出色的性能和潜力&#xff08;Chen等&…

C语言 12 函数

其实函数在一开始就在使用了&#xff1a; // 这就是定义函数 int main() { ... }程序的入口点就是main函数&#xff0c;只需要将程序代码编写到主函数中就可以运行了&#xff0c;不过这个函数只是由我们来定义&#xff0c;而不是我们来调用。 当然&#xff0c;除了主函数之…

SMT | Kriging代理模型原理及应用

前言 代理模型工具箱 (surrogate modeling toolbox, SMT) 是一个基于Python开发的第三方包&#xff0c;其中包含代理模型方法、采样技术和基准测试函数。有关SMT的详细介绍参见&#xff1a; SMT | 代理模型Python工具包推荐 SMT可实现几个与高斯过程回归相关的代理模型&#x…