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

从零了解Vector详细解析

与string的衔接

顺序表 vector ,是一个标准的模板

vector是一个双参数模板:

  • 第一个参数:T→ 容器里面存的数据类型(int/Channel

  • 第二个参数:Alloc→ 内存分配器,默认值就是allocator<T>

2.allocator<T>是什么

std::allocator是 C++ STL 默认内存管理器。

vector 需要堆内存存放元素;allocator的工作就是:向操作系统申请内存、释放内存。

它封装了底层的:

  • new/delete

  • malloc/free

它干两件核心工作

  1. 分配原始内存:只开辟一块裸内存,不调用构造函数

  2. 释放内存:归还内存,不调用析构函数

区分两个概念

  • 分配内存:找一块空地

  • 构造对象:在这块空地上调用类的构造函数

emplace_back:allocator 先分配内存 → 然后原地构造对象,正好就是你之前代码_channels.emplace_back(wfd,subid)的完整流程。

而 string 是一个具体类型,在库中string本质为 basic_string <char> 的别名

vector是类模板,类模板必须显示实例化

std::vector<T>—— 这是模板(图纸),不是一个可用的类型。

显式实例化:给模板参数传值,造出一个真实的类(成品)

std::vector<int> v1; // 显式实例化,得到一个实实在在的类 std::vector<int> v2(10,1); std::vector<int> v3(v2.begin(),v2.end());

下面这个写法根本不能用来定义变量

为没有显式实例化,就是只写了模板名字,没有给尖括号传类型参数

std::vector; // 报错!!没有填模板参数T

遍历vector的方法:

用迭代器:

vector<int>::iterator it = v3.begin();

范围for:

for(auto e;v3) { //... }

vector不会缩容

而string会缩容:重新分配更小的内存 + 拷贝数据 + 释放旧的大内存

(操作系统的堆内存管理规则是:分配的内存块必须整体释放,无法拆分归还)

vector::resize

把vector的数据个数改为 n(size也会变)

<size:删除数据

>size:插入数据,空间不够就扩容

vector::operator[]

operator[]就是下标运算符重载

v3[i]编译器等价翻译成:v3.operator[](i)

1.两个重载版本
① 非 const 版本 —— 可读、可修改元素
int &a = v3[0]; a = 666; //可以修改容器里面的对象

返回值:引用reference→ int&

② const 版本(容器被 const 修饰时调用),只读
const std::vector<int>& b = v3; const int& c = b[0]; // c = 999; //报错,不能修改
2.operator [] 最核心特点:不做越界检查
std::vector<int> v{10,20,30}; v[100] = 999; //下标100远远超出范围

编译不会报错,运行时不会抛出异常,直接访问非法内存 → 未定义行为 (UB),程序可能随机崩溃、乱改内存。

这是和.at()的最大区别

方法越界检查越界行为
vec[i]operator[]无检查未定义行为,危险
vec.at(i)边界检查抛出 std::out_of_range 异常,安全

示例对比

v.at(100); //越界 →抛异常,可以try‑catch捕获 v[100]; //越界 →直接野内存访问,无法捕获
3.返回引用
std::vector<int> v = {1,2,3};int x = v[0]; // 值拷贝,x是副本,改x不会影响vint &y = v[0]; // 拿到容器内部元素的引用,改y就改v内部元素 y = 99;// v[0] 变成99
4.迭代器 / 引用失效大坑(网络编程必踩坑)

当你调用push_back / emplace_back触发 vector 扩容

之前用operator[]获取到的引用Channel& ch = _channels[0];直接失效!变成野引用,再使用就是 UB 崩溃。

例子:

std::vector<int> v{1,2,3};int &ref = v[0]; v.reserve(100); //扩容!内存搬家! ref = 99; //野引用!未定义行为!
5.底层原理(vector 内存连续)

vector 的元素在内存一块连续数组。operator[]底层实现逻辑简化:

//伪代码 T& operator[](size_t pos) { return *( _start + pos ); }

_start指向数组首地址,下标就是指针偏移。

所以vec[2]*(vec.begin()+2)完全等价。

6.什么时候用 [],什么时候用 at ()

  1. 你已经手动保证下标一定合法 → 用[],速度更快,无额外检查开销(循环遍历 i 从 0 到 size ()-1)

for(int i=0;i<_channels.size();i++) { auto &ch = _channels[i]; //安全,i一定合法 }
  1. 下标来自外部输入、不确定是否合法 → 用.at(),开启越界保护

vector::insert

insert:在指定迭代器位置,插入一个 / 一批元素。

注意:vector 内存连续,插入中间位置,后面所有元素向后移位,效率低 O (n)

重载 1:插入 n 个相同的值
iterator insert(iterator pos, size_type count, const T& value);
std::vector<int> v = {1,2,3}; v.insert(v.begin(), 3, 88);// 在最前面插入3个88// v: 88,88,88,1,2,3
重载 2:插入一段区间 [first, last)
template<class InputIt> iterator insert(iterator pos, InputIt first, InputIt last);
std::vector<int> a = {1,2,3}; std::vector<int> b = {100,200};// 在a的尾部,插入b的全部元素 a.insert(a.end(), b.begin(), b.end());// a: 1,2,3,100,200
重载 3:初始化列表 C++11
v.insert(v.begin(), {5,6,7});
返回值极其重要(大坑!迭代器失效)

vector 一旦在中间insert,有可能触发扩容,所有旧迭代器全部失效!

std::vector<int> v = {1,2,3};auto it = v.begin()+1; v.insert(it, 99); //插入后,it失效!!不能再用it!// 正确做法:用insert返回的新迭代器 it = v.insert(v.begin()+1, 99);

不要保存旧迭代器;insert返回新迭代器,赋值回去。

vector::erase

erase的作用:删除 vector 中一个或者一段区间的元素

注意: vector 内存连续,删除中间元素后,后面所有元素必须向前移动补齐空位,时间复杂度 O (n);并且会造成迭代器失效,这是最高频的坑

重载 1:删除单个元素(传迭代器)
iterator erase(iterator pos);
重载 2:删除一段区间,左闭右开

[first, last)

iterator erase(iterator first, iterator last);

返回值:返回被删除元素的下一个位置的新迭代器

最经典坑:一边遍历一边删除

错误代码(失效崩溃)

std::vector<int> v = {1, 2, 2, 3}; for(auto it = v.begin(); it != v.end(); it++) { if(*it == 2) { v.erase(it); // erase后it立刻失效!循环下一次it++直接崩溃 } }

正确写法(利用 erase 返回值)

for(auto it = v.begin(); it != v.end(); ) { if(*it == 2) { it = v.erase(it); //接收返回的有效迭代器,不做it++ } else { ++it; } }

原理:erase返回删除点后面合法的迭代器,不需要再it++

vector::push_back

在 vector 的尾部追加一个元素

// 范围for for (auto e : v2) { cout << e << endl; } //其中 e 得到的是v2中所存的类型的深拷贝,而v2类型为string,拷贝效率低 ——> 用引用 for (const auto &e : v2)
for循环
// 二维数组,如4*5的 vector<int> v(5, 1); vector<vector<int>> v2(4, v); for (auto e : v2) { for (auto e1 : e) { cout << e1 << " "; } cout << endl; }

简易版vector的底层模型(STL 源码思路,简化掉分配器 allocator)

template <class T> class vector { private: T *_a; size_t _size; size_t _capacity; };
  1. _a

_a 是一个指针,类型:T*

_a 保存堆上那块连续数组内存的起始地址。

  • vector 的所有元素,并不存在栈上;而是在堆 (heap) 上面开辟一块连续的内存存放。

  • _a 就是指向这块堆数组第一个元素的指针。

  1. 三个成员变量一一对应含义
成员含义
T* _a内存起始指针,堆数组首地址
size_t _size当前元素个数。你能访问的有效元素数量。v.size() 返回的值
size_t _capacity已经分配的内存总容量。这块堆内存一共可以放下多少个 T 对象。v.capacity()

内存布局示意图:

堆内存: [ T0 ][ T1 ][ T2 ][ T3 ][ 空闲 ][ 空闲 ] ↑ _a _size = 3 //有效元素0,1,2 _capacity = 6 //整块内存一共能存6个元素

vector下标运算符重载

//可读可写,非const对象调用 T& operator[](int i) { return _a[i]; } //只读版本,const对象调用,不能修改元素 const T& operator[](int i) const { return _a[i]; }
下标运算符operator[]底层怎么实现

依靠_a指针偏移

T& operator[](size_t pos) { return _a[pos]; //等价于 *( _a + pos ) }

_a+pos:指针向后偏移 pos 个 T 类型的距离,找到对应元素。 这也就是为什么vector下标访问速度极快。

push_back 扩容时_a发生了什么
  1. 判断:_size == _capacity→ 内存满了,必须扩容

  2. 开辟一块更大的新堆内存,得到新指针T* new_a

  3. _a指向的旧内存所有元素拷贝到new_a

  4. 释放旧的 _a 指向的堆内存

  5. _a = new_a;,让指针指向新内存

  6. _capacity更新为新容量

  7. 在尾部放入新元素,_size++

扩容后旧的_a内存被释放! 所以之前保存的引用、迭代器(本质就是基于旧_a的指针)全部失效。

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

相关文章:

  • 网易深度学习算法岗笔试题复盘:从逻辑回归到KMP的备考路线
  • 警惕!只会敲命令的Linux运维将被淘汰,不懂安全的你没有未来了
  • DeepSeek+Codex CLI:一句话生成LaTeX Beamer PPT的实战指南
  • 具身智能入门路线与数据清洗:从树莓派小车到商业化落地
  • Transformer雷达回波外推实战:短临降水预测从数据到模型全解析
  • AI异构计算工程师必知:从体系结构到CUDA性能优化
  • 基于LLM的小市值股票多信号量化交易策略框架
  • 大模型面试高频考点全解析:从Transformer到RAG部署
  • 移动端安全实习生笔试全解析:考点、题型与备考路径
  • GPR、贝叶斯网络与LSTM在时序预测中的协同范式
  • Go 1.21 sync.OnceValue/OnceFunc 实战:三行搞定懒加载单例,告别手写双重检查
  • 【亲测有效】VS Code 已安装中文语言包却仍是英文的解决办法
  • Python从入门到精通完整学习路线(2026最新版)
  • Hackertab.dev(极客页面) v1.26.13 免费安装版
  • C++单元测试实战:GoogleTest从入门到CI落地
  • 深度学习算法岗笔试复盘:核心考点与复习路线全拆解
  • Transformer前置概念详解:从RNN到自注意力与位置编码
  • Python驱动的计算机视觉:从理论到实践的全栈指南
  • 论文AI率太高怎么降?有保障承诺的AI智能降重工具推荐,降AI率没达标直接退全款
  • Linux内核工程师笔试题深度解析:进程调度、内存管理与并发同步
  • 网易计算机视觉算法岗笔试:深度学习核心考点与备战框架
  • Redis命令:EXPIRETIME
  • 自托管数据管理器UI重构实战:从v1到v2的界面与性能优化
  • Java课程设计实战:员工工资管理系统V3完整实现
  • 腾讯校招2016编程题解析:格雷码、摩尔投票与动态规划
  • 从零搭建JARVIS语音助手:语音识别+大模型+语音合成全流程
  • B站社招面试全流程复盘:从投递到Offer的备考策略与避坑指南
  • 容器预热预跳转方案
  • 小苯的能量项链【牛客tracker 每日一题】
  • 0.3%差距背后的技术选型真相:从DeepSeek接入Claude Code看工程成本