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

STL中vector的使用

vector是C++ 标准模板库(STL)的一个容器,它本质是一个封装了动态内存管理的数组,既保留了原生数组随机访问的高效性,又解决了固定长度、手动管理内存的痛点。

一,vector是什么

std::vector是 STL 中的顺序容器,底层在堆上开辟连续的内存空间存储元素,支持动态自动扩容,可以理解为「C++ 版的动态数组」。

它的核心特性可以总结为四点:

1,连续内存存储:所有元素在内存中紧密连续排列,支持 O (1) 时间复杂度的随机访问,原生指针可直接操作元素,兼容所有 C 风格接口。

2,动态自动扩容:无需提前声明固定长度,当元素数量超过当前内存容量时,会自动申请更大的内存并迁移数据,全程无需开发者手动管理。

3,尾操作高效,中间操作低效:尾部插入 / 删除的均摊时间复杂度为 O (1);头部或中间位置的插入 / 删除需要移动后续所有元素,时间复杂度为 O (n)。

4,类型安全:基于模板实现,编译期完成类型校验,相比原生 C 数组更安全,不易出现类型不匹配的问题。

二,vector的基础用法

2.1,构造和初始化

vector 提供了多种构造方式,覆盖不同初始化场景:

#include <vector> #include <iostream> int main() { // 1. 空 vector,无元素,size=0,capacity=0 std::vector<int> v1; // 2. 构造包含 5 个元素的 vector,元素为默认值(int 为 0) std::vector<int> v2(5); // 3. 构造包含 5 个元素,每个元素值都是 10 std::vector<int> v3(5, 10); // 4. 拷贝构造:用另一个 vector 完整复制 std::vector<int> v4(v3); // 5. 列表初始化(C++11 起),最直观的写法 std::vector<int> v5{1, 2, 3, 4, 5}; // 6. 迭代器范围初始化 std::vector<int> v6(v5.begin(), v5.begin() + 3); // 元素为 1,2,3 return 0; }

2.2 ,元素访问

vector 提供了多种访问元素的方式,各有适用场景:

std::vector<int> v{10, 20, 30, 40, 50}; // 1. 下标访问 []:无越界检查,越界属于未定义行为,性能最高 int a = v[2]; // 30 // 2. at() 方法:越界时抛出 std::out_of_range 异常,更安全 int b = v.at(2); // 30 // 3. 访问首尾元素 int first = v.front(); // 10 int last = v.back(); // 50 // 4. 获取底层原生指针,可对接 C 语言接口 int* ptr = v.data();

2.3 ,常用增删操作

std::vector<int> v{1, 2, 3}; // 尾部插入 v.push_back(4); // 尾部插入元素 4 v.emplace_back(5); // 尾部直接构造元素,性能更优 // 尾部删除 v.pop_back(); // 删除最后一个元素,无返回值 // 指定位置插入 v.insert(v.begin() + 1, 10); // 在第2个位置插入 10 // 指定位置删除 v.erase(v.begin() + 1); // 删除第2个元素 // 清空所有元素 v.clear();

2.4, 三种遍历方式

std::vector<int> v{1, 2, 3, 4, 5}; // 1. 下标遍历,适合需要索引的场景 for (size_t i = 0; i < v.size(); ++i) { std::cout << v[i] << " "; } // 2. 迭代器遍历,容器通用的遍历方式 for (auto it = v.begin(); it != v.end(); ++it) { std::cout << *it << " "; // 3. 范围 for 循环(C++11 起),语法最简洁 for (auto num : v) { std::cout << num << " "; }

三,底层原理:vector 是怎么工作的

只停留在「会用」层面,很容易踩坑。理解 vector 的底层实现,是写出高效、安全代码的关键。

3.1 ,核心数据结构:三个指针

1,_start:指向内存块的起始位置,也就是第一个元素的地址

2,_finish:指向最后一个有效元素的下一个位置

3,_end_of_storage:指向整块分配内存的末尾位置

通过这三个指针,就能推导出两个核心属性:

1,size():当前有效元素个数 =_finish - _start。这个计算基于指针算术,时间复杂度为 O(1)。size()返回的是容器中实际存储的元素数量,而不是内存容量。当容器为空时,_start == _finishsize()返回 0。需要注意的是,size()返回的类型是size_t(无符号整数),因此在循环条件中与有符号数比较时要注意类型转换问题。此外,size()是成员函数,调用时不会改变容器状态,是 const 成员函数。

2,capacity():当前内存总共能容纳的元素数 =_end_of_storage - _start

3.2,扩容机制全流程

当我们调用push_back/emplace_back时,如果size() == capacity(),说明当前内存已经满了,就会触发自动扩容,完整流程分为四步:

1,申请新内存:向系统申请一块更大的连续内存,扩容倍率由编译器实现决定 ——GCC 标准库为 2 倍扩容,MSVC 为 1.5 倍扩容。

2,迁移元素:将原内存中的所有元素,通过拷贝构造或移动构造(C++11 起)复制到新内存中。

3,释放旧内存:调用原内存中所有元素的析构函数,再把整块旧内存归还给系统。

4,更新指针:将三个指针全部更新,指向新内存的对应位置。

这也是为什么扩容后,所有指向原 vector 的迭代器、指针、引用都会全部失效 —— 因为原内存已经被释放了。

四,高频接口

4.1,reserve和resize:

这是最容易混淆的两个接口,核心区别在于操作的对象不同:

1,reserve(n):操作容量(capacity),只预分配内存,不创建、不销毁任何元素,不会改变size()。它的唯一作用是提前申请内存,避免后续插入时频繁扩容。

2,resize(n, val):操作有效元素数量(size),会真正构造或销毁元素,改变size();如果 n 大于当前 capacity,会自动触发扩容。

举个直观的例子:

std::vector<int> v; v.reserve(10); // capacity=10,size=0,没有任何有效元素 v[0] = 1; // 错误!越界访问,未定义行为 v.resize(10); // capacity=10,size=10,有10个值为0的元素 v[0] = 1; // 正确

push_back vs emplace_back:性能差异后面的文章中我会讲到。

4.2,clear()会释放内存吗?

答案是:没有。

clear()只会调用所有元素的析构函数,将size()置为 0,但底层内存不会释放,capacity()保持不变。如果 vector 曾经装过大量数据,后续不再使用,直接调用clear()会造成内存闲置。

五,vector最容易犯的错误

5.1 迭代器失效:最经典的坑

迭代器失效是 vector 开发中最常见的 bug 来源,主要有两类场景:

1,触发扩容的插入操作:所有迭代器、指针、引用全部失效;

2,中间位置的插入 / 删除:操作点之后的所有迭代器、指针、引用全部失效。

最典型的错误写法 —— 循环中删除元素:

很多人误以为reserve(n)之后就有了 n 个元素,可以直接用下标赋值,这是典型的错误。reserve只分配了内存,没有构造元素,此时size()仍然是 0,下标访问属于越界行为,会导致未定义错误。

// 错误写法:erase 后迭代器失效,后续 ++ 是未定义行为 for (auto it = v.begin(); it != v.end(); ++it) { if (*it == target) { v.erase(it); } } // 正确写法:使用 erase 返回的下一个有效迭代器 for (auto it = v.begin(); it != v.end(); ) { if (*it == target) { it = v.erase(it); } else { ++it; } }

5.2,reserve后直接下标访问

很多人误以为reserve(n)之后就有了 n 个元素,可以直接用下标赋值,这是典型的错误。reserve只分配了内存,没有构造元素,此时size()仍然是 0,下标访问属于越界行为,会导致未定义错误。

5.3,忽略扩容代价,频繁 push_back

如果知道最终要插入的元素数量,却不提前reserve,会导致 vector 多次扩容、多次拷贝全量数据,在数据量大时会带来非常明显的性能损耗。

六,性能优化最佳实践

1. 预知数据量时,提前 reserve 预分配

这是性价比最高的优化手段。如果能预估元素的大致数量,提前调用reserve()一次性分配足够内存,可以彻底避免中途扩容带来的拷贝开销。

2. 避免在头部 / 中间频繁插入删除

vector 的优势在尾部操作,头部 / 中间插入需要移动大量元素。如果业务场景需要频繁在中间增删,应该优先选择std::list。

最后

vector在最常见的场景(随机访问、尾部增删)下做到了极致性能,同时兼顾易用性。理解它的底层逻辑是为了写出更高效、更安全的代码:知道什么时候该预分配,知道哪些操作会导致迭代器失效,知道它的边界在哪里,才能真正发挥 C++ 容器的威力。

http://www.cnnetsun.cn/news/3352629.html

相关文章:

  • 斯坦福大学:让AI学会“记笔记“,竟能让小模型打败顶级大模型?
  • 国自然初稿、未发表数据别再随意上传给AI!阿里封禁事件给所有科研人的3条红线
  • 如何用win-acme解决Windows服务器SSL证书自动化难题:完整实践指南
  • Ruby JSON库高级用法:自定义类型序列化与反序列化终极指南
  • 微信接口开发的三大通信机制全解析
  • pdf-inspector字体编码处理:CID字体、ToUnicode映射与字符解码终极指南
  • pdf-inspector Python绑定使用指南:在Python项目中集成PDF处理功能
  • 小程序 毕设项目:基于位置的共享雨伞租赁与点位查询小程序设计 便民共享雨伞租借计费管理系统 (源码+文档,讲解、调试运行,定制等)
  • 小程序 毕设项目:校园闲置物品回收与交易服务系统的设计与实现 基于 SpringBoot 的校园二手交易信息管理系统的设计与实现 (源码+文档,讲解、调试运行,定制等)
  • AI Agent 面试题 664:Prompt注入的自动化检测和实时拦截方案
  • 如何通过AMD Quark工具链优化dbrx-base-FP8-KV模型:完整指南与性能提升技巧
  • 百元打造智能四轴飞行器:ESP-Drone开源无人机终极指南
  • Qwopus3.6-27B-Coder-bf16提示词工程:提升代码生成质量的10个专业技巧
  • PCSX2完全指南:免费开源PS2模拟器终极配置教程
  • ChatGPT无法使用?不是网络问题!2024Q2全球节点失效地图+3个高可用备用API路由(含Token自动轮转Python工具)
  • 物流企业物流调度和单据处理,推荐哪款Agent?企业级智能体选型深度测评与落地指南
  • 【DeepSeek企业级提示词库】:含127个经生产环境验证的工业级prompt,仅限本周开放下载
  • gpt-oss-120b-w-mxfp4-a-fp8-kv-fp8-fp8attn-no_lmhead_router模型推理性能优化实战
  • 2026年开题报告生成神器:10分钟完成高质量开题报告加开题PPT
  • Phi-4推理增强模型量化完全指南:从BF16到W4A16的完整转换流程
  • 从0开始学AI模型部署:用mlx-optiq打造专属NVIDIA-Nemotron-3-Nano-4B本地服务
  • AMD Ryzen AI混合优化实践:Qwen2.5_3B_Instruct_rai_1.7.1_npu_4K的完整部署方案
  • 深入理解Cosmos-AnomalyGen-PCB-2B架构:从掩码编码器到扩散模型的完整指南
  • AnyFlow-Wan2.1-T2V-1.3B-Diffusers核心技术解析:流图蒸馏如何实现任意步长生成
  • 2026年软件工程毕业生学习数据分析的价值
  • AMD Ryzen AI生态全景:Qwen-2.5_1.5B_Instruct_rai_1.7.1_npu_4K的定位与应用场景
  • 计算机小程序毕设实战-基于 SpringBoot 的校园心理危机预防干预小程序的设计与实现 高校学生心理健康数据监测与疏导系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 一站式掌握material-ui-chip-input属性配置:提升表单交互体验的完整指南
  • 电商人必须立刻部署的ChatGPT工作流(2024Q2紧急更新):含抖音小店自动回评、跨境多语言SKU优化、退货话术实时生成
  • Instant-NuRec性能优化技巧:提升3D高斯溅射生成效率的10个方法