深入理解vector 【C++】

ops/2024/10/15 22:29:00/

                               

一、vector的介绍:

        1.vector是表示可变大小的顺序容器。

        2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。

        3.本质讲,vector使用动态分配数组来存储它的元素。当空间满了并且插入新元素的时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。

        4.vector分配空间策略:我们一般会按照两倍进行扩容来避免频繁扩容。

        5.vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。

        6.与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其他不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好。

总结:它底层实际上和顺序表差不多 可以使用下标来访问 为了提高效率扩容一般是按照两倍扩容

二、vector的定义:

方法一: 构造一个某类型的空容器

        vector<int> v1; // 构造一个int类型的空容器

方法二:构造一个含有n个val值的容器

        vector<int> v2(10, 2); // 构造一个int类型的容器 初始化102

方式三:拷贝构造

        vector<int> v3(v2);

方式四:使用迭代器拷贝

        vector<int> v4(v2.begin(), v2.end()); //迭代器拷贝构造

注:

1.使用别的容器的迭代器也可以拷贝(不是vector 以string为例)

        string s1("hello world");
        vector<char> v5(s1.begin(), s1.end()); // 使用string类的迭代器构造

 2.vector也可以嵌套自己,vector<vector<int>>相当于二维数组

        第一个vector中的指针指向了一块存放vector<int>类型的空间,而之后的每一个vector中的指针又指向存放int类型的空间。(就相当于二维数组,只不过这个是动态的并且有成员函数

三、空间相关

3.1 size

size_type就是size_t

   返回容器中占用的元素个数 

#include <iostream>
#include <vector>int main()
{std::vector<int> myints;std::cout << "0. size: " << myints.size() << '\n';for (int i = 0; i < 10; i++) myints.push_back(i);std::cout << "1. size: " << myints.size() << '\n';myints.insert(myints.end(), 10, 100);   //从最后的位置开始插入10个100std::cout << "2. size: " << myints.size() << '\n';myints.pop_back();std::cout << "3. size: " << myints.size() << '\n';return 0;
}

   

3.2 capacity

size_type就是size_t

#include <iostream>
#include <vector>int main()
{std::vector<int> myvector;// set some content in the vector:for (int i = 1; i <= 100; i++){myvector.push_back(i);if (i % 10 == 0){std::cout << "size: " << (int)myvector.size() << '\n';std::cout << "capacity: " << (int)myvector.capacity() << '\n';}}return 0;
}

 

        VS保持一贯的作风,先有一段空间存放数据,超了之后按1.5倍扩容。

3.3 empty

        判断vector是否为空(size是否为0)  为空就返回true      不为空返回false

3.4 resize

        resize函数可以改变容器的有效元素个数

                   value_type就是模板类型T ,不传的话,就会去调用对应的 T() 构造函数

使用规则是这样子的

  1. 假设所给值小于当前容器size值 size缩小至所给值
  2. 假设所给值大于当前容器size值并且小于capacity值 size扩大至所给值 扩大的范围赋值(调用对应的构造函数
  3.  假设所给值大于当前容器size值并且大于capacity值 先扩容 后面操作和2一样

 调用对应的构造函数:

    C++规定:内置类型也有对应的构造函数

        int -->  0

        double -->0.0

        int*  -->nullptr

    对应自定义类型,调用对应的构造函数 

// resizing vector
#include <iostream>
#include <vector>int main()
{std::vector<int> myvector;// set some initial content:for (int i = 1; i < 10; i++) myvector.push_back(i);myvector.resize(5);myvector.resize(8, 100);myvector.resize(12);std::cout << "myvector contains:";for (int i = 0; i < myvector.size(); i++)std::cout << ' ' << myvector[i];std::cout << '\n';return 0;
}

3.5 reserve

 

  使用规则如下

1.假设所给值小于当前容量值 什么都不做

2 假设所给值大于当前容量值 扩容至所给值

// vector::reserve
#include <iostream>
#include <vector>int main()
{std::vector<int>::size_type sz;//std全局域 vector<int>类 typedef的size_type  就是 size_tstd::vector<int> foo;sz = foo.capacity();std::cout << "making foo grow:\n";for (int i = 0; i < 100; ++i) {foo.push_back(i);if (sz != foo.capacity()) {sz = foo.capacity();std::cout << "capacity changed: " << sz << '\n';}}std::vector<int> bar;sz = bar.capacity();bar.reserve(100);   // this is the only difference with foo abovestd::cout << "making bar grow:\n";for (int i = 0; i < 100; ++i) {bar.push_back(i);if (sz != bar.capacity()) {sz = bar.capacity();std::cout << "capacity changed: " << sz << '\n';}}return 0;
}

reserve会先把空间括好,你想要括多少,就传参。 

四、迭代器相关

在前面的string类里面已经讲过迭代器了。这里我就贴一下代码就过了。后面的insert和erase迭代器失效才是重头戏!

// vector::reserve
#include <iostream>
#include <vector>
using namespace std;
int main()
{vector<int> v1; // 构造一个int类型的容器for (int i = 0; i < 10; i++){v1.push_back(i);}//正向迭代器vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}cout << endl;//反向迭代器vector<int>::reverse_iterator rit = v1.rbegin();while (rit != v1.rend()){cout << *rit << " ";rit++;}
}

 

五、访问数据 

 

5.1 operator[] 对[]运算符进行了重载,一个通过下标来访问元素。

5.2 at和operator[] 一样,通过下标来访问。

5.3 front 访问第一个元素

5.4 back 访问最后一个元素(v.size()-1的下标的元素)

5. data 用于返回指向vector中第一个元素的指针

// vector::data
#include <iostream>
#include <vector>int main()
{std::vector<int> myvector(5);int* p = myvector.data();*p = 10;++p;*p = 20;p[2] = 100;std::cout << "myvector contains:";for (unsigned i = 0; i < myvector.size(); ++i)std::cout << ' ' << myvector[i];std::cout << '\n';return 0;
}

 可以通过p指针来访问vector类中的元素

六、增删查改 

增:

5.1 push_back  向尾部插入一个数据

vector<int> v1;

v1.push_back(1);

v1.push_back(2);

v1.push_back(3); 

就存放了1 2 3三个数据到v1对象中

5.2 insert 指定位置插入数据

 第一种:   (返回值是iterator,在之后讲迭代器失效会提到)

        选择一个位置(迭代器),选择要插入的数据。

#include <iostream>
#include <vector>
using namespace std;
int main()
{vector<int> v1; // 创造一个空容器v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.insert(v1.begin(), 100); // 我们再开头插入一个元素100vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}return 0;
}

第二种:

        选择一个位置(迭代器),选择要插入的个数,选择要插入的数据。

#include <iostream>
#include <vector>
using namespace std;
int main()
{vector<int> v1; // 创造一个空容器v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.insert(v1.begin(), 10, 5); // 我们头插100个5vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}return 0;
}

 

删:

5.2 pop_back  从尾部删除一个数据

        

#include <iostream>
#include <vector>
using namespace std;
int main()
{vector<int> v1; // 创造一个空容器v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto u : v1){cout << u << ' ';}cout << endl;v1.pop_back();v1.pop_back();v1.pop_back();for (auto u : v1){cout << u << ' ';}cout << endl;return 0;
}

5.3 erase 指定位置删除数据

注意这里的返回值是iterator,在之后讲迭代器失效的时候会提到。 

第一种:删除一个指定位置的元素

第二种:删除一段区间的元素

// erasing from vector
#include <iostream>
#include <vector>int main ()
{std::vector<int> myvector;// set some values (from 1 to 10)for (int i=1; i<=10; i++) myvector.push_back(i);// erase the 6th elementmyvector.erase (myvector.begin()+5);std::cout << "myvector contains:";for (unsigned i=0; i<myvector.size(); ++i)std::cout << ' ' << myvector[i];std::cout << '\n';// erase the first 3 elements:myvector.erase (myvector.begin(),myvector.begin()+3);std::cout << "myvector contains:";for (unsigned i=0; i<myvector.size(); ++i)std::cout << ' ' << myvector[i];std::cout << '\n';return 0;
}

查:

5.4   find

 find函数可以找到容器中我们要寻找的值 并且返回迭代器

 find函数有三个参数 迭代器左区间 迭代器右区间 要查找元素 (左闭右开)适用于所有的迭代器。

 为什么string类里面要自己设计一个find函数呢?因为要满足对各种字符串查找的需求。 

        注意!!!!!!

        这里的find函数的头文件是 algorithm !!!!!!!!!

        一定要记住! 不是容器的默认函数

#include <iostream>
#include <vector>
#include<algorithm>//为了能够使用find函数
using namespace std;
int main()
{vector<int> v1; // 创造一个空容器v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);// v1.erase(v1.begin(), v1.begin()+2);vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);  // 找到3v1.erase(pos); // 删除3vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}return 0;
}

 

改:

operator=

// vector assignment
#include <iostream>
#include <vector>
using namespace std;
int main()
{std::vector<int> foo(3, 1);std::vector<int> bar(5, 2);bar = foo;foo = std::vector<int>();//打印foo中的元素for (auto u : foo){cout << u << " ";}cout << endl;std::cout << "Size of foo: " << int(foo.size()) << '\n';//打印bar中的元素for (auto u : bar){cout << u << " ";}cout << endl;std::cout << "Size of bar: " << int(bar.size()) << '\n';return 0;
}

 这个运算符会改变size大小和内部的变量

七、迭代器失效: 

在使用insert和earse的时候,会出现迭代器失效的问题。

 7.1  实例1:
#include <iostream>
#include <vector>
using namespace std;
void test_1()
{vector<int> vec;vec.push_back(0);vec.push_back(1);vec.push_back(2);vec.push_back(3);vec.push_back(4);cout << vec.size() << ":" << vec.capacity() << endl;vector<int>::iterator it = vec.begin();while (it != vec.end()){if (*it % 2 == 0){vec.insert(it, 5);   //这里会出现错误++it;}++it;}cout << vec.size() << ":" << vec.capacity() << endl;for (auto e : vec){cout << e << " ";}cout << endl;
}
int main()
{test_1();return 0;
}

分析:

vec.insert(it, 5);   //这里会出现错误   当你把it传进去的时候,是传值传进去的,

它在insert内部会进行扩容(异地扩容)操作。内部的pos指针还指向原地,就不再准确了。

就会造成野指针问题。

而如果传引用传递的话,会让其他一些函数不能使用。

原因:insert之后vector会分配新的空间,旧迭代器便会失效。

解决方法:取insert函数的返回值为新的迭代器。

 用图片来帮助理解:

      

         

改法:(接收新的it作为返回值)

while (it != vec.end())
{if (*it % 2 == 0){it=vec.insert(it, 5);//这样子写是错误的,应该怎么改?it++;}++it;}

7.2   实例2:
#include <iostream>
#include <vector>
using namespace std;
int main()
{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);vector<int>::iterator it = find(v1.begin(), v1.end(), 2); // 意思是我们要删除2v1.erase(v1.begin()); //删除头元素v1.erase(it); //删除元素2return 0;
}

调试看看:

 咦?你看到了没有,it指针指向的位置还是那个位置,但是里面的值却改变了。

删除头元素,就是把数据向前挪动,而it指针指向不变。导致后面出问题了。

解决方法:同样在使用完erase之后,拿it去接收erase函数的返回值

7.3  总结(重点)

1.     insert和erase都会导致迭代器失效,我们都需要用it来接收新的迭代器。

        it=insert(it,5);  //在it位置插入元素5,拿it来接收新迭代器

        it=erase(it);//删除it位置的元素,拿it来接收新迭代器

2.   insert的返回值的it -->指向新插入的元素

     erase的返回值的it  -->指向删除元素的下一个元素


 本篇内容到此结束!感谢大家阅读!!!


http://www.ppmy.cn/ops/29900.html

相关文章

大数据开发工作中的数仓设计(Hadoop,hive ,mysql )

1.HUE工具介绍使用 HUE是CDH提供一个hive和hdfs的操作工具&#xff0c;在hue中编写了hiveSQl也可以操作hdfs的文件 http://主机名字:端口号 hdfs的web访问端口 http://主机名字:端口号 hdfs的程序访问端口 进入后确保hdfs hive yarn 开启 在点击hue开启 在这里面也可以进行h…

Web,Sip,Rtsp,Rtmp,WebRtc,专业MCU融屏视频混流会议直播方案分析

随着万物互联&#xff0c;视频会议直播互动深入业务各方面&#xff0c;主流SFU并不适合管理&#xff0c;很多业务需要各种监控终端&#xff0c;互动SIP硬件设备&#xff0c;Web在线业务平台能相互融合&#xff0c;互联互通&#xff0c; 视频混流直播&#xff0c;录存直播推广&a…

Linux shell编程学习笔记48:touch命令

0 前言 touch是csdn技能树Linux基础练习题中最常见的一条命令&#xff0c;这次我们就来研究它的功能和用法。 1. touch命令的功能、格式和选项说明 我们可以使用命令 touch --help 来查看touch命令的帮助信息。 purpleEndurer bash ~ $ touch --help Usage: touch [OPTION]…

细说SVPWM原理及软件实现原理,关联PWM实现

细说SVPWM原理及软件实现原理&#xff0c;关联PWM实现 文章目录 细说SVPWM原理及软件实现原理&#xff0c;关联PWM实现1. 前言2. 基础控制原理回顾2.1 FOC 原理回顾2.2 细说 SVPWM2.2.1 矢量扇区计算2.2.2 矢量作用时间计算 2.2.3 如何理解 U4 U6 2/3Udc?2.2.4 如何理解 U4m…

【C++】命名冲突了怎么办?命名空间来解决你的烦恼!!!C++不同于C的命名方式——带你认识C++的命名空间

命名空间 导读一、什么是C?二、C的发展三、命名空间3.1 C语言中的重名冲突3.2 什么是命名空间&#xff1f;3.3 命名空间的定义3.4 命名空间的使用环境3.5 ::——作用域限定符3.6 命名空间的使用方法3.6.1 通过作用域限定符来指定作用域3.6.2 通过关键字using和关键字namespace…

Ubuntu下安装并配置SAMBA服务器

今天我要给大家带来一个关于在Ubuntu下安装并配置SAMBA服务器的详细技术博客。不过&#xff0c;在我们开始之前&#xff0c;我得先夸一夸阿贝云免费服务器&#xff0c;这个免费云服务器真是不错的东西啊&#xff01;配置有1核CPU、1G内存、10G硬盘和5M带宽。现在我们开始吧&…

Django响应‘表单请求’过程

&#xff08;1&#xff09;用户通过自己的浏览器&#xff08;客户端&#xff09;第一次向服务器发出含有表单页面的请求&#xff0c;Django会创建一个未绑定数据的表单实例&#xff08;例如form LoginForm(), form实例就是未绑定实例&#xff09;&#xff0c;即空表单&#xf…

【Docker】如何注册Hub账号并上传镜像到Hub仓库

一、创建Hub账户 浏览器访问&#xff1a;hub.docker.com 点击【Sign up】注册账号 输入【邮箱】【用户名】【密码】 ps&#xff1a;用户名要有字母数字&#xff1b;订阅不用勾选 点击【Sign up】注册即可 点击【Sign in】登录账号 输入【邮箱】【密码】 点击【Continue】登录 二…