C++ - STL详解—vector类

一. vector的概念

 向量(Vector)是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。 

Vector的特点:

  • vector是表示可变大小数组的序列容器。
  • vector就像数组一样,也采用的连续空间来存储元素,这也意味着可以采用下标对vector的元素进行访问。
  • 普通数组相比,vector的大小可以在运行时自动调整,根据需要增加或减少容量。

  • 当vector需要重新分配大小时,其做法是,分配一个新的数组,然后将全部元素移到这个数组当中,并释放原来的数组空间。
  • vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大。不同的库采用不同的策略权衡空间的使用和重新分配,以至于在末尾插入一个元素的时候是在常数的时间复杂度完成的。
  • 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好

二. Vector的使用 

需要包含头文件:#include<vector>

vector的构造

explicit vector (const allocator_type& alloc = allocator_type());	
explicit vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());	
vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
vector (const vector& x);
//alloc: 分配器对象。容器保留并使用一个这种分配器的内部拷贝。成员类型allocator_type是容器使用的内部分配器类型,定义在vector中作为其第二个模板参数(Alloc)的别名。如果allocator_type是默认分配器的一个实例化(默认分配器没有状态),这不相关。
//n: 初始容器大小(即,在构造时容器中的元素数量)。成员类型size_type是一个无符号整型类型。
//val: 用来填充容器的值。容器中的每一个n元素将会初始化为这个值的一个拷贝。成员类型value_type是容器中元素的类型,定义在vector中作为其第一个模板参数(T)的别名。
//first, last: 指向范围内初始和最终位置的输入迭代器。使用的范围是[first, last],其中包括所有first和last之间的元素,包括由first指向的元素但不包括由last指向的元素。函数模板参数InputIterator应该是一个输入迭代器类型,它指向可以构建value_type对象的元素类型。
//x: 另一个相同类型的vector对象(具有相同的类模板参数T和Alloc),其内容被复制或获取。
int main()
{   vector<int> v1; //构造int类型的空容器vector<int> v2(10, 2); //构造含有10个2的int类型容器vector<int> v3(v2); //拷贝构造int类型的v2容器的复制品vector<int> v4(v2.begin(), v2.end());//自己类型的迭代器进行初始化for (auto e : v4){cout << e << " ";}cout << endl;string s1 = "GalaxyPokemon"; //其他容器的迭代器进行初始化vector<char> v5(s1.begin(), s1.end());for (auto e : v5){cout << e << " ";}cout << endl;//还能用数组进行初始化,本质是指针,数组名代表首元素的地址int arr[] = { 1,2,3,4 };vector<int> v6(arr,arr+4);for (auto e : v6){cout << e << " ";}return 0;
}

 

为什么不能用cout<< v1 <<endl直接访问呢?

C++标准库中的vector没有重载<<运算符来支持直接打印。因此,直接尝试用cout打印vector会导致编译错误。

外传:vector<char> strV能不能替代string str,它们两个都是字符数组,也都能动态增长呀?可以替代吗?

答案是否定的:

他俩结构上有所不同stringvector<char> 的处理和终结字符 \0 的方式确实有所不同。

string 和 \0

C++ 中的 std::string 类自动处理字符串的终止字符 \0。为了更好的兼容C接口,当你使用 std::string 存储文本数据时,无论是通过构造函数、赋值操作还是添加内容,std::string 都会在内部确保字符串后面有一个 \0 字符。这意味着你可以安全地将 std::string 的内容传递给期望以 null 结尾的 C 字符串的 C 函数(如 strcpy, printf, 等等)。

std::string s = "hello";
std::cout << s.c_str();  // c_str() 返回一个以 '\0' 结尾的 const char* 指针

vector<char> 和 \0

std::string 不同,std::vector<char> 不会自动在其元素后添加 \0 字符。vector<char> 只是一个字符的动态数组,它不假设存储的数据是字符串。因此,如果你想用 vector<char> 来存储字符串并传递给期望以 null 结尾的字符串的 C 函数,你需要自己管理并在适当的位置添加 \0

std::vector<char> v = {'h', 'e', 'l', 'l', 'o'};
v.push_back('\0');  // 必须手动添加 '\0'
std::cout << &v[0];  // &v[0] 现在指向一个以 '\0' 结尾的字符数组

如果你的目的是处理文本字符串,推荐使用 std::string,因为它自动处理与字符串相关的许多细节,包括 \0 的添加。使用 std::vector<char> 来处理字符序列或者非文本的二进制数据更加合适,因为这时你可能不需要字符串的特定行为,如自动添加终止字符。

string的接口比vector更丰富

1. 功能和方法

  • 专用方法string 类提供了大量专门用于字符串处理的方法,如 substr(), find(), replace(), 和 append() 等。这些方法使得处理文本和字符串操作更加直观和高效。
  • 字符操作:虽然 vector<char> 也支持类似 push_back()pop_back() 这样的操作,但缺乏专门针对字符串处理的优化和功能。

2. 性能

  • 内存管理string 通常对字符串操作进行了优化,包括内存的分配和重新分配。虽然 vector<char> 也提供动态大小调整,但它的内存管理策略并不专门为字符串优化。
  • 效率:对于一些操作,如连接两个字符串,string+ 操作符或 append() 方法通常比 vector<char> 的元素逐一添加更高效。

3. 语义清晰性

  • 可读性:使用 string 类型可以使代码的意图更加明显,即处理的是文本数据。而 vector<char> 更多地被看作是字符的集合,它在语义上不如 string 直观。

虽然可以使用 vector<char> 来代替 string,但在处理字符串时,使用 string 类通常更加合适、高效和符合语义。vector<char> 适合于需要灵活处理字符数据的情况,尤其是当涉及到字符级别的复杂操作时。因此,在大多数涉及文本处理的场景中,推荐使用 string 而非 vector<char>

vector里面能存string吗?

单参数的构造函数支持隐式类型的转换

explicit vector (const allocator_type& alloc = allocator_type());	
explicit vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());
vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
vector (const vector& x);// 创建一个空的vector。
std::vector<int> v;
//创建一个给定大小的vector,使用元素的默认构造函数初始化所有元素。
std::vector<int> v(10);
//创建一个给定大小的vector,并初始化所有元素为指定的值。
std::vector<int> v(10, 5); // 10个元素,每个都是5
//通过一对迭代器给定范围创建vector。
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::vector<int> v(arr.begin(), arr.end());
//通过另一个同类型vector创建一个新的vector的副本。
std::vector<int> original(10, 5);
std::vector<int> v(original);

不仅可以用自己类型的迭代器,还能用其他能匹配上的类型的迭代器,比如说string

int main()
{string s1 = "GalaxyPokemon";vector<int> v3(s1.begin(), s1.end());for (auto e : v3){cout << e << " ";}return 0;
}

 

所以这样才是最准确的

int main()
{string s1 = "GalaxyPokemon";vector<char> v3(s1.begin(), s1.end());for (auto e : v3){cout << e << " ";}return 0;
}

vector的元素访问

接口名称使用说明

operator[]

访问元素  下标 + [ ]

at

访问元素  下标 + ( )

front

访问第一个元素

back

访问最后一个元素

  operator[ ] 

reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
//n:容器中元素的位置。注意,第一个元素的位置为0(不是1)。成员类型size_type是一个无符号整数类型。

int main()
{vector<int> v(5, 1);//使用“下标+[]”的方式遍历容器for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;return 0;
}

对于oparator[]来说,若是产生了一个越界访问的话就直接报出【断言错误】了 

at

reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
//n:容器中元素的位置。注意,第一个元素的位置为0(不是1)。成员类型size_type是一个无符号整数类型。
int main()
{vector<int> v(5, 1);//使用“下标+()”的方式遍历容器for (size_t i = 0; i < v.size(); i++){cout << v.at(i) << " ";}cout << endl;return 0;
}

 

front

reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
//n:容器中元素的位置。注意,第一个元素的位置为0(不是1)。成员类型size_type是一个无符号整数类型。
int main()
{vector<int> v(5, 1);cout << v.front() << endl;return 0;
}

back 

 

reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
//n:容器中元素的位置。注意,第一个元素的位置为0(不是1)。成员类型size_type是一个无符号整数类型。
int main()
{int arr[] = { 1,2,3,4 };vector<int> v(arr, arr + 4);cout << v.back() << endl;return 0;
}

范围for

vector是支持迭代器的,所以我们还可以用范围for对vector容器进行遍历。(支持迭代器就支持范围for,因为在编译时编译器会自动将范围for替换为迭代器的形式)

int main()
{vector<int> v(2, 10);for (auto e : v){cout << e << " ";}cout << endl;return 0;
}

 

vector及迭代器的操作

迭代器:

接口名称使用说明

       begin

返回指向第一个元素的迭代器

        end

返回指向最后一个元素的下一个位置的迭代器

      rbegin

返回指向最后一个元素的反向迭代器

       rend

返回指向第一个元素的前一个位置的反向迭代器

 这些接口的用法和迭代器用法类似,如果想了解更多可以去迭代器文章里面查看

begin和end

通过begin函数可以得到容器中第一个元素的正向迭代器,通过end函数可以得到容器中最后一个元素的后一个位置的正向迭代器。
正向迭代器遍历容器:

int main()
{vector<int> v(10, 2);//正向迭代器遍历容器vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;return 0;
}

rbegin和rend

通过rbegin函数可以得到容器中最后一个元素的反向迭代器,通过rend函数可以得到容器中第一个元素的前一个位置的反向迭代器。
反向迭代器遍历容器:

int main()
{vector<int> v(10, 2);//反向迭代器遍历容器vector<int>::reverse_iterator rit = v.rbegin();while (rit != v.rend()){cout << *rit << " ";rit++;}cout << endl;return 0;
}

外传

operator[]会检查越界,这里面的size为0,一检测就会越界

  1. v1.reserve(10); 这行代码只是预留了足够的空间来存储10个char类型的元素,但并没有实际创建这些元素。reserve函数只影响vector的容量,而不改变它的大小。

  2. 循环中的v1[i] = i; 试图访问并赋值给vector的元素。由于v1的大小(size)实际上是0(没有包含任何元素),这里的访问是越界的,因为它尝试访问并写入未初始化的内存位置。

为了修复这个问题,应该使用push_back方法或者在调用reserve后使用resize来实际创建元素:push_back插入数据会依次把size给涨上去

二者都会对越界进行检查,at是抛异常,[]是断言, 断言更暴力一点

使用find查找3,并删除,如果有多个3应该怎么搞?

迭代器失效的问题,删除了第一个3之后,就会失效

在C++中,迭代器失效(Iterator invalidation)是指当容器发生某些修改操作(如添加、删除元素等)后,其迭代器不再指向有效的容器元素,或者不再有有效的行为。迭代器失效可能会导致未定义行为,包括访问无效内存,这在程序中可能会导致错误或崩溃。不同类型的容器对迭代器的失效规则各有不同:

1. vectorstring

  • 添加元素:当通过push_back()insert()vector添加元素时,如果引发了重新分配(即容器当前的容量不足以容纳更多元素),则所有指向元素的迭代器、引用和指针都将失效。如果没有重新分配,那么只有指向插入点及之后元素的迭代器失效。
  • 删除元素:使用erase()删除vectorstring中的元素会使从删除点开始到末尾的所有迭代器失效。

vector的常见容量接口 

函数名称

功能

       size

返回字符串有效字符长度

     resize

扩容+初始化

   capacity

返回分配的存储容量大小(即有效元素的最大容量)

     empty

检测字符串是否为空串,是返回true,否则返回false

    reserve

对容量进行改变

size

int main()
{vector<int> v(10, 1);cout << v.size() << endl;return 0;
}

 

resize

void resize (size_type n, value_type val = value_type());

【resize】的功能则是 开空间 + 初始化,并且填上默认值

 这一块我们要通过调试来进行观察

接着调用resize,看到调试窗口中的size发生了变化,而且新增了3个为0的数据值 

 

 

capacity

size_type capacity() const;

对于capacity来说,就是容量大小,这里可以看到其与capacity是一同增长的,也为10

int main()
{vector<int> v(10, 1);cout << v.size() << endl;cout << v.capacity() << endl;return 0;
}

下面我们来看一下【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下的扩容机制是呈现 1.5 进行增长的

但是呢,在 Linux 下却始终是呈现的一个2倍的扩容机制 

empty

bool empty() const;
int main()
{vector<int> v;cout << v.empty() << endl;v.push_back(1);v.push_back(2);cout << v.empty() << endl;return 0;
}

当一开始进在初始化后是为空,但是在插入数据后就不为空了 

当size为 0 时,返回 1 

当size为 非0 时,返回 0

reserve

void reserve (size_type n);

它的主要功能是 开空间,避免频繁扩容 

int main()
{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';}}return 0;
}

外传:【reserve】和【resize】在使用中的易错点 

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

有同学说:感觉这代码也没什么错呀?怎么会有错误呢?

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

正确改进的方法:

vector<int> v2;
v2.resize(10);
for (size_t i = 0; i < 10; i++)
{v2[i] = i;
}
  • 或者呢,我们也可以写成下面这种形式。如果有同学还是要使用【reserve】的话就不要使用下标 + [] 的形式了,而是使用【push_back】的方式去不断尾插数据,因为在不断尾插的过程中就会去做一个扩容,这一点马上就会讲到

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win/mac达芬奇19下载:DaVinci Resolve Studio 19

DaVinci Resolve Studio 19 是一款功能强大的视频编辑和调色软件&#xff0c;广泛应用于电影、电视和网络节目的后期制作。这款软件不仅提供了专业的剪辑、调色和音频处理工具&#xff0c;还引入了全新的DaVinci Neural Engine AI工具&#xff0c;对100多项功能进行了大规模升级…

Vue3(五):组件通信详解(九种方法)

主要有九种方法&#xff0c;以下是详细解释及使用方法&#xff1a; 1.props props实现父子间的通信&#xff0c;是使用频率最高的。 &#xff08;1&#xff09;父传子&#xff1a;属性值是非函数。 以Father.vue和Child.vue 为例。 父组件中&#xff0c;引入子组件并给子组…

34、链表-合并K个升序链表

思路 1、直接全部放入集合中&#xff0c;然后排序&#xff0c;在进行构造节点返回 2、使用归并排序的方式&#xff0c;两两排序合并&#xff0c;最后合并大的。 3、第三中思路就比较巧妙了&#xff0c;可以使用小根堆&#xff0c;每次弹出堆顶&#xff0c;最小值&#xff0c…

【计算机网络】http协议的原理与应用,https是如何保证安全传输的

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

基于 RT-Thread 的 CMUX 串口多路复用的详细使用

一、CMUX 软件包的介绍 CMUX&#xff08;Connection Multiplexing &#xff09;&#xff0c;即连接&#xff08;串口&#xff09;多路复用&#xff0c;其功能主要在一个真实的物理通道上虚拟多个通道&#xff0c;每个虚拟通道上的连接和数据通讯可独立进行。  CMUX 软件包常用…

DRF ModelSerializer序列化类

ModelSerializer序列化类 【0】准备 模型表创建 from django.db import modelsclass Book(models.Model):name models.CharField(max_length64, verbose_name书名)price models.DecimalField(max_digits6, decimal_places2, verbose_name价格)publish models.ForeignKey(…

【C++打怪之路】-- C++开篇

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;C打怪之路&#xff0c;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &…

vue-cli2 与vue-cli3,vue2与vue3 初始化项目,本地vue项目,详细解析区别(2024-04-19)

目录 1、区别&#xff08;vue-cli2 与 vue-cli3 &#xff09; 2、例子1&#xff08;vue2项目&#xff09; 2.1 版本与命令行 2.2 项目本地截图 2.3 项目文件解析 &#xff08;1&#xff09;package.json 文件 &#xff08;2&#xff09;webpack.dev.conf.js文件 &#…

[大模型]Qwen-7B-hat Transformers 部署调用

Qwen-7B-hat Transformers 部署调用 环境准备 在autodl平台中租一个3090等24G显存的显卡机器&#xff0c;如下图所示镜像选择PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8 接下来打开刚刚租用服务器的JupyterLab&#xff0c;并且打开其中的终端开始环境配置、模型下…

NLP自然语言处理_序章

开一个新篇章&#xff0c;立一个flag&#xff0c;用一段时间来学习一下NLP&#xff0c;涨涨见识。 准备以B站 机器学习算法到transformer神经网络模型应用视频作为入门&#xff0c;此分类专门用于记录学习过程中的知识点以备自用。 一、何为NLP自然语言处理&#xff1f; NLP…

查看linux的主机配置脚本

废话不说 直接上指令 curl -Lso- bench.sh | bash 等待后&#xff0c;结果如图&#xff1a; 使用后没有问题&#xff0c;看情况使用 出事概不负责 介意勿用&#xff01;&#xff01;&#xff01;

RabbitMQ 各种通信模式的Python实现

一、RabbitMQ 原理 1、基本原理 RabbitMQ是流行的开源消息队列系统&#xff0c;用erlang语言开发。RabbitMQ是AMQP&#xff08;高级消息队列协议&#xff09;的标准实现。支持多种客户端&#xff0c;如&#xff1a;Python、Java、Javascript、C#、C/C,Go等&#xff0c;支持AJ…

使用yolov8 进行实例分割训练

1、基于windows 的ISAM标注 直接下载安装包&#xff0c;解压后即可使用 链接&#xff1a;https://pan.baidu.com/s/1u_6jk-7sj4CUK1DC0fDEXQ 提取码&#xff1a;c780 2、标注结果转yolo格式 通过ISAM标注后的json文件路径 原始json格式如下&#xff1a; ISAM.json 转 yolo.…

Leetcode算法训练日记 | day30

一、重新安排行程 1.题目 Leetcode&#xff1a;第 332 题 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发…

深入刨析 mysql 底层索引结构B+树

文章目录 前言一、什么是索引&#xff1f;二、不同索引结构对比2.1 二叉树2.2 平衡二叉树2.3 B-树2.4 B树 三、mysql 的索引3.1 聚簇索引3.2 非聚簇索引 前言 很多人看过mysql索引的介绍&#xff1a;hash表、B-树、B树、聚簇索引、主键索引、唯一索引、辅助索引、二级索引、联…

【Hadoop大数据技术】——Sqoop数据迁移(学习笔记)

&#x1f4d6; 前言&#xff1a;在实际开发中&#xff0c;有时候需要将HDFS或Hive上的数据导出到传统关系型数据库中&#xff08;如MySQL、Oracle等&#xff09;&#xff0c;或者将传统关系型数据库中的数据导入到HDFS或Hive上&#xff0c;如果通过人工手动进行数据迁移的话&am…

怎么看自己是不是公网IP?

当我们需要进行网络连接或者网络配置的时候&#xff0c;经常会遇到需要知道自己是否拥有公网IP的情况。公网IP是全球唯一的IP地址&#xff0c;在互联网上可直接访问和被访问&#xff0c;而私有IP则是在本地网络中使用&#xff0c;无法从互联网上直接访问。我们将介绍如何查看自…

笔记-----BFS宽度优先搜索

对于BFS&#xff1a;宽搜第一次搜到就是最小值&#xff0c;并且基于迭代&#xff0c;不会爆栈。 Flood Fill 模型 如果直译的话就是&#xff1a;洪水覆盖&#xff0c;意思就是像是从一个点一圈圈的往外扩散&#xff0c;如果遇见能够连通的就扩散&#xff0c;如果遇见无法联通的…

TCP三次握手,但通俗理解

如何用通俗的语言来解释TCP&#xff08;传输控制协议&#xff09;的三次握手过程&#xff1f; 想象一下你正在和朋友电话沟通&#xff0c;但你们之间不是心灵感应&#xff0c;而是需要通过清晰地听到对方的声音来确认通话质量良好。TCP三次握手就像是在电话拨通之前&#xff0…

爱普生发布一款16位MCU产品用于大电流LED驱动

精工爱普生发布一款内置Flash存储器的16位微控制器S1C17M13 该新品可以提供最大56mA的驱动电流用于驱动发光二极管(LED) 以往爱普生的微处理器大多继承了液晶驱动器电路&#xff0c;但近来随着工业自动化和家用设备使用7段LED显示的数量大幅增加&#xff0c;爱普生也推出了对应…