【C++】Vector完全指南:动态数组高效使用

0. 官方文档

vector

1. vector介绍

Vector 简单来说就是顺序表,是一个可以动态增长的数组。

  1. vector是表示可变大小数组的序列容器。

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

  3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。

  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

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

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

优点:

  1. 支持下标随机访问,间接的就很好的支持了排序、二分查找、堆算法等等

缺点:

  1. 头部和中部的插入删除效率低。O(N),因为需要挪动数据。

  2. 插入数据空间不够需要增容,增容需要开新空间,拷贝数据,释放旧空间,会付出很大代价。

2. vector库函数的使用

2.1 vector 的基本构造、拷贝与赋值操作

vector 的默认构造与元素添加

vector<int> v1;  // 创建一个空的整型vector
v1.push_back(1); // 在vector末尾添加元素1
v1.push_back(2); // 在vector末尾添加元素2
v1.push_back(3); // 在vector末尾添加元素3
v1.push_back(4); // 在vector末尾添加元素4
  • vector<int> v1 使用默认构造函数创建一个空的vector容器

  • push_back() 成员函数用于在vector的末尾添加新元素

vector 的拷贝构造

vector<int> v2(v1); // 使用v1作为源创建v2(拷贝构造)
  • 拷贝构造函数 vector<int> v2(v1) 创建一个新vector,其元素是v1元素的完整拷贝

  • 两个vector对象完全独立,修改一个不会影响另一个

vector 的遍历与下标访问

for (size_t i = 0; i < v1.size(); ++i)
{cout << v1[i] << " "; // 使用下标运算符[]访问元素
}
  • size() 成员函数返回vector中元素的数量

  • 可以使用下标运算符 [] 像数组一样访问vector中的元素

vector 的赋值操作

vector<int> v3;
v3.push_back(60);
v3.push_back(61);
v3.push_back(62);
v3.push_back(63);v1 = v3; // 将v3的内容赋值给v1
  • 赋值操作 v1 = v3 会将v3的所有元素复制到v1中

  • 赋值后v1的原始内容会被替换,大小会调整为与v3相同

2.2 vector 的遍历与元素修改

使用下标遍历和修改元素

for (size_t i = 0; i < v.size(); ++i)
{v[i] *= 2;        // 修改元素值cout << v[i] << " "; // 访问元素值
}
  • 使用下标遍历是最直观的方式,类似于操作普通数组

  • 可以通过下标直接修改vector中的元素

使用迭代器遍历和修改元素

vector<int>::iterator it = v.begin(); // 获取指向第一个元素的迭代器
while (it != v.end())                 // 循环直到迭代器指向末尾
{*it *= 2;          // 解引用迭代器并修改元素值cout << *it << " "; // 访问元素值++it;              // 移动迭代器到下一个元素
}
  • begin() 返回指向第一个元素的迭代器

  • end() 返回指向最后一个元素之后位置的迭代器

  • 迭代器提供了类似指针的语法来访问和修改元素

使用范围for循环遍历元素

for (auto e : v)
{cout << e << " "; // 访问每个元素
}
  • 范围for循环是C++11引入的语法糖,代码更简洁

  • 编译器会自动将其转换为迭代器操作

  • 注意:这种方式默认是值拷贝,如果需要修改元素,应使用引用 for (auto& e : v)

2.3 vector 的迭代器类型

        一般来说分为三大种:正向、反向、const

普通正向迭代器

vector<int>::iterator it = v.begin();
while (it != v.end())
{*it *= 2;        // 可读写操作cout << *it << " ";++it;
}
  • 普通迭代器允许读取和修改指向的元素

  • 使用 iterator 类型定义,适用于需要修改vector内容的场景

const 正向迭代器

vector<int>::const_iterator it = vt.begin();
while (it != vt.end())
{// *it *= 2;     // 错误:不能修改const迭代器指向的值cout << *it << " "; // 只能读取++it;
}
  • const迭代器指向的元素不能被修改

  • 使用 const_iterator 类型定义,适用于只读访问场景

反向迭代器

vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{cout << *rit << " "; // 从后向前遍历++rit;               // 注意:++操作是向前移动
}
  • 反向迭代器从容器的末尾向开头遍历

  • rbegin() 返回指向最后一个元素的迭代器

  • rend() 返回指向第一个元素之前位置的迭代器

  • ++ 操作使迭代器向前移动(向容器开头方向)

const 反向迭代器

vector<int>::const_reverse_iterator crit = v.rbegin();
  • 结合了反向迭代和const特性的迭代器

  • 可以反向遍历容器,但不能修改元素值

函数参数中的const引用

void print_vector(const vector<int>& vt)
{vector<int>::const_iterator it = vt.begin();while (it != vt.end()){cout << *it << " ";++it;}cout << endl;
}
  • 使用const引用传参 const vector<int>& vt 避免不必要的拷贝

  • 函数内部使用const迭代器确保不会意外修改原始数据

  • 这是C++中常见的编程实践,既能提高效率又能保证数据安全

const对象与const迭代器

  • const对象只能使用const迭代器进行遍历

  • 在实际工程中,const对象常用于函数参数,防止函数内部修改调用者的数据

  • 使用const是正确的编程习惯,可以提高代码的可读性和安全性

2.4 容量相关

下面是一段扩容测试代码,插入1000个数据,观测容量空间变化:

void test_vector4()
{vector<int> v;cout << "making v grow:\n";size_t sz = v.size();for (int i = 0; i < 1000; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}

同样的代码,如下图,左边是linux环境下编译运行出的结果,右边是window的vs编译的结果:

  • 增容次数越少,效率越高,但可能导致的空间浪费越多。

  • 增容次数越多,效率越低,但可能导致的空间浪费越少。

v.reserve(1000);        // 扩容
v.resize(1000);         // 扩大size,额外扩出来的部分会自动补'\0'或自定义字符

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。

resize在开空间的同时还会进行初始化,影响size。

2.5 修改与查找

尾部插入元素

vector<int> v;
// 尾插 - 使用 push_back() 在容器末尾添加元素
v.push_back(5);
v.push_back(4);
v.push_back(3);
v.push_back(2);
v.push_back(1);
  • push_back() 是 vector 最常用的添加元素方法

  • 每次调用会在 vector 的末尾添加一个新元素

  • 如果当前容量不足,vector 会自动重新分配更大的内存空间

指定位置插入元素

// 头插 - 使用 insert() 在指定位置插入元素
v.insert(v.begin(), 0);     // 在开头插入元素0
v.insert(v.end(), -1);    // 在结尾插入元素-1
  • insert() 方法可以在任意位置插入元素

  • 第一个参数是迭代器,指定插入位置

  • 第二个参数是要插入的值

  • 在开头插入元素会导致后续所有元素向后移动,效率较低

删除指定位置元素

// 头删 - 使用 erase() 删除指定位置的元素
v.erase(v.begin());  // 删除第一个元素
  • erase() 方法删除指定位置的元素

  • 参数是一个迭代器,指向要删除的元素

  • 删除元素后,后面的所有元素会向前移动

  • 删除开头元素效率较低,因为需要移动所有后续元素

查找操作:使用标准库的 find 算法

// vector 没有提供内置的查找方法// 使用标准库中的 find 算法
vector<int>::iterator pos = find(v.begin(), v.end(), 5);
  • vector 本身不提供查找方法,需要使用标准库的 find 算法

  • find 需要包含 <algorithm> 头文件

  • find 接受三个参数:起始迭代器、结束迭代器和要查找的值

  • 查找范围是左闭右开区间 [first, last),即包含 first 但不包含 last

处理查找结果

if (pos != v.end())  // 检查是否找到元素
{v.erase(pos);    // 如果找到,删除该元素
}
  • find 返回一个迭代器,指向找到的元素

  • 如果没找到,返回结束迭代器 v.end()

  • 在删除前必须检查是否找到元素,否则可能引发未定义行为

vector 的排序操作:使用标准库的 sort 算法

// 使用标准库的 sort 算法对 vector 排序
sort(v.begin(), v.end());  // 默认升序排序
  • sort 需要包含 <algorithm> 头文件

  • 默认情况下,sort 按升序排列元素

  • sort 使用快速排序算法实现,平均时间复杂度为 O(N log N)

  • 排序范围也是左闭右开区间 [first, last)

2.6 标准库算法的说明

find 算法

// std::find 函数模板声明(简化)
template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value);
  • find 是一个函数模板,可用于任何支持迭代器的容器

  • 它在 [first, last) 范围内查找第一个等于 value 的元素

  • 如果找到,返回指向该元素的迭代器;否则返回 last

  • 使用 == 运算符进行比较,因此元素类型必须支持此操作

sort 算法

// std::sort 函数模板声明(简化)
template <class RandomAccessIterator>
void sort(RandomAccessIterator first, RandomAccessIterator last);
  • sort 要求迭代器是随机访问迭代器,vector 的迭代器满足此要求

  • 默认使用 < 运算符进行比较,因此元素类型必须支持此操作

  • 可以自定义比较函数来实现不同的排序规则

2.7 迭代器失效问题

1.增容导致的迭代器失效

看下面这段代码:

void test_vector8()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);vector<int>::iterator it = v.begin();v.push_back(6);v.push_back(7);while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}

如果我们把上面的代码修改一下,把push_back(7)注释掉,就可以运行:

        这是为什么呢?这个问题和动态增容有关。

        假设我们插入 7 之前的 v 有6个空间,即 v.capacity() 为6,当我们插入 7 后发生了增容,开辟了一块新的空间,v 的内容全都被拷贝去了新的空间,然而此时 it 迭代器还指向旧空间的 v.begin() ,it 迭代器就失效了。

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

        所以 push_back 、insert 、resize 、reserve 等可能扩容的接口都会导致迭代器失效。

解决办法(正确写法):

        很简单,不要在初始化迭代器后在调用可能导致扩容的接口就行了。

2.删除导致的迭代器失效

void test_vector9()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);// 要求删除容器中的所有偶数vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}++it;}for (auto e : v){cout << e << " ";}cout << endl;}

程序又崩溃了:

        当 it 到 2 的位置时,会删掉 2 ,然后后面的数据会往前挪,it++,此时 it 会直接略过 3 ,到 4 的位置。

        也就是说,删除 it 位置的元素后,it 就失效了,这里的失效是 it 迭代器已经失效了,再 ++it 就不行。这个问题在 vs 下会报错,是编译器强制检查的,但是在 gcc 下就不报错,但是结果不对,无法删除掉连续的偶数(比如把3改成30就无法删除30)。

        编译器检查迭代器失效原因:erase删除 it 位置元素后,it 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果 it 刚好是最后一个元素,删完之后 it 刚好是end的位置,而end位置是没有元素的,那么 it 就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

        总结:不管哪个平台下,erase(it) 后,it 就失效了,只是导致的结果不一样,总之都有各种各样的问题。

        作为编译器检查迭代器失效的印证,哪怕把代码改成下面这样也会报错(vs 会报错,linux 下正常运行)

解决办法(正确写法):

        通过官网对 erase 的返回值的介绍,我们可以知道,erase 的返回值是被删除元素的下一个元素的位置。我们只需要改一下代码就可以正常运行了:

void test_vector9()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);// 要求删除容器中的所有偶数vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);        // 修改}else{++it;}}for (auto e : v){cout << e << " ";}cout << endl;}

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

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

相关文章

关于无法导入父路径的问题

问题重现 有下面的代码&#xff1a; from ..utils import Config,set_DATA_PATH DATA_PATH set_DATA_PATH()报错如下&#xff1a;from ..utils import Config,set_DATA_PATH ImportError: attempted relative import beyond top-level package解决方案 #获取当前脚本所在目录的…

C/C++包管理工具:Conan

Conan是一个专为C/C设计的开源、去中心化、跨平台的包管理器&#xff0c;致力于简化依赖管理和二进制分发流程。Conan基于Python进行开发&#xff0c;支持与主流的构建系统集成&#xff0c;提供了强大的跨平台和交叉编译能力。通过Conan&#xff0c;开发者可以高效的创建、共享…

核心高并发复杂接口重构方案

核心高并发复杂接口重构方案 一、重构目标与原则 核心目标 提升接口性能:降低响应时间,提高吞吐量,降低资源使用 增强可维护性:拆解复杂逻辑,模块化设计,降低后续迭代成本 保障稳定性:通过架构优化和灰度策略,确保重构过程无服务中断 提升扩展性:设计灵活的扩展点,…

C++容器内存布局与性能优化指南

C容器的内存布局和缓存友好性对程序性能有决定性影响。理解这些底层机制&#xff0c;能帮你写出更高效的代码。 一、容器内存布局概述 不同容器在内存中的组织方式差异显著&#xff0c;这直接影响了它们的访问效率和适用场景。容器类型内存布局特点元数据位置元素存储位置std::…

Beautiful.ai:AI辅助PPT工具高效搞定排版,告别熬夜做汇报烦恼

你是不是每次做 PPT 都头大&#xff1f;找模板、调排版、凑内容&#xff0c;熬大半夜出来的东西还没眼看&#xff1f;尤其是遇到 “明天就要交汇报” 的紧急情况&#xff0c;打开 PPT 软件半天&#xff0c;光标在空白页上晃来晃去&#xff0c;连标题都想不出来 —— 这种抓瞎的…

阿里云携手MiniMax构建云原生数仓最佳实践:大模型时代的 Data + AI 数据处理平台

MiniMax简介MiniMax是全球领先的通用人工智能科技公司。自2022年初成立以来&#xff0c;MiniMax以“与所有人共创智能”为使命&#xff0c;致力于推动人工智能科技前沿发展&#xff0c;实现通用人工智能(AGI&#xff09;。MiniMax自主研发了一系列多模态通用大模型&#xff0c;…

一键生成PPT的AI工具排名:2025年能读懂你思路的AI演示工具

人工智能正在重塑PPT制作方式&#xff0c;让专业演示变得触手可及。随着人工智能技术的飞速发展&#xff0c;AI生成PPT工具已成为职场人士、学生和创作者提升效率的得力助手。这些工具通过智能算法&#xff0c;能够快速将文本、数据或创意转化为结构化、视觉化的演示文稿&#…

数据库基础知识——聚合函数、分组查询

目录 一、聚合函数 1.1 count 1.1.1 统计整张表中所有记录的总条数 1.1.2 统计单列的数据 1.1.3 统计单列记录限制条件 1.2 sum 1.3 avg 1.4 max, min 二、group by 分组查询 2.1 语法 2.2 示例 2.3 having 一、聚合函数 常用的聚合函数 函数说明count ([distinc…

改 TDengine 数据库的时间写入限制

一 sql连数据库改 改 TDengine 数据库的时间写入限制 之前默认了可写入时间为一个月&#xff0c;调整为10年&#xff0c;方便测试&#xff1a; SHOW DATABASES;use wi; SELECT CONCAT(ALTER TABLE , table_name, KEEP 3650;) FROM information_schema.ins_tables WHERE db_…

数码视讯TR100-OTT-G1_国科GK6323_安卓9_广东联通原机修改-TTL烧录包-可救砖

数码视讯TR100-OTT-G1_国科GK6323_安卓9_广东联通原机修改-TTL烧录包-可救砖刷机教程数码视讯 TR100-G1 TTL 烧录刷机教程固件由广东联通 TR100-G1 28 原版修改&#xff0c;测试一切正常1、把刷机文件解压出 备用&#xff0c;盒子主板接好 TTL&#xff0c;不会接自行查找 TTl 接…

TVS防护静电二极管选型需要注意哪些参数?-ASIM阿赛姆

TVS防护静电二极管选型关键参数详解TVS(Transient Voltage Suppressor)二极管作为电路防护的核心器件&#xff0c;在电子设备静电防护(ESD)、浪涌保护等领域发挥着重要作用。本文将系统性地介绍TVS二极管选型过程中需要重点关注的参数指标&#xff0c;帮助工程师做出合理选择。…

项目经理为什么要有一张PMP®认证?

在项目管理日益成为企业核心竞争力的今天&#xff0c;PMP已成为项目经理职业发展的重要“通行证”。这张由美国项目管理协会&#xff08;PMI&#xff09;颁发的全球公认证书&#xff0c;不仅是专业能力的象征&#xff0c;更在职业竞争力、项目成功率、团队协作等多个维度为项目…

Qt中QSettings的键值使用QDataStream进行存储

1. QDataStream介绍 数据流是编码信息的二进制流&#xff0c;与主机的操作系统、CPU 或字节顺序完全无关。例如&#xff0c;Windows 系统下 PC 写入的数据流可由运行 Solaris 的 Sun SPARC 读取。 您还可以使用数据流读/写raw unencoded binary data 。如果需要 "解析 &…

Typer 命令行工具使用示例

Typer 命令行工具使用示例 示例1&#xff1a;简单问候程序 代码 import typerapp typer.Typer()app.command() def greet(name: str):"""简单的问候命令"""typer.echo(f"Hello {name}!")if __name__ "__main__":app()使用…

关于CAN总线bus off 理论标准 vs 工程实践

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

CAN堆栈

PDU映射到HOH将硬件对象句柄HOH抽象成为硬件抽象层CanIf将pdu映射到硬件对象句柄上一个HOH代表一个Can控制器的一个消息缓冲区发送缓存区当所有Can硬件资源被占用时&#xff0c;LPDU存储在缓冲区中。发送取消为了解决优先级反转的问题&#xff0c;高优先级L-PDU会请求取消低优先…

sub3G和sub6G的区别和联系

Sub-3G 和 Sub-6G 的区别与联系Sub-3G 和 Sub-6G 是无线通信中频段的不同分类&#xff0c;尤其在4G LTE和5G网络中&#xff0c;定义了无线信号传输的不同频率范围。具体来说&#xff0c;Sub-3G 通常指的是低于3 GHz的频段&#xff0c;而 Sub-6G 是指低于6 GHz的频段。这些频段的…

【数据可视化-106】华为2025上半年财报分析:用Python和Pyecharts打造炫酷可视化大屏

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

Scikit-learn Python机器学习 - 特征预处理 - 归一化 (Normalization):MinMaxScaler

锋哥原创的Scikit-learn Python机器学习视频教程&#xff1a; 2026版 Scikit-learn Python机器学习 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 课程介绍 本课程主要讲解基于Scikit-learn的Python机器学习知识&#xff0c;包括机器学习概述&#xff0c;特征工程(数据…

LINUX_Ubunto学习《2》_shell指令学习、gitee

0、前言&#xff1a; 0.1、为什么学习shell脚本 学习Shell&#xff08;Shell脚本编程&#xff09;是提升系统管理和开发效率的重要技能&#xff0c;尤其在Linux/Unix环境中作用显著。Shell是用户与操作系统内核的接口&#xff0c;学习Shell有助于掌握系统工作原理。shell的核心…