深入解剖STL Vector:从底层原理到核心接口的灵活运用

深入解剖STL Vector:从底层原理到核心接口的灵活运用


在这里插入图片描述
💗博主介绍:计算机专业的一枚大学生 来自重庆 @燃于AC之乐✌专注于C++技术栈,算法,竞赛领域,技术学习和项目实战✌💗
💗根据博主的学习进度更新(可能不及时)
💗后续更新主要内容:C语言,数据结构,C++、linux(系统编程和网络编程)、MySQL、Redis、QT、Python、Git、爬虫、数据可视化、小程序、AI大模型接入,C++实战项目与学习分享。
👇🏻 精彩专栏 推荐订阅👇🏻
点击进入🌌作者专栏🌌:
Linux系统编程
算法画解
C++
🌟算法相关题目点击即可进入实操🌟
感兴趣的可以先收藏起来,请多多支持,还有大家有相关问题都可以给我留言咨询,希望希望共同交流心得,一起进步,你我陪伴,学习路上不孤单!

文章目录

前言

回想初次接触STL时,面对庞大知识体系和复杂底层实现的那种茫然感,至今记忆犹新。

最近,十几个小时的钻研,无数次的调试与画图,我终于将网上学习,《STL源码剖析》中vector这一章的精华,结合自己的理解整理成文。

这篇文章记录了我啃源码时的一个个“顿悟”时刻——从最初只把vector当作“会变大的数组”,到真正看懂它的迭代器设计、内存管理和性能奥秘。侯捷老师的书让我明白:真正掌握一个工具,必须看到它最底层的模样。

现在我把这份万字长文分享给你。这里有vector的完整解剖图,有每个关键操作背后的源码逻辑,更有从实践中总结的使用技巧。

让我们一起,从使用者变为真正的理解者。

希望这篇文章能成为你深入STL世界的第一步。

在这里插入图片描述

1.vector概述

vector的数据安排以及操作方式于array非常相似,二者唯一差别在于对空间的灵活运用。

array是静态空间,一旦配置,不能改变,用户要重新配置空间,移动元素,释放旧空间。vector的内部机制会自行扩充空间。

因此vector给我们带来了很大的灵活性,不用担心空间不够用,而去开一个大array。

vector的技术实现在于对大小的控制以及数据分配时的移动效率

引:如果旧空间用满了,每加一个元素就扩容,(配置新空间,挪动数据,释放旧空间),时间成本高,实为不智,所以应有未雨绸缪的考虑。

2.vector的迭代器

vector维护的是一个连续的线性空间,普通的指针可以满足要求,操作行为如:operator*,operator->,operator++,operator–,operator+,operator-,operator+=,operator-=,vector支持随机存取,而普通指针正有这样的能力。

底层代码:

//vector的迭代器template<classT,classAlloc= alloc>classvector{public:typedef T value_type; tyoedef value_type* iterator;//vector的迭代器是普通指针//...};

3.vector的数据结构

非常简单,现象连续空间,用两个迭代器start,finish分别指向配置得来的连续空间被已使用的范围,用迭代器end_of_storage指向整块连续空间(含备用空间)的尾端:其实就是最后一个空间的下一个位置。

底层代码:

template<classT,classAlloc= alloc>classvector{//...protected: iterator start;//目前使用空间的头 iterator finish;//目前使用空间的尾 iterator end_of_storage;//目前可用空间的尾};

为降低空间配置速度成本,vector实际配置的大小可能比用户需求更大,以备扩容,这就是容量(capacity),vector的容量大于等于其大小。

运用start,finish,end_of_storage三个迭代器可以容易实现大小,容量,首尾,判断,[], begin(),end()……

底层代码:

//用start,finish,end_of_storage实现template<classT,classAlloc= alloc>classvector{//...public: iterator begin(){return start;} iterator end(){return finish;} iterator size(){returnsize_type(end()-begin());} size_type capacity()const{returnsize_type(end_of_storage -begin());}boolempty()const{returnbegin()==end();} reference operator[](size_type n){return*(begin()+ n);} reference front(){return*begin();} reference back(){return*(end()-1);}};
在这里插入图片描述

4.vector的构造和内存管理

在gcc编译器下是2倍扩容,MSVC是1.5倍扩容。(扩大,size())

如下:

#include<iostream>#include<vector>#include<algorithm>usingnamespace std;intmain(){ vector<int>iv(2,9); cout <<"size="<< iv.size()<< endl;//size=2 cout <<"capacity="<< iv.capacity()<< endl;//capacity=2 iv.push_back(1); cout <<"size="<< iv.size()<< endl;//size=3 cout <<"capacity="<< iv.capacity()<< endl;//capacity=4 iv.push_back(2); cout <<"size="<< iv.size()<< endl;//size=4 cout <<"capacity="<< iv.capacity()<< endl;//capacity=4 iv.push_back(3); cout <<"size="<< iv.size()<< endl;//size=5 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8 iv.push_back(4); cout <<"size="<< iv.size()<< endl;//size=6 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8for(int i =0; i < iv.size();++i) cout << iv[i]<<' ';//9 9 1 2 3 4  cout << endl; iv.push_back(5); cout <<"size="<< iv.size()<< endl;//size=7 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8for(int i =0; i < iv.size();++i) cout << iv[i]<<' ';//9 9 1 2 3 4 5 cout << endl; iv.pop_back(); iv.pop_back(); cout <<"size="<< iv.size()<< endl;//size=5 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8 iv.pop_back(); cout <<"size="<< iv.size()<< endl;//size=4 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8 vector<int>::iterator ivite =find(iv.begin(), iv.end(),1);if(ivite != iv.end()) iv.erase(ivite); cout <<"size="<< iv.size()<< endl;//size=3 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8for(int i =0; i < iv.size();++i) cout << iv[i]<<' ';//9 9 2 cout << endl; ivite =find(iv.begin(), iv.end(),2);if(ivite != iv.end()) iv.insert(ivite,3,7); cout <<"size="<< iv.size()<< endl;//size=6 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8for(int i =0; i < iv.size();++i) cout << iv[i]<<' ';//9 9 7 7 7 2 cout << endl; iv.clear(); cout <<"size="<< iv.size()<< endl;//size=0 cout <<"capacity="<< iv.capacity()<< endl;//capacity=8}

gcc(linux):

在这里插入图片描述


MSVC(vs):

在这里插入图片描述

vector提供许多constructors:

底层代码:

//构造函数,允许指定vector的大小和初值vector(size_type n,const T& value){fill_initialize(n, value);}//填充并初始化voidfill_initialize(size_type n,const T& value){ start =allocate_and_fill(n, value); finish = start + n; end_of_storage = finish;}//配置后填充 iterator allocate_and_fill(size_type n,const T& x){ iterator result = data_allocator::allocate(n);uninitialized_fill_n(result, n, x);return result;}

push_back()尾插元素时,检查是否还有备用空间,有就在其上插入元素,调整finish,vector变大,没有,就扩容(重新配置,移动数据,释放旧空间)

底层代码:
(篇幅有限,这里只做简略,全局函数具体实现未详细写出)

//尾插voidpush_back(const T& x){if(finish != end_of_storage){construct(finish, x);++finish;}elseinsert_aux(end(), x);}template<classT,classAlloc>voidvector<T, Alloc>::insert_aux(iterator position const T& x){if(finish != end_of_storage){//还有备用空间,在其上构造元素,以最后一个元素为初值construct(finish,*(finish -1))++finish; T x_copy = x;copy_bakword(position, finish -2, finish -1);*position = x_copy;}else{//无备用空间const size_type old_size =size();const size_type len = old_size !=0?2* old_zie :1;//原大小为0,变为1;不为0,变为2倍;前半段放原数据,后半段放新数据 iterator new_start = data_allocator::allocate(len); iterator new_finish = new_start;try{ new_finish =uninitialized_copy(start, position, new_start);construct(new_finish, x);++new_finish; new_finish =uninitialize_copy(position, finish, new_finish);}//...//析构并释放原vectordestroy(begin(),end());deallocate();//调整迭代器,指向新的vector start = new_start; finish = new_finish; end_of_storage = new_start + len;}}

动态增加大小,不是在原空间后连续接新空间,因为不能保证后面有足够且可分配的空间,以原来两倍的大小配置一块新空间,将原来空间的内容拷贝过来,更新构造元素,释放旧空间

5.vector的元素操作

pop_back,erase,clear,insert

底层代码:

//尾删voidpop_back(){--finish;destroy(finish);}//清除[first,last)所有元素 iterator erase(iterator first, iterator last){ iterator i =copy(last, finish, first);destroy(i, finish); finish = finish -(last - first);return first;}//删除某个 iterator erase(iterator position){if(position +1!=end())copy(position +1, finish, position);//全局函数--finish;destroy(finish);return position;}voidclear(){erase(begin(),end());}

erase逻辑:

在这里插入图片描述

insert逻辑:

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

6.接口具体使用介绍

6.1.constructor

在这里插入图片描述
#include<vector>#include<iostream>usingnamespace std;//构造与访问voidtest_vector1(){ vector<int> v1; vector<int>v2(10,1); vector<int>v3(v2.begin(), v2.end());for(size_t i =0; i < v3.size(); i++){ cout << v3[i]<<" ";//用[] 访问} cout << endl; vector<int>::iterator it = v3.begin();while(it != v3.end()){ cout <<*it <<" ";//迭代器++it;} cout << endl;for(auto e : v3){//范围for,底层就是迭代器 cout << e <<" ";} cout << endl;}intmain(){test_vector1();return0;}

6.2.resize

在这里插入图片描述


n < size(),删除数据,不缩容,n > size(),空间不够,扩容。

不支持头插,头删, 支持尾插,尾删(v.push_bacK(), v.pop_back())

6.3.reserve

在这里插入图片描述


要求向量容量至少能够容纳n个元素。
若n大于当前向量容量,该函数将导致容器重新分配存储空间,使其容量增加至n(或更大)。

其他情况,string : n比size()小最多也就缩容到size(),不会影响长度,内容。vector: vs,linux 不缩容

voidtest_vector2(){//不缩容 vector<int>v(10,1); v.reserve(20); cout << v.size()<< endl; cout << v.capacity()<< endl; v.reserve(20); cout << v.size()<< endl; cout << v.capacity()<< endl; v.reserve(5); cout << v.size()<< endl; cout << v.capacity()<< endl;}

6.4.insert

在这里插入图片描述


只支持迭代器

v.insert(v.begin(), 1);

v.insert(v.begin() + 3, 2); 3后插入

为了STL统一,因为后面list那些没有下标的概念。

insert,erase本质上要挪动数据,效率低。

6.5.erase

在这里插入图片描述


v.erase(v.begin());

v.rease(v.begin() + 1, begin() + 4); 2~5

voidtest_vector3(){ vector<int>v(5,6); v.push_back(7); v.insert(v.begin(),1); v.insert(v.begin()+3,9);for(auto e : v){ cout << e <<" ";} cout << endl; v.pop_back(); v.erase(v.begin()); v.erase(v.begin()+1, v.begin()+4);for(auto e : v){ cout << e <<" ";} cout << endl;}

v.clear()清除所有数据

vector可以比较大小

vector不支持流插入/提取,很灵活。

vector v; (没有\0)不能代替string(有\0,兼容c语言)。string还有很多需求和特性。

vector, vector<vecotr> , (二维数组),还可存自定义类型, 范围for里面用引用,减少拷贝。

voidtest_vector4(){ vector<string> v1; string s1("xxxx"); v1.push_back(s1); v1.push_back("yyyyy");for(constauto& e : v1)//每次都是取得string,&减少拷贝,const不变{ cout << e <<" ";} cout << endl;//二维数组 10 * 5 vector<int>v(5,1); vector<vector<int>>vv(10, v); vv[2][1]=2;//实际是连续的两个operator[][]的调用//vv.operator[](2).operator[](1) = 2; 两个operator[]不同for(size_t i =0; i < vv.size(); i++){for(size_t j =0; j < vv[i].size();++j){ cout << vv[i][j]<<" ";} cout << endl;} cout << endl;}
在这里插入图片描述


在这里插入图片描述

加油!志同道合的人会看到同一片风景。
看到这里请点个赞,关注,如果觉得有用就收藏一下吧。后续还会持续更新的。 创作不易,还请多多支持!

Read more

【C/C++】Linux epoll详解与实战

Linux epoll 详解与实战 一、什么是 epoll epoll 是 Linux 内核提供的高性能 I/O 事件通知机制(I/O event notification facility),专门用于监控大量文件描述符上的事件。它是 select 和 poll 的改进版本,解决了它们在处理大量并发连接时的性能瓶颈,是现代 Linux 高并发服务器的基石。 I/O 多路复用的演进 在理解 epoll 之前,我们需要了解 I/O 多路复用的背景。传统的阻塞 I/O 模型中,每个连接需要一个线程来处理,当并发连接数达到数万甚至数十万时,线程切换的开销会变得不可接受。I/O 多路复用允许单个线程同时监控多个文件描述符,当任何一个文件描述符就绪时,程序得到通知并进行处理。

By Ne0inhk

C++七级GESP所有知识点超详细指南

论文的主要内容如下: * GESP C++七级考试概述:介绍考试的基本情况、考核目标和能力要求,使用列表说明考试形式和时间分配。 * 数学库函数的高级应用:详细介绍三角函数、对数函数和指数函数的使用方法和应用场景,包含代码示例和表格对比。 * 复杂动态规划算法精解:分析二维动态规划、经典问题模型和优化技巧,通过实例讲解状态定义和转移方程。 * 图论算法的深入解析:阐述图的基本概念、遍历算法、最短路径算法和实际应用,包含多种存储结构的对比。 * 哈希表的原理与应用:讲解哈希表的工作原理、冲突解决方法和在C++中的实际应用,提供性能分析表格。 C++七级GESP所有知识点超详细指南 1 引言 1.1 GESP C++七级考试概述 GESP(Grade Examination of Software Programming)C++七级考试是中国计算机学会推出的软件编程能力等级认证中的高级别考试,旨在评估考生对C++编程语言和算法设计的深入理解以及实际应用能力。该考试面向已经掌握C++基础语法和常用数据结构,并希望进一步学习高级算法和复杂程序设计的学习者。通过七级考试的

By Ne0inhk
C++新手入门学习教程(完整版)

C++新手入门学习教程(完整版)

以下教程覆盖了 C++ 学习的各个方面,适合初学者循序渐进地学习。学习过程中,建议初学者多做练习和项目,以加深对理论知识的理解。希望这个教程能为你提供一个清晰的学习路径。 目录 第一章:C++ 简介 1.1 C++ 的历史与演变 1.2 C++ 的特点和优势 1.3 C++ 的应用领域 1.4 C++ 的未来展望 第二章:环境搭建 2.1 安装 C++ 编译器与 IDE Windows Linux Mac 2.2 配置开发环境 2.3 编译与运行示例程序 第三章:基本语法 3.1 C+

By Ne0inhk
C++幻象:内存序、可见性与指令重排

C++幻象:内存序、可见性与指令重排

C++ 井发的假象:内存序、可见性与指令重排 写在前面:当你第一次把 std::atomic、memory_order 这些词读到手软时,可能会觉得这是 OS 或硬件工程师的专属领域。但其实理解内存模型并不需要掌握每一条 CPU 手册的汇编,只要抓住核心概念与工程实践,你就能写出既高效又安全的并发代码。 本文面向有一定 C++ 并发基础的读者(知道线程、互斥量、基本的 std::atomic 用法),但想把“为什么这样”弄清楚。我们会从 std::atomic 的语义出发,讲清 CPU cache coherence、内存屏障(fence)、指令重排 和 happens-before 的关系——不是空洞的定义,而是大量实战例子、容易踩的坑和调试技巧。文风尽量自然、通俗,

By Ne0inhk