深入理解vector 【C++】

                               

一、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个100
	std::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_t
    std::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 above
    std::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); // 我们再开头插入一个元素100
    vector<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个5
    vector<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 element
  myvector.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);  // 找到3
    v1.erase(pos); // 删除3
    vector<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); // 意思是我们要删除2
    v1.erase(v1.begin()); //删除头元素
    v1.erase(it); //删除元素2
    return 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  -->指向删除元素的下一个元素


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++STL详解(五)】--------list的介绍与使用

目录 前言 一、list的介绍 二、list的使用 Ⅰ.默认成员函数 1、构造函数 2、赋值重载 3、析构函数 Ⅱ、容量 1.size() Ⅲ、迭代器与遍历 1.beginend (正向迭代器) 2.rbeginrend (反向迭代器) 3.front 4.back Ⅳ、增删查改 1.push_front 2.pop_front 3.push_b…

Matlab|二阶锥松弛在配电网最优潮流计算中的应用

目录 一、主要内容 二、部分代码 三、程序代码 四、下载链接 一、主要内容 最优潮流计算是电网规划、优化运行的重要基础。首先建立了配电网全天有功损耗最小化的最优潮流计算模型&#xff1b;其次结合辐射型配电网潮流特点建立支路潮流约束&#xff0c;并考虑配电网中的可…

平平科技工作室-Python-步步惊心

一.准备图片 放在 文件夹取名为imgs,分为两种boys和girls 二.编写程序 首先创建一个文件名为index.py 其次编写程序 # coding:utf-8 import sys, time, easygui, os, pygame from pygame.locals import * pygame.init() # 设置窗口显示位置、大小、颜色、标题 os.environ[ …

Go语言的包管理工具go mod与之前的GOPATH有什么区别?

在深入探讨Go语言的包管理工具go mod与之前的GOPATH之间的区别之前&#xff0c;我们首先需要理解这两个概念各自的作用和背景。 GOPATH时代 在Go语言早期版本中&#xff0c;GOPATH是一个非常重要的环境变量。它告诉Go工具链在哪里查找你的Go代码、第三方库以及编译后的二进制…

C#描述-计算机视觉OpenCV(4):图像分割

C#描述-计算机视觉OpenCV&#xff08;4&#xff09;&#xff1a;图像分割 前言用 GrabCut 算法分割图像实例展示 前言 本文中如果有什么没说明的地方&#xff0c;大概率在前文中描述过了。 C#描述-计算机视觉OpenCV&#xff08;1&#xff09;&#xff1a;基础操作 C#描述-计算…

docker 指定根目录 迁移根目录

docker 指定根目录 1、问题描述2、问题分析3、解决方法3.1、启动docker程序前就手动指定docker根目录为一个大的分区(支持动态扩容)&#xff0c;事前就根本上解决根目录空间不够问题3.1.0、方法思路3.1.1、docker官网安装文档3.1.2、下载docker安装包3.1.3、安装docker 26.1.03…

记一次从登录框到前台rce

接口未授权挖掘 在网站未登录的情况下&#xff0c;由于不知道后台接口。唯一办法通过js文件、路径扫描。通过这种收集方式使用burp进行批量扫描&#xff0c;分别探测GET/POST请求。观察响应包跟状态码。判断响应包&#xff0c;确定存在未授权后&#xff0c;再构造数据包。 2 突…

C++ | Leetcode C++题解之第64题最小路径和

题目&#xff1a; 题解&#xff1a; class Solution { public:int minPathSum(vector<vector<int>>& grid) {if (grid.size() 0 || grid[0].size() 0) {return 0;}int rows grid.size(), columns grid[0].size();auto dp vector < vector <int>…

机器学习笔记-18

异常检测问题 异常检测虽然主要用于无监督学习问题上&#xff0c;但是和监督学习问题很相似。 异常检测(Anomaly Detection)&#xff1a;给定正确样本集{ x ( 1 ) , x ( 2 ) . . . x ( n ) x^{(1)},x^{(2)}...x^{(n)} x(1),x(2)...x(n)}&#xff0c;记新样本即要检测的样本为…

NIO(非阻塞I/O)和IO(阻塞I/O)详解

文章目录 一、NIO&#xff08;Non-blocking I/O&#xff0c;非阻塞I/O&#xff09;1、Channel&#xff08;通道&#xff09;与Buffer&#xff08;缓冲区&#xff09;1.1、使用ByteBuffer读取文件1.2、ByteBuffer 方法1.2、ByteBuffer 结构1.3、字符串与 ByteBuffer 互转1.4 Sca…

代码随想录算法训练营DAY48|C++动态规划Part9|121.买卖股票的最佳时机、122.买卖股票的最佳时机II、123.买卖股票的最佳时机III

文章目录 121.买卖股票的最佳时机思路CPP代码 122.买卖股票的最佳时机II思路CPP代码 123.买卖股票的最佳时机III思路CPP代码 121.买卖股票的最佳时机 力扣题目链接 文章讲解&#xff1a;121.买卖股票的最佳时机 视频讲解&#xff1a;动态规划之 LeetCode&#xff1a;121.买卖股…

BJFUOJ-C++程序设计-实验3-继承和虚函数

A TableTennisPlayer 答案&#xff1a; #include<iostream> #include<cstring> using namespace std;class TableTennisPlayer{ private:string firstname;string lastname;bool hasTable;public:TableTennisPlayer(const string &, const string &, bool…

jupyter notebook使用与本地位置设置

本地安装好Anaconda之后&#xff0c;自带的有Jupter notebook。 使用jupyter notebook 使用jupyter notebook时&#xff0c;可以直接打开或者搜索打开&#xff1a; 打开后&#xff0c;我们生成的或者编辑的一些文件&#xff0c;都可以看到&#xff0c;如下&#xff1a; j…

HTML标签大全

本文是用于解释文章中使用的标签&#xff0c;方便萌新理解标签结构&#xff0c;也方便大佬忘了过来查一下~ 本文根据博客教学进度实时更新&#xff0c;可以收藏一下~ 文章目录 第二篇1.template2.div3. button 第三篇4.ul5.li 第二篇 第二篇链接 1.template <template&g…

计算机408备考-数据结构重要知识点-数据结构的定义

请关注一下B站账号&#xff1a;谭同学很nice&#xff01;后期更新发布在这个账号上。。【计算机408备考-数据结构重要知识点-数据结构的定义-哔哩哔哩】https://b23.tv/x7shjNf 数据是信息的载体。数据元素是数据的基本单位。一个数据元素可由若干数据项组成&#xff0c;数据项…

利用大语言模型(KIMI)构建控制信息模型

数字化的核心是数字化建模&#xff0c;为一个事物构建数字模型是一项十分复杂的工作。不同的应用场景&#xff0c;对事物的关注重点的不同的。例如&#xff0c;对于一个智能传感器而言&#xff0c;从商业的角度看&#xff0c;产品的信息模型中应该包括产品的类型&#xff0c;名…

ESD管 AZ5825-01F国产替代型号ESDA05CPX

已经有很多客户选用雷卯的ESDA05CPX替代Amazing 的 AZ5825-01F&#xff0c; 客户可以获得更好的价格和更快的交期&#xff0c;主要应用于对5V供电和4.5V供电电流较大的Vbus线路插拔保护等。 雷卯ESDA05CPX优势&#xff1a; 带回扫 &#xff0c;钳位电压Vc 低&#xff0c;IPP为…

逻辑漏洞:水平越权、垂直越权靶场练习

目录 1、身份认证失效漏洞实战 2、YXCMS检测数据比对弱&#xff08;水平越权&#xff09; 3、MINICMS权限操作无验证&#xff08;垂直越权&#xff09; 1、身份认证失效漏洞实战 上一篇学习了水平越权和垂直越权的相关基本知识&#xff0c;在本篇还是继续学习&#xff0c;这…

利用亚马逊云科技GenAI企业助手Amazon Q Business构建企业代码开发知识库

2024年五一节假日的前一天&#xff0c;亚马逊云科技正式重磅发布了云计算行业期待已久的服务——Amazon Q Business。Amazon Q Business是专为企业用户打造的一个开箱即用的完善而强大企业GenAI助手。企业用户只需要将Amazon Q Business连接到现有的企业内部数据源&#xff0c;…

整合文本和知识图谱嵌入提升RAG的性能

我们以前的文章中介绍过将知识图谱与RAG结合的示例&#xff0c;在本篇文章中我们将文本和知识图谱结合&#xff0c;来提升我们RAG的性能 文本嵌入的RAG 文本嵌入是单词或短语的数字表示&#xff0c;可以有效地捕捉它们的含义和上下文。可以将它们视为单词的唯一标识符——捕获…