C++第十弹 ---- vector的介绍及使用

目录

  • 前言
  • vector的介绍及使用
    • 1. vector的使用
      • 1.1 vector的定义
      • 1.2 iterator的使用
      • 1.3 vector空间增长问题
      • 1.4 vector增删查改
    • 2. vector迭代器失效问题(重点)
  • 总结

前言

本文介绍了C++中的vector数据结构及其使用方法。

更多好文, 持续关注 ~ 酷酷学!!!


正文开始

vector的介绍及使用

  1. vector是表示可变大小数组的序列容器.
  2. 就像数组一样, vector也采用的连续存储空间来存储元素. 也就是意味着可以采用下标对vector的元素进行访问, 和数组一样高效. 但是又不像数组, 它的大小是可以动态改变的, 而且它的大小会被容器自动处理.
  3. 本质讲, vector使用动态分配数组来存储它的元素. 当新元素插入的时候, 这个数组需要被重新分配大小为了增加存储空间, 其做法是, 分配一个新的数组, 然后讲全部元素移动到这个数组, 就时间而言, 这是一个相对代价高的任务, 因为每当一个新的元素加入到容器的时候, vector并不会每次都重新分配大小.
  4. vector分配空间策略: vector会分配一些额外的空间以适应可能的增长, 因为存储空间比实际比实际需要的存储空间更大. 不同的库采用不同的策略权衡空间的使用和重新分配, 但是无论如何, 重新分配都应该是对数增长的间隔大小, 以至于在末尾插入一个元素的时候是在常数时间复杂度完成的.
  5. 因此,vector占用了更多的存储空间, 为了获得管理存储空间的能力, 并且以一种有效的方式动态增长
  6. 与其它动态序列容器相比(deque,list and forward_list) , vector在访问元素的时候更加高效, 在末尾添加和删除元素相对高效, 对于其他不在末尾的删除和插入操作, 效率更低, 比起list和forward_list 统一的迭代器和引用更好

使用STL的三个三个境界: 能用, 明理, 能拓展, 下面讲详细介绍STL – vector


1. vector的使用

我们先来查看vector的文档介绍, vector在实际中非常重要, 在实际中我们熟悉常见的接口就可以.

  • 成员函数
    在这里插入图片描述

1.1 vector的定义

在这里插入图片描述

在这里插入图片描述

代码演示:

int TestVector1()
{// constructors used in the same order as described above:vector<int> first;                                // empty vector of intsvector<int> second(4, 100);                       // four ints with value 100vector<int> third(second.begin(), second.end());  // iterating through secondvector<int> fourth(third);                       // a copy of third// 下面涉及迭代器初始化的部分,我们学习完迭代器再来看这部分// the iterator constructor can also be used to construct from arrays:int myints[] = { 16,2,77,29 };vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));cout << "The contents of fifth are:";for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)cout << ' ' << *it;cout << '\n';return 0;
}

在这里插入图片描述

1.2 iterator的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码演示:

void PrintVector(const vector<int>& v)
{// const对象使用const迭代器进行遍历打印vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}void TestVector2()
{// 使用push_back插入4个数据vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);// 使用迭代器进行遍历打印vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;// 使用迭代器进行修改it = v.begin();while (it != v.end()){*it *= 2;++it;}// 使用反向迭代器进行遍历再打印// vector<int>::reverse_iterator rit = v.rbegin();auto rit = v.rbegin();while (rit != v.rend()){cout << *rit << " ";++rit;}cout << endl;PrintVector(v);
}

1.3 vector空间增长问题

在这里插入图片描述

  1. capacity的代码在vs和g++下分别运行会发现, vs下capacity是按照1.5倍增长的, g++是按2倍进行增长的, 这个问题经常会考察, 不要固化的认为, vector增容都是2倍, 具体增长多少是根据具体的需求定义的, vs是PJ版本STL, g++是SGI版本STL.
  2. reserve只负责开辟空间, 如果确定知道需要用多少空间, reverse可以缓解vector增容的代价缺陷问题
  3. resize在开空间的同时还会进行初始化, 影响size.

可以执行下面代码在VS和g++编译器分别进行测试vector的扩容机制

// 测试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';}}
}vs:运行结果:vs下使用的STL基本是按照1.5倍方式扩容
making foo grow :
capacity changed : 1
capacity changed : 2
capacity changed : 3
capacity changed : 4
capacity changed : 6
capacity changed : 9
capacity changed : 13
capacity changed : 19
capacity changed : 28
capacity changed : 42
capacity changed : 63
capacity changed : 94
capacity changed : 141g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow :
capacity changed : 1
capacity changed : 2
capacity changed : 4
capacity changed : 8
capacity changed : 16
capacity changed : 32
capacity changed : 64
capacity changed : 128

如果已经确定好vector中要存储元素的大概个数, 可以提前将空间设置足够,就可以避免边插入边扩容导致效率低下的问题了

举个例子:


void TestVectorExpandOP()
{vector<int> v;size_t sz = v.capacity();v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容cout << "making bar 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';}}
}

1.4 vector增删查改

在这里插入图片描述

代码演示:
尾插和尾删: push_back和pop_back

void TestVector4()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);auto it = v.begin();while (it != v.end()) {cout << *it << " ";++it;}cout << endl;v.pop_back();v.pop_back();it = v.begin();while (it != v.end()) {cout << *it << " ";++it;}cout << endl;
}

在这里插入图片描述

在任意位置插入: insert和erase, 以及查找find
注意: find不是vector自身提供的方法, 是STL提供的算法模块, 使用时需要包含< algorithm >头文件

void TestVector5()
{// 使用列表方式初始化,C++11新语法vector<int> v{ 1, 2, 3, 4 };// 在指定位置前插入值为val的元素,比如:3之前插入30,如果没有则不插入// 1. 先使用find查找3所在位置// 注意:vector没有提供find方法,如果要查找只能使用STL提供的全局findauto pos = find(v.begin(), v.end(), 3);if (pos != v.end()){// 2. 在pos位置之前插入30v.insert(pos, 30);}vector<int>::iterator it = v.begin();while (it != v.end()) {cout << *it << " ";++it;}cout << endl;pos = find(v.begin(), v.end(), 3);// 删除pos位置的数据v.erase(pos);it = v.begin();while (it != v.end()) {cout << *it << " ";++it;}cout << endl;
}

operator[]+index 和 C++11中vector的新式for+auto的遍历
vector使用这两种遍历方式是比较便捷的

void TestVector6()
{vector<int> v{ 1, 2, 3, 4 };// 通过[]读写第0个位置。v[0] = 10;cout << v[0] << endl;// 1. 使用for+[]小标方式遍历for (size_t i = 0; i < v.size(); ++i)cout << v[i] << " ";cout << endl;vector<int> swapv;swapv.swap(v);cout << "v data:";for (size_t i = 0; i < v.size(); ++i)cout << v[i] << " ";cout << endl;// 2. 使用迭代器遍历cout << "swapv data:";auto it = swapv.begin();while (it != swapv.end()){cout << *it << " ";++it;}// 3. 使用范围for遍历for (auto x : v)cout << x << " ";cout << endl;
}

2. vector迭代器失效问题(重点)

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

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

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

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

int main()
{vector<int> v{ 1,2,3,4,5,6 };auto it = v.begin();v.resize(100, 8);//将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容,其it不可以在使用v.reserve(100);//reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间会引起底层容量改变,其it不可以在使用v.insert(v.begin(), 0);v.push_back(8);//插入元素期间,可能会引起扩容, 而导致原空间被释放,it不可以在使用v.assign(100, 8);//给vector重新赋值,可能会引起底层容量改变, 其it不可在使用while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

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

可以看到insert这里返回值为一个迭代器, 迭代器的位置位插入元素之后的下一个新位置, 我们可以用来接受新的迭代器

在这里插入图片描述

  1. 指定位置元素的删除操作 – erase
int main()
{int a[] = { 1,2,3,4 };vector<int> v(a, a + sizeof(a) / sizeof(int));//使用find查找3所在位置的iteratorvector<int>::iterator pos = find(v.begin(), v.end(), 3);//删除pos位置的数据,导致pos迭代器失效v.erase(pos);cout << *pos << endl;return 0;
}

在这里插入图片描述

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


当然, vs也给出了解决方案, 如果还想访问it则编译器会将新的it作为函数返回值

在这里插入图片描述
一个指向函数调用后最后被删除元素的下一个位置的迭代器。如果操作删除了序列中的最后一个元素,那么就是容器的末尾。

错误写法

int main()
{vector<int> v{ 1,2,3,4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}++it;}return 0;
}

正确写法:

int main()
{vector<int> v{ 1,2,3,4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);}++it;}return 0;
}
  1. 注意: Linux下,g++编译器对迭代器失效的检测并不是非常严格, 处理也没有vs下极端.

下面来看看Linux下的迭代器

  • 扩容之后, 迭代器已经失效了, 程序虽然可以运行, 但是运行结果已经不对了
int main()
{vector<int> v{1,2,3,4,5};for(size_t i = 0; i < v.size(); ++i)cout << v[i] << " ";cout << endl;auto it = v.begin();cout << "扩容之前,vector的容量为: " << v.capacity() << endl;// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效 v.reserve(100);cout << "扩容之后,vector的容量为: " << v.capacity() << endl;// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会// 虽然可能运行,但是输出的结果是不对的while(it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}程序输出:
1 2 3 4 5 
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5 409 1 2 3 4 5
  • erase删除任意位置代码后, linux下迭代器并没有失效, 因为空间还是原来的空间, 后序元素往前搬移了, it的位置还是有效的
int main()
{vector<int> v{ 1,2,3,4,5 };vector<int>::iterator it = find(v.begin(), v.end(), 3);v.erase(it);cout << *it << endl;while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}程序可以正常运行,并打印:
4
4 5

但是, erase删除的迭代器如果是最后一个元素, 删除之后it已经超过end, 此时迭代器是无效的, ++it导致程序崩溃

int main()
{vector<int> v{1,2,3,4,5};// vector<int> v{1,2,3,4,5,6};auto it = v.begin();while(it != v.end()){if(*it % 2 == 0)v.erase(it)}for(auto e : v)cout << e << " ";cout << endl;return 0;
}========================================================
// 使用第一组数据时,程序可以运行
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
1 3 5 
=========================================================
// 使用第二组数据时,程序最终会崩溃
[sly@VM-0-3-centos 20220114]$ vim testVector.cpp 
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
Segmentation fault

从上述三个例子中可以看到: SGI STL中, 迭代器失效后, 代码并不一定会崩溃, 但是运行结果肯定不对, 如果it不在begin和end范围内, 肯定会崩溃的.


  • 与vector类似, string在插入+扩容操作+erase之后, 迭代器也会失效

这里代码放开后会崩溃, 因为resize到20, string会进行扩容, 扩容之后, it指向之前的旧空间就已经被释放了, 该迭代器就失效了, 后续打印时, 在访问it指向的空间程序就会崩溃.

int main()
{string s("hello");auto it = s.begin();//s.resize(20, '!');while (it != s.end()){cout << *it;++it;}cout << endl;return 0;
}

erase也是如此

int main()
{string s("hello");auto it = s.begin();while (it != s.end()){s.erase(it);//错误写法it = s.erase(it);//++it;}return 0;
}

总结一下: 迭代器的解决办法, 在使用之前, 对迭代器重新赋值即可.

总结

vector是可变大小的数组,能够动态调整其存储容量。
通过下标访问vector中元素的效率与数组相同,但其大小由容器自动管理。
在扩容时,vector会分配额外的空间,从而提高末尾插入元素的效率。
vector支持通过迭代器高效访问元素,但在增删操作中迭代器可能失效。
调用reserve可以提前分配空间,减少扩容的性能损失。
vector中的元素可以通过多种方式插入和删除,包括在中间位置插入以及尾部的增删。
在操作vector时,若要继续使用迭代器,需重新赋值以避免失效。


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

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

相关文章

面试场景题系列--(2)短 URL 生成器设计:百亿短 URL 怎样做到无冲突?--xunznux

文章目录 面试场景题&#xff1a;短 URL 生成器设计&#xff1a;百亿短 URL 怎样做到无冲突&#xff1f;1. 需求分析2. 短链接生成算法2.1 自增法2.2 散列函数法2.3 预生成法 3. 部署模型3.1 其他部署方案 4. 设计4.1 重定向响应码4.2 短 URL 预生成文件及预加载4.3 用户自定义…

代码静态检查简介

在软件开发领域&#xff0c;确保代码质量是项目成功的关键要素之一。代码静态检查作为一种重要的质量保证手段&#xff0c;通过在不运行代码的情况下&#xff0c;对代码进行自动化的分析和审查&#xff0c;帮助开发团队及时发现并修复潜在的缺陷、安全漏洞以及不符合编码规范的…

Jenkins详细使用教程

目录 1. 什么是Jenkins&#xff1f; 2. 为什么使用Jenkins&#xff1f; 3. 安装Jenkins 3.1 下载相关文件 3.2 解压Linux版本的JDK 3.3 配置JDK环境 3.4 运行jenkins.war 3.5 安装完成 4. 访问Jenkins 5. 修改密码 6. 集成JDK 7. Jenkins集成Git 7.1 使用Jenkins拉取…

7月26日贪心练习-摆动序列专题

前言 大家好&#xff0c;今天学习用贪心思想解决摆动序列问题&#xff0c;共三题&#xff0c;分享自己的思路&#xff0c;请大家多多支持 算法思想 大家可以先看看这道我们后面会讲的题看看怎么个事&#xff0c;. - 力扣&#xff08;LeetCode&#xff09; 由此题题解说明算…

若依ruoyi+AI项目二次开发

//------------------------- //定义口味名称和口味列表静态数据 const dishFlavorListSelectref([ {name:"辣度",value:["不辣","微辣","中辣","重辣"]}, {name:"忌口",value:["不要葱","不要…

JVM之对象的创建过程

目录 对象的创建&#xff1a; 对象内存分配的两种方式&#xff1a; 指针碰撞&#xff1a; 空闲列表&#xff1a; 对象的内存布局&#xff08;基本结构&#xff09;&#xff1a; 对象的访问定位&#xff1a; 主流的访问方式主要有使用句柄和直接指针两种。 对象的创建&…

基于微信小程序+SpringBoot+Vue的流浪动物救助(带1w+文档)

基于微信小程序SpringBootVue的流浪动物救助(带1w文档) 基于微信小程序SpringBootVue的流浪动物救助(带1w文档) 本系统实现的目标是使爱心人士都可以加入到流浪动物的救助工作中来。考虑到救助流浪动物的爱心人士文化水平不齐&#xff0c;所以本系统在设计时采用操作简单、界面…

FPGA实现LCD1602控制

目录 注意&#xff01; 本工程采用野火征途PRO开发板&#xff0c;外接LCD1602部件进行测试。 有偿提供代码&#xff01;&#xff01;&#xff01;可以定制功能&#xff01;&#xff01;&#xff01; 联系方式见底部 一、基础知识 1.1 引脚信息 1.2 指令 1.2.1 清屏 1.…

ubuntu实践

目录 扩容 本机上ping不通新建立的虚拟机 ssh连接 装sshd ssh客户端版本较低&#xff0c;会报key exchange算法不匹配问题 ubuntun上装docker 将centos7下的安装包改造成适配 ubuntu的包 参考文章 扩容 Hyper-V 管理器安装的ubutun扩容磁盘空间说明_hype-v磁盘扩容-…

人工智能算法工程师(中级)课程20-模型注意力机制之注意力机制的原理、计算方式与代码详解

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程20-模型注意力机制之注意力机制的原理、计算方式与代码详解。本文深入探讨了注意力机制在深度学习中的应用与原理&#xff0c;尤其聚焦于序列到序列模型的上下文中。通过直观的解释和详…

48 mysql 全局变量修改了时区, 客户端拿到的依然是旧时区

前言 这是一个 我们最近碰到的问题 在我们的一个 服务平台 查询到的时间字段 比 当前时区的当前时间多 8 小时 然后 这个问题 也是挺神奇的, navicate 上面查询到的 时间是在正常的时间 然后 查询环境变量 tz_zone 是 “08:00”, 也没有问题, 但是 客户端这边 拿到的是 当…

【HTML+CSS】HTML超链接:构建网页导航的基石

目录 什么是HTML超链接&#xff1f; 基本语法 示例 链接到另一个网页 链接到同一页面内的不同部分 常用属性 在Web开发的广阔世界中&#xff0c;HTML&#xff08;HyperText Markup Language&#xff09;作为网页内容的标准标记语言&#xff0c;扮演着至关重要的角色。而在…

nodejs安装及环境配置轨道交通运维检测系统App-OA人事办公排班故障维修

✌网站介绍&#xff1a;✌10年项目辅导经验、专注于计算机技术领域学生项目实战辅导。 ✌服务范围&#xff1a;Java(SpringBoo/SSM)、Python、PHP、Nodejs、爬虫、数据可视化、小程序、安卓app、大数据等设计与开发。 ✌服务内容&#xff1a;免费功能设计、免费提供开题答辩P…

【SpringCloud】企业认证、分布式事务,分布式锁方案落地-2

目录 高并发缓存三问 - 穿透 缓存穿透 概念 现象举例 解决方案 缓存穿透 - 预热架构 缓存穿透 - 布隆过滤器 布隆过滤器 布隆过滤器基本思想​编辑 了解 高并发缓存三问 - 击穿 缓存击穿 高并发缓存三问 - 雪崩 缓存雪崩 解决方案 总结 为什么要使用数据字典&…

对Linux目录结构的补充

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

白鲸开源CEO郭炜荣获「2024中国数智化转型升级先锋人物」称号

2024年7月24日&#xff0c;由数据猿主办&#xff0c;IDC协办&#xff0c;新华社中国经济信息社、上海大数据联盟、上海市数商协会、上海超级计算中心作为支持单位&#xff0c;举办“数智新质力拓未来 2024企业数智化转型升级发展论坛——暨AI大模型趋势论坛”数据猿“年中特别策…

数据结构_study(一)

术语 程序设计数据结构算法 数据结构&#xff1a;相互之间存在一种或多种特定关系的数据元素的集合 数据&#xff1a;输入到计算机中可以操作的对象&#xff0c;数值类型&#xff08;整型&#xff0c;浮点型&#xff09;&#xff0c;非数值类型&#xff08;字符&#xff0c;…

算法——二分查找(day10)

目录 69. x 的平方根 题目解析&#xff1a; 算法解析&#xff1a; 代码&#xff1a; 35. 搜索插入位置 题目解析&#xff1a; 算法解析&#xff1a; 代码&#xff1a; 69. x 的平方根 69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09; 题目解析&#xff1a; 老…

Linux 安装mysql-client-core-8.0

在Linux上安装mysql-client-core-8.0 安装流程 下面是安装mysql-client-core-8.0的步骤和相应的命令&#xff1a; 步骤1&#xff1a;更新系统软件源 我们首先需要更新系统的软件源&#xff0c;以确保我们能够获取到最新的软件包列表。使用以下命令更新软件源&#xff1a; …

C语言——运算符及表达式

C语言——运算符及表达式 运算符运算符的分类&#xff08;自增运算符&#xff09;、--&#xff08;自减运算符&#xff09;赋值运算符逗号运算符&#xff08;顺序求值运算符&#xff09; 表达式 运算符 运算符的分类 C语言的运算符范围很宽&#xff0c;除了控制语句和输入输出…