news 2026/7/13 21:24:55

STL中vector的使用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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++ 容器的威力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/13 21:22:31

斯坦福大学:让AI学会“记笔记“,竟能让小模型打败顶级大模型?

这项由斯坦福大学团队完成的研究以预印本形式发布于2026年7月1日&#xff0c;论文编号为arXiv:2607.01224&#xff0c;有兴趣深入了解的读者可以通过该编号查询完整论文。人类有一种奇妙的能力&#xff0c;叫做"元记忆"——你知道自己记住了什么、忘记了什么&#xf…

作者头像 李华
网站建设 2026/7/13 21:20:30

如何用win-acme解决Windows服务器SSL证书自动化难题:完整实践指南

如何用win-acme解决Windows服务器SSL证书自动化难题&#xff1a;完整实践指南 【免费下载链接】win-acme Automate SSL/TLS certificates on Windows with ease 项目地址: https://gitcode.com/gh_mirrors/wi/win-acme 你是否在为Windows服务器上繁琐的SSL证书管理而头疼…

作者头像 李华
网站建设 2026/7/13 21:19:50

Ruby JSON库高级用法:自定义类型序列化与反序列化终极指南

Ruby JSON库高级用法&#xff1a;自定义类型序列化与反序列化终极指南 【免费下载链接】json JSON implementation for Ruby 项目地址: https://gitcode.com/gh_mirrors/json13/json JSON作为现代Web开发中最常用的数据交换格式&#xff0c;在Ruby开发中扮演着至关重要的…

作者头像 李华
网站建设 2026/7/13 21:14:00

微信接口开发的三大通信机制全解析

在做微信接口二次开发&#xff08;比如接入个人号自动化、社群机器人、运维告警通知&#xff09;时&#xff0c;无论是用什么语言或框架&#xff0c;最底层的逻辑都绕不开与微信服务器的通信。 很多刚接触这块的后端开发&#xff0c;在写完 Demo 上线后经常遇到奇奇怪怪的 Bug…

作者头像 李华