【C++】STL--vector

目录

vector的使用

vector的定义

vector iterator的使用

vector空间增长问题

vector增删查改

vector深度剖析及模拟实现

vector核心接口模拟实现

使用memcpy拷贝问题

迭代器失效问题


vector的使用

vector的定义

 C++中,vector是一个模版,第一个参数是类型T,第二个参数暂且不考虑。

vector<int> v; 

我们可以通过上面这句代码将参数T给成int,那么vector中所包含的元素就是int。 

(重点)1.vector()

无参构造

(重点)2.vector (const vector& x)

拷贝构造

3.vector(size_type n, const value_type& val = value_type())

构造并初始化n个val

4.vector(size_type n, const value_type& val = value_type()

使用迭代器进行初始化构造

vector iterator的使用

(重点)1.begin+end

获取第一个数据位置的iterator/const_iterator,获取最后一个数据的下一个位置的iterator/const_iterator

2.rbegin+rend

获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

vector空间增长问题

1.size和capacity

size用来获取数据个数,capacity获取容量大小

2.empty

判断是否为空

(重点)3.resize

改变vector的size

(重点)4.reserve

改变vector的capacity

关于空间增长问题,有几个点需要注意:

1.capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

2.reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。

3.resize在开空间的同时还会进行初始化,影响size。


测试vector的扩容机制

void TestVectorExpand() 
{size_t sz; vector<int> v; sz = v.capacity();cout << "making v grow:\n"; for (int i = 0; i < 100; ++i) {v.push_back(i);if (sz != v.capacity()) {sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}

在vs2019下的运行结果:

结论:vs下使用的STL基本是按照1.5倍方式扩容!

在g++下的运行结果:

结论:linux下使用的STL基本是按照2倍方式扩容!

//如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
//就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{vector<int>v;size_tsz=v.capacity();v.reserve(100);//提前将容量设置好,可以避免一遍插入一遍扩容cout<<"making bar grow:\n";for(inti=0;i<100;++i){v.push_back(i);if(sz!=v.capacity()){sz=v.capacity();cout<<"capacity changed: "<<sz<<'\n';}}
}

vector增删查改

(重点)1.push_back

尾插

(重点)2.pop_back

尾删

 注意,vector未提供头插头删,因为那样效率很低!  

3.find

查找。(注意这个是算法模块实现,不是vector的成员接口)

4.insert

在position之前插入val

5.erase

删除position位置的数据

6.swap

交换两个vector的数据空间

7.operator[ ]

像数组一样访问。越界时会发生assert报错!

更进一步

除了将vector中的参数T初始化为int、double等内置类型外,还可以将其初始化为string、vector<int>等。

void test_vector()
{vector<string> v;string s1("苹果");v.push_back(s1);v.push_back(string("香蕉"));v.push_back("草莓");
}
vector<vector<int>> v;

下面画出这个的示意图,以便理解:

对于vector<vector<int>> v这种,我们可以把它看成一个二维数组,可以通过v[i][j]的形式去访问其成员,那么我们难免会联想到二维数组的访问也是v[i][j]这种形式,那它们有区别吗?其实,它们的区别很大

对于静态的二维数组,v[i][j]其底层是一个一维数组,数组名v表示第一行的地址,v+i表示第i行地址(从0行开始),*(v+i)表示第i行第一个元素的地址,*(*(v+i)+j)表示第i行第j个元素,即v[i][j]。

对于vector<vector<int>> v,v[i][j]可以看成两次函数调用,第一次调用相当于vv.operator[](i),其返回值为vector<int>对象,第二次调用相当于vv.operator[](i).operator[](j)。

vector深度剖析及模拟实现

如果按照之前string的习惯,start就是a,finish就是a+size,end_of_storage就是a+capacity。

vector核心接口模拟实现

#include <assert.h>namespace ghs
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}iterator begin(){return _start; }iterator end(){return _finish;}vector(){}//v2(v1)vector(const vector<T>& v){reserve(v.capacity());//引用,防止深拷贝for (auto& ch : v){push_back(ch);}}//vector<int> v = { 1,2,3,4,5,6,7,8,9 };vector(initializer_list<T> il){reserve(il.size());for (auto e : il){push_back(e);}}//类模板的成员函数可以是函数模版template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}//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);}}//和上面的构成重载vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}//v1 = v3,v是v3的拷贝vector<T>& operator=(vector<T> v){swap(v);return *this;}size_t capacity()const{return _endofstorage - _start;}size_t size()const{return _finish - _start;}~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}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 reserve(size_t n){if (n > capacity()){T* tmp  = new T[n];size_t old_size = size();//memcpy(tmp, _start, old_size * sizeof(T));for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];//这样如果T是string,走的深拷贝}delete[] _start;_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}void resize(size_t n, const T& val = T()){if (n > size()){reserve(n);//插入while (_finish < _start + n){*_finish = val;_finish++;}}else{//删除_finish = _start + n;}}void push_back(const T& val){if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = val;_finish++;}void pop_back(){/*assert(!empty());--_finish;*/erase(--end());}bool empty(){return _start == _finish;}void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());//如果扩容了,要更新pospos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = val;_finish++;}iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;//返回pos,用于迭代器更新return pos;}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};template<class T>void print_vector(const vector<T>& v) {for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;//加一个typename,告诉编译器后边那个是一个类型,否则报错//typename vector<T>::const_iterator it = v.begin();/*auto it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;for (auto c : v){cout << c << " ";}cout << endl;*/}
}

使用memcpy拷贝问题

假设模拟实现的vector中的reserve接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?

void testvector()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);v1.push_back(8);
}

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。

2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且 自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃

在上面我们自己模拟实现的扩容接口中:

void reserve(size_t n)
{if (n > capacity()){T* tmp  = new T[n];size_t old_size = size();//memcpy(tmp, _start, old_size * sizeof(T));for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];//这样如果T是string,走的深拷贝}delete[] _start;_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}
}

我们使用for循环赋值的方式进行拷贝,这样即使T是自定义类型,比如是string,那么会去调string的赋值,string的赋值是深拷贝。

迭代器失效问题

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

对于vector可能会导致其迭代器失效的操作有:

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

2. 指定位置元素的删除操作--erase

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

下面实现了删除vector中偶数的功能:

void testvector()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);v1.push_back(7);v1.push_back(8);print_vector(v1);//删除偶数vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it);}else{++it;}}print_vector(v1);
}

其中,我们每次使用了it = v1.erase(it);更新迭代器。

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Qt踩坑】ARM 编译Qt5.14.2源码-QtWebEngine

1.下载源码 下载网站&#xff1a;Index of /new_archive/qt/5.14/5.14.2/single 2.QWebEngine相关依赖 sudo apt-get install flex libicu-dev libxslt-dev sudo apt-get install libssl-dev libxcursor-dev libxcomposite-dev libxdamage-dev libxrandr-dev sudo apt-get …

Netty实现udp服务器

1、TCP与UDP通信协议 网络传输层协议有两种&#xff0c;一种是TCP&#xff0c;另外一种是UDP。 TCP是一种面向连接的协议&#xff0c;提供可靠的数据传输。TCP通过三次握手建立连接&#xff0c;并通过确认和重传机制&#xff0c;保证数据的完整性和可靠性。TCP适用于对数据准…

OpenHarmony南向开发实例:【智能可燃气体检测系统】

样例简介 本项目是基于BearPi套件开发的智能可燃气体检测Demo&#xff0c;该系统内主要由小熊派单板套件和和MQ5可燃气体检测传感器组成。 智能可燃气体检测系统可以通过云和手机建立连接&#xff0c;可以在手机上控制感应的阈值&#xff0c;传感器感知到的可燃气体浓度超过阈…

美国B2985A是德科技高阻表

181/2461/8938产品概述&#xff1a; 特点: 0.01 fA最小测量分辨率10便士&#xff1f;1000 V电源下的最大电阻测量每秒20&#xff0c;000次读数分辨率为6.5位数的测量范围图形查看模式&#xff08;仪表、图表、直方图和滚动视图&#xff09;易于使用的自动导航功能可选择最佳范…

系统更新Javahome之后,eclipse ide没有同步更新的解决方案

1、确认eclipse idea当前使用jdk 路径 &#xff1a; 2、确认Ide路径为旧的之后&#xff0c;去到eclipse的应用启动路径&#xff0c;编辑【eclipse.ini】, 在【-vmargs】之前设置vm路径&#xff08;换行为必须的&#xff09;&#xff1a; -vm C:\Program Files\Java\jdk1.8.0_1…

解决在嵌入式系统开发编译时遇到的“no definition for ‘xxx‘在xxx.o文件中”BUG

提示&#xff1a;本文章是自己折腾嵌入式系统开发过程学习记录 解决在嵌入式系统开发编译时遇到的“no definition for xxx在xxx.o文件中”BUG BUG描述一、编译开发环境开发语言和库 二、问题分析知识点说明函数声明&#xff1a;函数定义&#xff1a; 三、解决方案1. 检查函数声…

BP实战之猫狗分类数据集

目录 补充知识 python类里面的魔法方法 transforms.Resize() python里面的OS库 BP实战之猫狗分类数据集 猫狗数据集 注意事项 使用类创建自己的猫狗分类数据集 代码 实例化对象尝试 代码 结果 利用DataLoader加载数据集 BP神经网络的搭建以及对象的使用 运行结果…

HTTP与HTTPS:深度解析两种网络协议的工作原理、安全机制、性能影响与现代Web应用中的重要角色

HTTP (HyperText Transfer Protocol) 和 HTTPS (Hypertext Transfer Protocol Secure) 是互联网通信中不可或缺的两种协议&#xff0c;它们共同支撑了全球范围内的Web内容传输与交互。本文将深度解析HTTP与HTTPS的工作原理、安全机制、性能影响&#xff0c;并探讨它们在现代Web…

小程序项目思路分享爬虫

小程序项目思路分享爬虫 具体需求&#xff1a; 有这几个就行&#xff0c;门店名称门店地址门店类型&#xff0c;再加上省、市、县/区门店名称&#xff1a;storeName 门店地址&#xff1a;storeAddress 程序运行&#xff1a; honor_spider获取经纬度信息。 经纬度——>详…

SpringCloudAlibaba-整合sleuth和zipkin(六)

目录地址&#xff1a; SpringCloudAlibaba整合-CSDN博客 一、整合sleuth 1.引入依赖 在需要追踪的微服务中引入依赖&#xff0c;user、order、product <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter…

java-数组转换为List集合

方法一&#xff1a;使用 Arrays.asList() 方法 Arrays.asList() 方法可以将数组转换为一个固定大小的List。 import java.util.Arrays; import java.util.List; import java.util.ArrayList; public class ArrayToListExample { public static void main(String[] args…

AI大模型引领未来智慧科研暨ChatGPT自然科学高级应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

数据结构——线性表(链式存储结构)

语言&#xff1a;C语言软件&#xff1a;Visual Studio 2022笔记书籍&#xff1a;数据结构——用C语言描述如有错误&#xff0c;感谢指正。若有侵权请联系博主 一、线性表的逻辑结构 线性表是n个类型相同的数据元素的有限序列&#xff0c;对n>0&#xff0c;除第一元素无直接…

数据仓库与数据挖掘(第三版)陈文伟思维导图1-5章作业

第一章 概述 8.基于数据仓库的决策支持系统与传统决策支持系统有哪些区别&#xff1f; 决策支持系统经历了4个阶段。 1.基本决策支持系统 是在运筹学单模型辅助决策的基础上发展起来的&#xff0c;以模型库系统为核心&#xff0c;以多模型和数据库的组合形成方案辅助决策。 它…

021——搭建TCP网络通信环境(c服务器python客户端)

目录 前言 服务器程序 服务器程序验证过程 客户端程序 前言 驱动开发暂时告一段落了。后面在研究一下OLED和GPS的驱动开发&#xff0c;并且优化前面已经移植过来的这些驱动&#xff0c;我的理念是在封装个逻辑处理层来处理这些驱动程序。server直接操作逻辑处理层的程序。 …

labview技术交流-如何判断一个数是否为质数

问题起源 如何判断一个数是否为质数&#xff0c;其实并不难&#xff0c;只要你知道质数的定义&#xff0c;按照它的定义去编写代码就可以了。但是没有思路的人可能就会一直找不到方向&#xff0c;所以我就简单介绍一下。 还有我想吐槽的点&#xff0c;labview本来就是很小众的语…

外包干了15天,技术倒退明显

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01; 而我已经在一个企业干了四年的功能…

uniapp小程序中使用video视频播放卡顿

问题:在使用uniapp小程序的video视频播放,视频已经在播放了,但是进度条没走,还是卡顿的状态(测试ios能正常使用,安卓手机会出现此问题) 在网上找了很多方法,最多的说是用:custom-cache"false",试了并没有效果,看来和我问题不一样,后来用了个简单粗暴的方法,发现是有效…

详解Spring event如何优雅实现系统业务解耦、实现原理及使用注意项

1.概述 在我们平时的项目业务系统开发过程中&#xff0c;一个需求功能的业务逻辑经常出现主线业务和副线业务之分。比如&#xff0c;在当下移动端电商app进行注册账号操作&#xff0c;注册成功之后会发送短信、邮箱、站内信等通知&#xff0c;发放红包活动抵用券&#xff0c;推…