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

C++vector的使用方法

文章目录

  • 一、vector的介绍
    • 1. 文档链接
    • 2. 简要介绍
  • 二、vector的使用
    • 1.vector的定义
      • (1)构造函数
      • (2)拷贝构造函数
      • (2)赋值重载
    • 2. vector 增删查改
      • (1)operator []
      • (2)push_back和pop_back
      • (3)insert和erase
      • (4)find查找
    • 3. vector 空间增长问题
      • (1)size和empty及capacity
      • (2)resize和reserve
      • (3 reserve
      • (4 resize
    • 4. vector iterator 的使用
      • (1)begin和end
      • (2)rbegin和rend
      • (3)范围for


一、vector的介绍

1. 文档链接

参考文档
在这里插入图片描述

2. 简要介绍

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

二、vector的使用

1.vector的定义

(constructor)构造函数声明接口说明
vector()(重点)无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造
vector& operator= (const vector& x);赋值重载

(1)构造函数

  • vector的构造函数主要有三种
    • 无参构造
    • 放入n个相同的数据
    • 使用迭代器区间进行构造

其中的allocator是空间配置器,只是用来分配空间的,加快空间申请和释放的速度。
在这里插入图片描述
vector是一个模板类,在实例化的时候要指明其内置类型。

template < class T, class Alloc = allocator<T> > class vector;
void test1()
{vector<int> v1;//无参构造vector<int> v2(3, 2);//构造一个有3个2的vectorstring s1("abcd");vector<int> v3(s1.begin(), s1.end());//迭代器构造}

在这里插入图片描述

在这里插入图片描述
在迭代器构造的示例中我们可以看到v3的size是4,但是里面存的却不是字符abcd,那是因为我们实例化的时候类型写的是int,他这里发生了隐式类型转换。
如果想要看到正确的字符的话改成下面这样就好了

vector<char> v3;

vector<char>和string的区别:vector存的是一个一个的字符,结尾是没有'\0'的,而是string是字符串所以结尾会有'\0'

vector中不仅可以存自定义类型,它还可以放自定义类型,你创建的结构体什么的都可以放。因为【vector】是一个模版类,其会根据所传入的类型去做一个自动类型的推导,例如在vector中放入string对象,我们就可以直接这样写

vector<string> s1;

(2)拷贝构造函数

在这里插入图片描述
我们可以简单来看一下
在这里插入图片描述

void test2()
{vector<int> v1(3, 2);vector<int> v2(v1);//拷贝构造vector<int> v3 = v1;//这也是拷贝构造,不是赋值重载
}

在这里插入图片描述

(2)赋值重载

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

void test3()
{vector<int> v1(3, 2);vector<int> v2;v2 = v1;//赋值重载}

在这里插入图片描述

2. vector 增删查改

vector增删查改接口说明
push_back(重点)尾插
pop_back (重点)尾删
find查找。(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[] (重点)像数组一样访问

(1)operator []

在vector中对[]进行了运算符重载,使得我们可以像通过下标访问数组元素一样来访问vector,同时支持修改

  • 下面是官方文档中的形式,虽然看起来很复杂,但是读者完全不用理会,会用就可以了
reference operator[] (size_type n);const_reference operator[] (size_type n) const;

在这里插入图片描述

void test4()
{string s1("hello,world");vector<char> v1(s1.begin(), s1.end());for (int i = 0; i < s1.size(); i++){cout << v1[i] << " ";}//支持访问cout << endl;v1[0] = 'x';v1[1] = 'y';//支持修改for (int i = 0; i < s1.size(); i++){cout << v1[i] << " ";}
}

在这里插入图片描述

(2)push_back和pop_back

在数组尾部插入一个元素和删除最后一个元素。
在这里插入图片描述
在这里插入图片描述

void test4()
{string s1("hello,");vector<char> v1(s1.begin(), s1.end());for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;v1.push_back('L');v1.push_back('i');v1.push_back('n');v1.push_back('u');v1.push_back('x');for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;v1.pop_back();v1.pop_back();v1.pop_back();v1.pop_back();v1.pop_back();v1.pop_back();for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}
}

在这里插入图片描述
如果我们这里采取string类作为【vector】的内置类型,然后通过三种形式往里面插入数据:

  • 第一种是构造出具体的对象
  • 第二种采取的是匿名对象
  • 第三种采取的则是单参数的构造函数所引发的 隐式类型转换
void test5()
{vector<string> v;string name1("张三");v.push_back(name1);v.push_back(string("李四"));v.push_back("王五");		// 单参数的构造函数支持隐式类型转换
}

在这里插入图片描述

(3)insert和erase

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

insert有很多,这里仅仅展示部分常用的两参数的,第一个参数是要插入位置的迭代器,第二个是要插入的元素。

void test7()
{vector<string> v;v.push_back("张三");v.push_back("王五");v.insert(v.begin() + 1, "李四");//在begin的下一个位置插入一个元素for (int i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;v.erase(v.begin());//删除begin位置的元素for (int i = 0; i < v.size(); i++){cout << v[i] << " ";}}

在这里插入图片描述
如果想要删除指定元素,我们可以find和erase搭配使用

(4)find查找

在这里插入图片描述
find是在范围内查找,观察函数的参数我们可以知道如果要使用这个函数的话就需要先传入一个迭代器区间,然后传入一个值,在指定的区间内查找这个值。如果找到则返回指向改元素位置的迭代器,如果找不到那就返回最后的迭代器也就是end()

void test6()
{string s1("hello,world");vector<char> v1(s1.begin(), s1.end());std::vector<char>::iterator it = find(v1.begin(), v1.end(), 'w');if (it != v1.end()){cout << *it << endl;}}

在这里插入图片描述
我们可以搭配erase加循环使用,删除vector中所有的 ‘l’ 字符

void test6()
{string s1("hello,world");vector<char> v1(s1.begin(), s1.end());std::vector<char>::iterator it = find(v1.begin(), v1.end(), 'l');while (it != v1.end()){it = v1.erase(it);it = find(it, v1.end(), 'l');}for (auto e : v1){cout << e << " ";}cout << endl;}

在这里插入图片描述

3. vector 空间增长问题

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve (重点)改变vector的capacity

(1)size和empty及capacity

size就是获取容器中有几个元素,capacity就是容器的容量
capacity和size是不一样的,你可以开10个空间但只放5个数据,此时size就是5,而capacity就是10.

void test10()
{vector<int> v(5, 10);cout << v.size();
}

可以看到我们插入了5个10,所以size是5,capacity也是5.
在这里插入图片描述

empty就是判断容器是否有元素,没有元素返回1,有元素返回0

void test11()
{vector<int> v;cout << v.empty() << endl;//没元素所以为真,输出1v.push_back(1);cout << v.empty();//插入一个元素后不为空,输出0
}

在这里插入图片描述

(2)resize和reserve

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中,vector是按1.5倍的规则扩容的,我们去Linux平台下再去试试。
在这里插入图片描述
可以看到在Linux平台下是按照2倍的扩容逻辑走的。可见不同地方vector的实现方法略有区别。
在这里插入图片描述

(3 reserve

reserve是开好空间但不填充元素,所以size是不改变的,只有capacity会改变。因为size没改变,所以不能通过[]来访问没元素的位置。

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';}}
}

可以看到避免了频繁扩容
在这里插入图片描述

(4 resize

resize是开好指定的空间并填充默认值,capacity和size都会改变
可以看到size和capacity都是3,填充的默认值我们没有指定,所以默认填充的是0。
在这里插入图片描述
当然我们可以指定填充的内容,比如这里我们指定填充数字10
在这里插入图片描述
接下来我们说一下常见的错误
大家可以看看下面的代码有什么问题,是不是乍一看好像每什么问题,但是一运行直接寄了。

void test13()
{vector<int> v1;v1.reserve(10);for (size_t i = 0; i < 10; i++){v1[i] = i;}
}

在这里插入图片描述

  • 大家要关注前面的reserve(10),我们在上面说到对于【reserve】而言只是做的扩容而已,即只变化capacity,而不会变化size
  • 另一点,对于v1[i]我们上面在讲元素访问的时候有说到过,这是下标 + []的访问形式,在出现问题的时候会直接给出断言错误。因为这里我们在【reserve】的时候只是开出了指定的空间,但size还是为0,此时去访问的时候肯定就出错了

改正方法就是将reserve改成resize即可
可以看到成功运行
在这里插入图片描述

4. vector iterator 的使用

iterator的使用接口说明
begin + end(重点)获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

在这里插入图片描述

(1)begin和end

  • 和【string】中一样,每个迭代器也是具有两种形式,第一个呢是具有读写的,第二个则是只读的const迭代器
  • begin获取一个字符的迭代器
  • end获取最后一个字符下一个位置的迭代器
    在这里插入图片描述
    在这里插入图片描述
    迭代器的理解,迭代器呢可以说是STL中很重要的一部分。简单来说迭代器就是用来遍历或访问容器中的数据的,我们暂时可以把迭代器想象成指针,通过指针的++或者–加解引用的方式,我们就可以遍历一个数组,或者访问数组中的元素。当然指针只是迭代器中的一种,迭代器要实现的目的就是通过++或者–能够遍历容器中所有的元素,我们数组是一段连续的空间,可以通过指针加一的方式遍历整个数组,但是如果是链表呢?这种情况下通过对每个指针++的操作就无法实现目的了,因此指针就不适合当迭代器了,我们就得封装新的迭代器。
void test8()
{string s1("hello,world");vector<char> v1(s1.begin(), s1.end());vector<char>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";//通过解引用迭代器获取容器中的元素++it;//迭代器++,指向下一个位置}
}

在这里插入图片描述

(2)rbegin和rend

rbegin和rend是反向迭代器
在这里插入图片描述
在这里插入图片描述
反向迭代器呢顾名思义就是从反方向进行遍历

void test8()
{string s1("hello,world");vector<char> v1(s1.begin(), s1.end());vector<char>::iterator it1 = v1.begin();while (it1 != v1.end()){cout << *it1 << " ";++it1;}cout << endl;auto it2 = v1.rbegin();while (it2 != v1.rend()){cout << *it2 << " ";++it2;}
}

在这里插入图片描述

(3)范围for

既然支持迭代器的话,那肯定支持范围for的,我们可以来试试。

void test9()
{string s1("hello,world");vector<char> v1(s1.begin(), s1.end());for (auto e : v1){cout << e << " ";}
}

可以看到没有任何问题。
在这里插入图片描述

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

相关文章:

  • 视频生成模型Sora的全面解析:从AI绘画、ViT到ViViT、DiT、VDT、NaViT、VideoPoet
  • 4.1k star,官方出品的redis桌面管理工具——redislnsight
  • 论文目录3:大模型时代(2023+)
  • FPGA IBUFG
  • 探索数据结构:单链表的实战指南
  • 短视频矩阵系统----矩阵系统源码搭建(技术门槛?)
  • Spring事务注解@Transactional的流程和源码分析
  • 在别的地方下载的二次封装Windows镜像怎么安装?GHO镜像详细安装教程
  • 使用Lerna + Yarn Workspace管理Monorepo项目
  • 如何将gzip后缀压缩包重命名任意后缀名并依然通过gzip.open()读取压缩包文件内容
  • C语言从入门到精通 第十一章(文件操作)
  • 安装安卓studio无法下载sdk解决方法
  • express+mysql+vue,从零搭建一个商城管理系统10--添加商品
  • java实现大文件的分割与合并
  • 【计网】TCP协议安全与风险:深入探讨网络通信的基石
  • 苹果App Store上架工具介绍
  • TCP重传机制、滑动窗口、拥塞控制
  • electron+vue3全家桶+vite项目搭建【29】封装窗口工具类【3】控制窗口定向移动
  • 深入了解304缓存原理:提升网站性能与加载速度
  • python-批量操作excel
  • #QT(串口助手-界面)
  • C语言进阶——位段
  • 软件设计师软考题目解析23 --每日五题
  • 总结:前后端集合、数组类型数据交互底层原理,SpringBoot框架解析
  • 2024蓝桥杯每日一题(前缀和)
  • 2007-2022年上市公司迪博内部控制评价缺陷数量数据
  • JAVA虚拟机实战篇之内存调优[4](内存溢出问题案例)
  • qt自定义时间选择控件窗口
  • 如何不解压直接读取gzip文件里面的文件
  • python 截取字符串string.split