当前位置: 首页 > news >正文

【C++初阶】vector容器

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、什么是vector
  • 二、vector初始化
      • 2.1 默认构造函数(常见)
      • 2.2 构造函数将n个元素拷贝给本身
      • 2.3 拷贝构造函数(常见)
      • 2.4 区间拷贝
      • 2.5 数组方式
  • 三、迭代器的使用
      • 3.1 begin + end(常见)
      • 3.2 rbegin + rend
  • 四、遍历
      • 4.1 operator[]
      • 4.2 迭代器遍历
      • 4.3 范围for
  • 五、空间增长问题
      • 5.1 size
      • 5.2 empty
      • 5.3 resize
      • 5.4 reserve
      • 5.5 swap
  • 六、vector的插入与删除
      • 6.1 push_back - 尾插
      • 6.2 pop_back - 尾删
      • 6.3 insert - 插入
      • 6.4 erase - 删除pos位置的数据
      • 6.5 clear - 清空所有数据
  • 七、几个常用算法
      • 7.1 sort - 排序
      • 7.2 reverse
      • 7.3 find
  • 八、迭代器失效问题
      • 8.1 什么是迭代器失效
      • 8.2 为什么string不存在迭代器失效问题
      • 8.3 几个常见的迭代器失效样例
      • 8.4 如何解决迭代器失效问题

一、什么是vector

  • vector容器和数组非常相似,与普通数组的区别:数组是静态空间,而vector可以动态扩展
  • 动态扩展并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间
  • 使用vector容器需要包含头文件:#include <vector>
  • vector可以存储多种不同的数据类型,是因为它是一个模板容器
    在这里插入图片描述
    通过使用模板参数,我们可以在vector中指定要存储的数据类型,例如:
vector<int> vi // 整型容器
vector<char> vc // 字符型容器
vector<double> vd // 浮点型容器
vector<string> vs // string类型容器
vector<vector<int>> // 本质是二维数组
// 等等...

类模板实例化与普通类的实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,而普通类的类名就是类型。

注意:需要注意数据类型的统一性。例如,如果创建了一个存储整数的vector,就应该只向其中存储整数类型的数据,否则可能会出现类型错误或数据损坏的问题。

二、vector初始化

2.1 默认构造函数(常见)

#include <iostream>
#include <vector>
using namespace std;
int main()
{// 默认构造函数vector<int> v;return 0;
}

默认构造的对象,sizecapacity都为0。

在这里插入图片描述

2.2 构造函数将n个元素拷贝给本身

【函数原型】

vector<T> v(n,elem);

【代码示例】

#include <iostream>
#include <vector>
using namespace std;int main()
{// 构造函数将n个元素拷贝给本身// 将3个100拷贝给本身vector<int> v(3, 100);for (int i = 0; i < v.size(); i++){cout << v[i] << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

2.3 拷贝构造函数(常见)

【函数原型】

vector (const vector& x);

【代码示例】

#include <iostream>
#include <vector>
using namespace std;int main()
{// 构造函数将3个100拷贝给本身vector<int> v1(3, 100);cout << "v1:";for (int i = 0; i < v1.size(); i++){cout << v1[i] << ' ';}cout << endl;// 拷贝构造函数// v2是v1的副本vector<int> v2(v1);cout << "v2:";for (int i = 0; i < v2.size(); i++){cout << v2[i] << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

2.4 区间拷贝

【函数原型】

vector(v.begin(),v.end());

【代码示例】

#include <iostream>
#include <vector>
using namespace std;int main()
{// 构造函数将n个元素拷贝给本身// 将5个100拷贝给本身vector<int> v1;// 写入1 2 3 4 5v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);// 区间拷贝// 拷贝v1对象中的2、3、4、5vector<int> v2(v1.begin() + 1, v1.end());cout << "v2:";for (int i = 0; i < v2.size(); i++){cout << v2[i] << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

2.5 数组方式

#include <iostream>
#include <vector>
#include <string>
using namespace std;int main()
{vector<int> v1{ 1,2,3,4,5,6 };vector<char> v2{ 'h','e','l','l','o' };vector<string> v3{ "hello", "vector" };return 0;
}

【输出结果】

在这里插入图片描述

三、迭代器的使用

3.1 begin + end(常见)

大家可以认为迭代器是指针。begin指向第一个数据的位置,end指向最后一个数据的下一个位置

在这里插入图片描述

【代码示例】

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<char> v;// 插入abcdefgv.push_back('a');v.push_back('b');v.push_back('c');v.push_back('d');v.push_back('e');v.push_back('f');v.push_back('g');vector<char>::iterator begin = v.begin();// begin指向第一个元素,对齐解引用就能得到cout << "第一个元素为:" << *begin << endl;vector<char>::iterator end = v.end();// end指向最后一个元素的下一个位置cout << "最后一个元素为:" << *(end - 1) << endl;return 0;
}

【输出结果】

在这里插入图片描述

3.2 rbegin + rend

rbeginrend是反着来的。rbegin指向的是最后一个元素,rend指向的是第一个元素的前一个位置
在这里插入图片描述

【代码示例】

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<char> v;// 插入abcdefgv.push_back('a');v.push_back('b');v.push_back('c');v.push_back('d');v.push_back('e');v.push_back('f');v.push_back('g');// vector<char>::iterator 如果认为太长// 可用autoauto vci = v.rbegin();// vci指向最后一个元素cout << "最后一个元素:" << *vci << endl;auto vcc = v.rend();// vcc指向第一个元素的前一个位置// 对其-1。就指向第一个元素cout << "第一个元素" << *(vcc - 1) << endl;return 0;
}

【输出结果】

在这里插入图片描述

四、遍历

4.1 operator[]

在这里插入图片描述

vector底层重载了下标访问操作符[],因此可以像数组一样变量

#include <iostream>
#include <vector>
using namespace std;
int main()
{vector<char> v;// 插入abcdefgv.push_back('a');v.push_back('b');v.push_back('c');v.push_back('d');v.push_back('e');v.push_back('f');v.push_back('g');// operator[]for (int i = 0; i < v.size(); i++){cout << v[i] << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

4.2 迭代器遍历

#include <iostream>
#include <vector>
using namespace std;
int main()
{vector<char> v;// 插入abcdefgv.push_back('a');v.push_back('b');v.push_back('c');v.push_back('d');v.push_back('e');v.push_back('f');v.push_back('g');// 迭代器vector<char>::iterator begin = v.begin();while (begin != v.end()){cout << *begin << ' ';begin++;}cout << endl;// 以上用for循环也是可以的for (vector<char>::iterator begin = v.begin(); begin != v.end(); begin++){cout << *begin << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

4.3 范围for

string类时,我们讲过 范围for的底层是迭代器。

#include <iostream>
#include <vector>
using namespace std;
int main()
{vector<char> v;// 插入abcdefgv.push_back('a');v.push_back('b');v.push_back('c');v.push_back('d');v.push_back('e');v.push_back('f');v.push_back('g');// 范围forfor (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

五、空间增长问题

5.1 size

功能:获取数据的有效个数

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v{ 1,2,3,4 };cout << "个数为:" << v.size() << endl;for (int i = 0; i < v.size(); i++){cout << v[i] << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

5.2 empty

功能:判断容器是否为空。如果为空返回1,否则返回0

#include <iostream>
#include <vector>
using namespace std;int main()
{// 默认构造默认有效个数size为0vector<int> vi;cout << "vi:" << vi.empty() << endl;vector<char> vc{ 'h', 'e', 'l', 'l','o' };cout << "vc:" << vc.empty() << endl;return 0;
}

【输出结果】

在这里插入图片描述

5.3 resize

【函数原型1】

resize(int num);

功能:重新指定容器的长度为num

情况1:若有效数据size > num,则会保留前num个,剩下的删除

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v{ 1,2,3,4,5,6 };cout << "改变之前的长度:" << v.size() << endl;// 重新指定容器长度// 保留前3个v.resize(3);cout << "改变后的长度:" << v.size() << endl;cout << "改变后的内容为:";for (int i = 0; i < v.size(); i++){cout << v[i] << ' ';}cout << endl;return 0;
}

【程序结果】

在这里插入图片描述

【函数原型2】

resize(int num, elem);

情况2:若num > size,则会增加有效长度。如果不指定第二个参数,默认增加的内容是0

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v1{ 1,2,3,4 };v1.resize(6);// 增加了2个,不指定第二个参数默认是0for (auto x : v1){cout << x << ' ';}cout << endl;vector<char> v2{ 'h','i' };v2.resize(6, 'x');for (auto x : v2){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

5.4 reserve

功能:改变vector容器的容量。一般都是扩容。

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;int capacity = v.capacity();for (int i = 0; i < 100; i++){// 插入100个数据v.push_back(i);// v.capacity是输出当前容量的if (capacity != v.capacity()){capacity = v.capacity();cout << "容量改变:" << capacity << '\n';}}return 0;
}

【输出结果】

在这里插入图片描述

通过以上代码我们发现:vs下的容量是按1.5倍增长的g++是按2倍增长的。

然后我们再把以上代码拿到Linux环境下测试:

在这里插入图片描述

我们发现:Linux下使用的vector是按照2倍方式扩容。

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

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;int capacity = v.capacity();// 提前开好大小为100的容量v.reserve(100);for (int i = 0; i < 100; i++){// 插入100个数据v.push_back(i);// v.capacity是输出当前容量的if (capacity != v.capacity()){capacity = v.capacity();cout << "容量改变:" << capacity << '\n';}}return 0;
}

【输出结果】

在这里插入图片描述

或者还能这样开空间:

在这里插入图片描述

5.5 swap

【函数原型】

swap(vec); // 将vec与本身的元素互换

【代码示例】

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v1{1,2,3,4,5,6};v1.reserve(100);cout << "交换前:" << endl;cout << "v1:";for (auto x : v1){cout << x << ' ';}cout << endl;vector<int> v2{ 7,8,9,10,11, 12 };cout << "v2:";for (auto x : v1){cout << x << ' ';}cout << endl;cout << "v1的容量" << v1.capacity() << endl;cout << "v2的容量" << v2.capacity() << endl;// 交换v1.swap(v2);cout << "交换后:" << endl;cout << "v1:";for (auto x : v1){cout << x << ' ';}cout << endl;cout << "v2:";for (auto x : v1){cout << x << ' ';}cout << endl;cout << "v1的容量" << v1.capacity() << endl;cout << "v2的容量" << v2.capacity() << endl;return 0;
}

【输出结果】

在这里插入图片描述

通过以上发现:swap不仅可以交换容器内容,同时还达到实用的收缩内存效果

六、vector的插入与删除

6.1 push_back - 尾插

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;// 尾插1、2、3、4v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

6.2 pop_back - 尾删

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;// 尾插1、2、3、4v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto x : v){cout << x << ' ';}cout << endl;// 删掉4v.pop_back();for (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

6.3 insert - 插入

【函数原型1】

iterator insert (iterator position, const value_type& val);

注意:insert是要配合迭代器使用的!

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;// 尾插1、2、3、4v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto x : v){cout << x << ' ';}cout << endl;// 头插一个6v.insert(v.begin(), 6);for (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

【函数原型2】

void insert (iterator position, size_type n, const value_type& val);

【代码样例】

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;// 尾插1、2、3、4v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto x : v){cout << x << ' ';}cout << endl;// 尾插4个6v.insert(v.end(), 4, 6);for (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

6.4 erase - 删除pos位置的数据

【函数原型1】

erase(const iterator pos);

注意:erase也是要配合迭代器使用的

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);// 删除最后一个数据v.erase(v.end() - 1);for (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

【函数原型2】

erase(const iterator start,const iterator end)

功能:删除某个区间的数据

【代码示例】

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);// 清空数据v.erase(v.begin(), v.end());for (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

除了以上方式可以清空数据,clear同样也行

6.5 clear - 清空所有数据

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.clear();for (auto x : v){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

七、几个常用算法

注意:使用库里的算法需要加上头文件#include <algorithm>

7.1 sort - 排序

#include <iostream>
#include <vector>
using namespace std;
#include<algorithm>int main()
{vector<int> a{ 4,7,1,0,5,3, 2 };// 从小到大sort(a.begin(), a.end());for (auto x : a){cout << x << ' ';}cout << endl;// 从大到小sort(a.rbegin(), a.rend());for (auto x : a){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

7.2 reverse

#include <iostream>
#include <vector>
using namespace std;
#include<algorithm>int main()
{vector<int> a{ 1,2,3,4,5,6 };//逆置reversereverse(a.begin(), a.end());for (auto x : a){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

7.3 find

注意:vector是没有提供find接口的。而我们知道vector是一个类似于数组的容器,因此如果想找一个数据,直接遍历即可。但是算法库提供了find

#include <iostream>
#include <vector>
using namespace std;
#include<algorithm>int main()
{vector<int> a{ 1,2,3,4,5,6 };// 查找4vector<int>::iterator pos = find(a.begin(), a.end(), 4);if (pos != a.end()){// 找到4就删掉a.erase(pos);}for (auto x : a){cout << x << ' ';}cout << endl;return 0;
}

【输出结果】

在这里插入图片描述

八、迭代器失效问题

8.1 什么是迭代器失效

迭代器失效实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃。即如果继续使用已经失效的迭代器,程序可能会崩溃。

8.2 为什么string不存在迭代器失效问题

string是一个特殊的容器,它是由字符组成的连续序列,类似于C语言的字符串。string类会动态地管理内部存储区域,确保足够的容量容纳字符串。当我们向string中插入或删除字符时,并不会导致整个字符串被复制到新的内存位置,因此迭代器不会失效。

对比vector,它是一个动态数组,它使用连续的内存存储元素。当我们向vector中插入元素时,如果导致当前内存不足以容纳所有元素,vector会重新分配更大的内存空间,并将所有元素复制到新的内存中。这样一来,原来指向旧内存中的元素的迭代器就会失效,因为它们指向的位置已经改变了。

8.3 几个常见的迭代器失效样例

当涉及到插入或删除操作时,我们需要注意vector迭代器的失效问题

  • 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resizereserveinsertpush_back
#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v{ 1, 2, 3 };// 1. 将有效元素个数增加到100个//   多出的位置使用6填充,操作期间底层会扩容v.resize(100, 6);// 2. reserve的作用就是改变扩容大小但不改变有效元素个数,// 操作期间可能会引起底层容量改变v.reserve(100);// 3. insert和尾插期间// 可能会引起扩容,而导致原空间被释放v.insert(v.begin(), 0);v.push_back(10);return 0;
}

以上操作可能会导致vector扩容,扩容就会导致旧空间被释放掉,而返回的迭代器是指向被释放的空间,如果再对迭代器进行使用,会引起代码崩溃。

  • 指定位置元素的删除操作
#include <iostream>
#include <vector>
using namespace std;int main()
{int a[] = { 1, 2, 3, 4, 5 };vector<int> v(a, a + sizeof(a) / sizeof(int));// 使用find查找4所在位置auto pos = find(v.begin(), v.end(), 4);// 删除pos位置的数据,v.erase(pos);// 预测打印5cout << *pos << endl;return 0;
}

【输出结果】

理论上删除了4*pos应该是5,可是为什么没有打印出来呢?

我们可以通过调试来观察:

在这里插入图片描述

当我再按F10,发现pos的地址变了:

在这里插入图片描述

理论上删除4后,5应该占据4的空间,然而地址却变了。因此导致了迭代器失效了。

那如果是删除最后一个数据,那么结果更加明显:

在这里插入图片描述

8.4 如何解决迭代器失效问题

在使用前,对迭代器重新赋值即可。

#include <vector>
#include <iostream>
using namespace std;
int main()
{vector<int> v{ 1,2,3,4,5,6,7,8,9,10};// 保留1 2 3// 剩下全删除vector<int>::iterator it = v.begin() + 3;while (it != v.end()){// 在使用之前重新赋值it = v.erase(it);}for (auto& x : v){cout << x << ' ';}cout << endl;return 0;
}

在这里插入图片描述

【输出结果】

在这里插入图片描述

http://www.lryc.cn/news/135112.html

相关文章:

  • ThreadLocal深度解析
  • 06有监督学习——迁移学习
  • 快速连接服务器脚本 可从多个服务中选择并连接
  • MemSeg:一种差异和共性来检测图像表面缺陷的半监督方法
  • 迈向未来的大门:人脸识别技术的突破与应用
  • Vue-9.集成(.editorconfig、.eslintrc.js、.prettierrc)
  • Qt 编译使用Bit7z库接口调用7z.dll、7-Zip.dll解压压缩常用Zip、ISO9660、Wim、Esd、7z等格式文件(一)
  • AndroidUI体系
  • CBV (基于类的视图)源码解析(1)
  • 2023-08-17 Untiy进阶 C#知识补充7——C#8主要功能与语法
  • 登陆接口的的Filter过滤
  • 【Python原创设计】基于Python Flask的全国气象数据采集及可视化系统-附下载方式以及项目参考论文,原创项目其他均为抄袭
  • 【力扣】42. 接雨水 <模拟、双指针、单调栈>
  • 【leetcode 力扣刷题】链表基础知识 基础操作
  • 关于openfeign调用时content-type的问题
  • OpenCV 玩转图像和视频
  • 技术分享 | 如何编写同时兼容 Vue2 和 Vue3 的代码?
  • 基于ArcGis提取道路中心线
  • xcode14.3更新一系列问题
  • 1U和2U的服务器怎么选择
  • 【SA8295P 源码分析】05 - SA8295P QNX Host 上电开机过程 进一步梳理(结合代码)
  • 【数据结构与算法】迪杰斯特拉算法
  • python爬虫-网页数据提取
  • ZigBee的Many-to-One和Source Routing
  • 七夕节 Chinese Valentine‘s Day 的由来
  • 掌握JDK21全新结构化并发编程,轻松提升开发效率!
  • 【SA8295P 源码分析】00 - 系列文章链接汇总 - 持续更新中
  • TCP拥塞控制详解 | 6. 主动队列管理
  • 前端学习清单
  • go atomic原子操作详细解读