news 2026/8/28 19:12:13

深入浅出理解计算机核心知识系列【C++语言特性合集-STL_vector篇】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入浅出理解计算机核心知识系列【C++语言特性合集-STL_vector篇】

本人志在持续更新计算机系统、计算机网络、C++语言的核心知识点的系列合集,以易懂、全面的方式讲解底层知识。对于正在准备面试八股的朋友来说,本系列涵盖了本人面试中遇到的所有考点以及许多相关拓展知识,读完后能帮助你从容面对大部分面试拷打;对于想要深入学习计算机知识的朋友来说,本系列比较系统地介绍了操作系统和网络等重点内容,也举了不少例子,大大有助于你从底层的视角去理解计算机系统。
先说明,本系列恐怕不是计算机小白或是想速通期末的朋友们的目标,它需要一定系统和语言基础,也并不是面向教材和考试要求去讲解,所以更适合那些实操过代码、了解一些计算机系统知识、并且想要深入底层和扎实基础的朋友们去耐心学习。如果你是这样的人,欢迎阅读该系列文章,并分享自己的理解或提出文章中的模糊、错误的地方(不排除有)。
想要阅读系列中其他内容或想要持续关注本系列更新可移步:https://github.com/feiyangyang11/Cpp-Core-CS-Interview-Guide.git。


vector 底层原理详解

朴素版源码与实现思路

下面是简化版的 vector 容器源码,主要突出核心成员变量、核心函数逻辑

实现思路

vector 本身一般是栈上对象,且并不是个数组,实际数据都放在一个堆数组上,它本身只有三个指针类型的成员变量

本质上用三个指针管理一块连续动态内存:begin指向首元素,end指向已构造元素末尾,cap指向已分配空间末尾

元素内存通过allocator申请

管理器allocator_traits统一负责allocate / construct / destroy / deallocate(内存分配 / 对象构造 / 对象析构 / 内存回收)

支持动态扩容、移动和拷贝构造

整体核心就是:连续内存 + 三指针边界管理 + 对象生命周期与内存生命周期分离

源码

#include<memory>// std::allocator, allocator_traits#include<utility>// std::move_if_noexcept#include<cstddef>// size_ttemplate<classT>classMyVector{public:usingAlloc=std::allocator<T>;usingTraits=std::allocator_traits<Alloc>;private:Alloc alloc_;// vector 本质上最核心就是三个指针T*begin_=nullptr;// 第一个元素T*end_=nullptr;// 最后一个有效元素的下一个位置T*cap_=nullptr;// 已分配内存的末尾public:MyVector()=default;~MyVector(){// 调用所有已经构造出来的 T 的析构函数clear();// destroy -> 调用对象析构// deallocate -> 释放原始内存if(begin_){Traits::deallocate(alloc_,begin_,capacity());}}size_tsize()const{// 指针差 = 已经构造的元素数量returnend_-begin_;}size_tcapacity()const{// [begin_, cap_) 是 vector 拥有的整块内存returncap_-begin_;}boolempty()const{returnbegin_==end_;}T&operator[](size_t index){// vector 的 [] 本身不检查越界returnbegin_[index];}constT&operator[](size_t index)const{returnbegin_[index];}voidclear(){// 从后往前析构所有元素while(end_!=begin_){--end_;Traits::destroy(alloc_,end_);}}voidpush_back(constT&value){// 左值版本 -> 新元素需要拷贝构造if(end_==cap_){// 没空间了,进行扩容grow();}// 在 end_ 指向的“未构造内存”上构造 TTraits::construct(alloc_,end_,value);++end_;}voidpush_back(T&&value){// 右值版本 -> 新元素移动构造if(end_==cap_){grow();}Traits::construct(alloc_,end_,std::move(value));++end_;}template<class...Args>T&emplace_back(Args&&...args){if(end_==cap_){grow();}// 直接在 vector 内存中构造对象Traits::construct(alloc_,end_,std::forward<Args>(args)...);T*new_element=end_;++end_;return*new_element;}voidreserve(size_t new_capacity){// reserve 只扩 capacity,不改变 sizeif(new_capacity<=capacity()){return;}reallocate(new_capacity);}voidresize(size_t n){if(n<size()){// 缩小while(size()>n){--end_;Traits::destroy(alloc_,end_);}}elseif(n<=capacity()){// 容量够,直接构造新元素while(size()<n){Traits::construct(alloc_,end_);++end_;}}else{// 容量不够reallocate(/* new capacity >= n */);while(size()<n){Traits::construct(alloc_,end_);++end_;}}}private:voidgrow(){size_t old_capacity=capacity();// 实际 STL 不保证一定 ×2,这里只为了容易理解size_t new_capacity=old_capacity==0?1:old_capacity*2;reallocate(new_capacity);}voidreallocate(size_t new_capacity){T*new_begin=Traits::allocate(alloc_,new_capacity);T*new_end=new_begin;try{for(T*p=begin_;p!=end_;++p){Traits::construct(alloc_,new_end,std::move_if_noexcept(*p));++new_end;}}catch(...){/* * 如果迁移过程中失败,抛出异常 * 已经成功构造出来的 A、B 必须析构。 */while(new_end!=new_begin){--new_end;Traits::destroy(alloc_,new_end);}// 再释放新申请的原始内存Traits::deallocate(alloc_,new_begin,new_capacity);// 继续把异常抛给上层throw;}size_t old_capacity=capacity();for(T*p=begin_;p!=end_;++p){Traits::destroy(alloc_,p);}// 释放旧内存if(begin_){Traits::deallocate(alloc_,begin_,old_capacity);}//最后把三个核心指针指向新内存。begin_=new_begin;end_=new_end;cap_=new_begin+new_capacity;}};

核心成员变量

vector 的核心数据对象都保存在堆上,在栈对象中通过三个指针进行管理,左闭右开

T* begin_:保存数组第一个有效元素的地址

T* end_:保存数组最后一个有效元素的下一个位置的地址

T* cap_:保存已分配的数组内存的末尾位置地址

Alloc alloc_内存分配器实例,vector 通过它申请数组内存,它从 malloc / 自定义memory pool / arena……中申请内存。可以自定义内存分配器并通过 vector 的模板参数传入,如std::vector<T, MyAllocator<T>> v;,这样就 vector 就可以从自定义的内存池申请内存。如果使用的是默认的allocator,就会走标准的动态内存分配路径(new / malloc)申请内存

核心函数

长度、容量

  • vec.size():有效元素的个数,由end_ - begin_得到
  • vec.capcity():数组容量,表示当前数组能容纳的最多有效元素的个数,由cap_ - begin_得到
  • vec.resize(N):在数组末尾填充元素直至有效元素个数至 N,如果 N >vec.capcity()就扩容数组再填充;如果 N <vec.size(),那么就析构多出的数组元素
  • vec.reserve(N):改变数组容量,若 N >vec.capcity()就触发扩容;若 N <vec.capcity()则直接返回

内存与元素管理

对于数组元素,有插入与删除操作,对应着内存分配/释放、元素构造/析构

插入元素时,通常先分配可用内存,再在内存上调用元素的构造函数构造出对象

删除元素时,通常先调用对象的析构函数,再回收对象原来占有的这片内存

当然不是每次和删除都伴随着内存的分配与回收,此处只是为了建立一个清晰的模型来介绍数组的机制

  • Traits::allocate(alloc_,new_capacity):通过alloc_申请内存,返回新内存的起址
  • Traits::construct(alloc_,new_end,std::move_if_noexcept(*p)):在数组末尾构造出一个新对象。优先尝试调用移动构造函数来构造对象,通过std::move_if_noexcept(*p)判断该类的移动构造是否会抛异常,如果会抛异常就退化为拷贝构造
  • Traits::destroy(alloc_,new_end):主动调用数组最后一个有效元素的析构函数,移除对象
  • Traits::deallocate(alloc_,begin_,old_capacity):释放原数组申请的所有内存

Traits是提供统一接口的内存管理器,它调用alloc_提供的接口函数管理内存和对象。四个接口都要传入alloc_实例是因为——如果希望在操作内存或对象时增加一些自定义逻辑,比如统计构造次数、使用特殊内存、记录调试信息……就需要传入自定义alloc_。为了兼容这种需求,所以Traits的接口都要求传入alloc_实例

扩容

数组元素个数即将超出容量——触发扩容,首先调用grow()

grow():算出new_capacity(通常扩充为原容量的 2 倍或 1.5 倍),然后调用reallocate(new_capacity)

reallocate(new_capacity):通过静态方法Traits::allocate申请new_capacity大小的新内存,然后把原数组元素迁移过去,优先移动构造,其次拷贝构造。如果构造过程中抛出异常,立即析构所有对象并释放新申请的内存,然后将异常抛给上层;如果成功构造,就析构原有内存上的对象并释放所有内存,然后更新三个指针

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

Agent 工程笔记①:工具调用失败时,先查哪三层

Agent 靠工具调用&#xff08;tool calling&#xff09;读写文件、跑命令、查接口。失败时&#xff0c;控制台往往只丢一句「tool error」&#xff0c;排障却可能停在错误层。 本系列叫「Agent 工程笔记」。第一篇只建立排查顺序&#xff1a;先分清失败发生在哪一层&#xff0c…

作者头像 李华
网站建设 2026/8/28 18:59:11

197、运动相机水下拍摄的色彩补偿——基于深度估计的R通道衰减校正与白平衡联动算法

197、运动相机水下拍摄的色彩补偿——基于深度估计的R通道衰减校正与白平衡联动算法 去年夏天在海南做的那批运动相机水下固件,至今想起来还觉得牙根发痒。客户反馈说潜水到五米以下,拍出来的视频红彤彤一片,像蒙了一层夕阳滤镜。我们第一反应是白平衡没调好,把AWB的色温范…

作者头像 李华
网站建设 2026/8/28 18:58:49

machine 花纹钢板 GB/T 3277-1991

概述 这份是旧国标 GB/T 3277-1991 花纹钢板的技术图纸与参数表&#xff0c;规定了热轧花纹钢板的 3 种花纹型式、尺寸公差、理论重量、供货要求等。 充&#xff1a;该标准已更新为 GB/T 3277-2015&#xff0c;本章节是 1991 旧版内容。 T、三种花纹形式 菱形花纹 纹路为交叉…

作者头像 李华
网站建设 2026/8/28 18:56:21

手把手教你本地部署 Dify

Dify 是一个开源项目&#xff0c;支持可视化编排 AI 工作流、RAG 管道&#xff0c;并集成了大量模型。最重要的是&#xff0c;自托管部署非常方便&#xff0c;只需几条命令。 准备工作 确保你已经具备&#xff1a; ✅ Docker Desktop 正常运行&#xff08;docker version 有 …

作者头像 李华
网站建设 2026/8/28 18:54:58

2篇2章1节:认识横断面研究和其流程

临床流行病学观察性研究体系包含多种经典设计类型,不同研究方法在时间维度、因果推断能力、应用场景上差异显著,直接决定科研设计的科学性与结果可信度。横断面研究作为临床实操中最常用、易落地的基础研究方案,凭借独特的时间同步性设计,广泛应用于疾病现状调研、人群特征…

作者头像 李华