STL源码刨析:序列式容器之vector

目录

        1.序列式容器和关联式容器

        2.vector的定义和结构

        3.vector的构造函数和析构函数的实现

        4.vector的数据结构以及实现源码

        5.vector的元素操作


前言

        本系列将重点对STL中的容器进行讲解,而在容器的分类中,我们将容器分为序列式容器和关联式容器。本章作为容器的实现源码的讲解,将简单介绍这两种类型的容器的区别,再对每一个类型所含的容器的实现源码进行讲解。


序列式容器和关联式容器

        序列式容器:按照元素的添加顺序来组织元素。提供了一种线性的数据结构,可以快速何位置的元素,插入和删除操作可能需要移动其他元素

        关联式容器:通过键值对来存储元素,并且通常使用某种形式的平衡树或哈希表来组织元素。特别是对于大量数据,关联式容器在查找、插入和删除操作上通常具有较高的效率

        序列式容器和关联式容器的区别:

序列式容器关联式容器
存储方式

元素添加顺序存储

按键的顺序或哈希值存储
访问速度支持快速随机访问不支持快速随机访问
插入和删除在插入和删除时要移动元素,可能较慢能提供快速的插入和删除操作
元素唯一性不保证元素的唯一性保证元素的唯一性
迭代器类型随机访问迭代器双向迭代器或正向迭代器

        表1.序列式容器和关联式容器的区别


vector的定义和结构

        vector属于序列式容器,定义在头文件<vector>中,但是SGI STL将其实现放在更底层的<stl_vector.h>头文件中。vector和数组很像,但是vector属于动态存储空间,能自动随着元素的插入而自行扩充空间以容纳新的元素,数组只能通过new或者malloc重新分配更大的空间,并将元素移动到新的空间。

        在阅读过《STL源码刨析:迭代器概念与Traits编程方法》后,我们应该清楚以下几点:

                1.每一个容器和具体的实现算法是分开定义的,而为了将容器和算法串联在一起,我们要为其定义迭代器(PS:vector的迭代器类型为随机迭代器,因为使用vector容器时支持下标操作)

                2.针对迭代器的类型,我们还需要对其封装并判断传入模板的类型(Traits编程方法)

                3.每一个容器都需要为其分配内存空间,所以我们还需要一个空间配置器         

        在以上三点的基础上,我们还需要对容器定义三个迭代器,分别指向使用空间的头,使用空间的尾和空闲空间的尾。所以我们的vector容器的结构应该大体如下:

//vector容器的大体结构
template<class T, class Alloc = alloc>    //alloc是默认的配置器
class vector{
public:typedef T value_type;           //传入的参数的类型    typedef value_type* pointer;    //传入的参数的指针typedef value_type* iterator;   //传入的参数的迭代器typedef value_type& reference;  //传入的参数的引用typedef size_t size_type;       //传入的大小typedef ptrdiff_t difference_type;    //传入的元素之间的距离
protector:typedef simple_alloc<value_type, Alloc> data_allocator;    //配置器的定义iterator start;         //表示可用空间的头iterator finish;        //表示可用空间的尾iterator end_od_storage;//表示空闲空间的尾
}//simple_alloc配置器的实现
template<class T, class Alloc = alloc>
class simple_alloc{
public://分配空间static T* allocate(size_t n){ return 0 == n ? (T*)Alloc:: allocate(n * sizeof(n)); }static T* allocate(void){ return (T*)Alloc :: allocate(sizeof(T)); }//释放空间statice T* deallocate(T* p, size_t n){if(0 != n){ Alloc::deallocate(p,n * sizeof(T));}}statice T* deallocate(T* p){ Alloc::deallocate(p,sizeof(n));}
}

vector的构造函数和析构函数的实现

        vector作为常用的容器类型,无论是在项目中还是在算法题中常常出现,所以我们对其构造函数并不陌生,以下便是的构造函数和析构函数的实现(PS:千万不要在容器中存放指针,指针如果是使用new进行分配的,并不能直接通过调用vector的析构函数对其元素进行释放,必须得遍历整个容器依次释放,否则会存在内存泄漏):

/*  针对代码中的uninitalized_fill_n()和destroy函数请参见《STL源码刨析:空间配置器(allocator)》中内存基本处理函数  *///vector容器的构造函数
vecotr() : start(0), finish(0), end_of_storage(0){}    //列表初始化
vector(int n, const T& value){ fill_initialize(n,value); }
vector(long n,const T& value){ fill_initialize(n,value); }
explicit vector(szie_type n){ fill_initialize(n,T()); }    //explicit用于禁止隐式转换
vector(size_type n, const T& value){ fill_initialize(n,value); }void fill_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 new_size, const T& x){iterator result = data_allocator::allocate(n);    //分配空间uninitalized_fill_n(result,n,x);    return result;
}//vector容器的析构函数
~vector(){destroy(start,finish);deallocate();
}void deallocate(){if(start){data_allocator::deallocate(start, end_of_storage - start);    //释放空间}
}

vector的数据结构以及实现源码

        vector容器的数据结构采用的是线性连续空间,以定义的迭代器start和finish分别指向以及使用的空间的头部和尾部(范围),并以迭代器end_of_storage指向分配的空间的尾部(PS:start和finish指向的是使用的空间,end_of_storage指向的分配的全部空间的尾部),使用这三个迭代器我们将可以使用首尾元素,容器大小,整体容量,空容器的判断,下标运算符[],头元素和尾元素的值的接口函数,整体实现如下:

//返回头元素指针
iterator begin(){ return start; }
//返回尾元素指针
iterator end() { return finish; }
//返回容器使用的空间大小
size_type size() const { return szie_type(end() - begin());}
//返回容器的全部空间大小
size_type capacity() const {return size_type(end_of_storage - begin());
}
//返回容器是否为空
bool empty() const { return begin() == end(); }
//返回指定下标的元素
reference operator[](size_type n){ return *(begin() + n); }
//返回容器的头元素的值
reference front(){ return *begin(); }
//返回容器的尾元素的值
reference back() {return *(end() - 1); }

        阅读本段,可能会产生疑问。全部空间是什么?已经使用的空间是什么?为什么back返回的值是尾指针-1后的值?针对这些疑问,获取阅读下图便会清晰:

图1.vector的数据结构

        参考上图可知,迭代器finish指向的并不是最后一个元素的地址,而是指向最后一个元素的地址的下一个地址。而且size()函数返回的是已经使用的空间大小,capacity()函数返回的是系统分配的整体空间的大小


vector的元素操作

        关于vactor容器的元素操作,我们常用的便是push_back(),pop-back(),clear()和insert(),在此基础上,还要有一个用于清除元素的操作erase(),其中clear()函数也是调用的erase()函数。针对以上的四个函数实现如下。

        1.push_back()函数实现源码

//push_back函数主要用于在容器的尾部插入元素
void push_back()(const T& x){if(finish != end_of_storage){    //存在多余空间construct(finish,x);         //参见《STL源码刨析:空间配置器(allocator)》,配置器初始化++finish;}elseinsert_aux(end(),x);
}template <class T, class Alloc>
void vector<T,Alloc>::insert_aux(iterator position, const T& x){if(finish != end_of_storage){    //存在多余空间construct(finish,*(finish - 1));++finish;    //调整尾指针T x_copy = x;copy_bcakward(position,finish - 2,finish - 1);*position = x_copy;}else{    //无多余空间const size_type old_size = size();    //当前使用空间const size_type len = ild_size != 0 ? 2 * old_size : 1;//当前空间为0则分配一个内存大小,当前空间不为0则分配2倍的当前空间的大小iterator new_start = data_allocator::allocate(len);iterator new_finish = new_statr;try{    //尝试将先前的元素拷贝到新申请的空间new_finish = uninitialized_copy(start,positon,new_start);//将原容器中的元素插入新容器construct(new_finish,x);++new_finish;new_finish = uninitialized_copy(position,finish,new_finish);//将新元素插入新容器    }catch(){//捕获异常destroy(new_start,new_finish);    //释放分配的空间data_allocator::deallocate(new_start,len);throw;}destory(begin(),end());    //释放原来容器的空间deallocate();//更新迭代器start = new_start;finish = new_finish;end_of_storage = new_start + len;    }
}

        2.pop_back()函数实现源码

//pop_back()函数主要用于将容器尾部的元素删除
void pop_back(){--finish;destroy(finish);    //使用配置器释放空间
}

        3.erase()函数实现源码

//erase()函数主要用于清除容器中的指定范围的元素或指定元素
iterator erase(iterator first, iterator last){    //清除容器中[first,last)范围中的元素iterator i = copy(last,finish,first);    //将last后的元素复制到firstdestroy(i,finish);    //释放空间finish = finish - (last - first);return finish;
}iterator erase(iterator position){    //清除指定位置的元素if(position + 1 != end())copy(position + 1, finish, position);--finish;destory(finish);return position;
}        

                针对erase()函数,可参考下图,直观了解其实现过程:

图2.erase()函数的调用过程

        4.clear()函数实现源码

//clear()函数主要用于将容器的元素删除
void clear() { erase(begin(),end()); } 

        5.insert()函数实现源码

//insert()函数主要用于将容器的元素插入
template<class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x){if(n != 0){    //插入的元素个数不为0if(size_type(end_of_storage - finish) >= n){    //空闲空间大于插入个数T x_copy = x;const size_type elems_after = finish - position;    //获取当前位于插入位置后的元素个数iterator old_finish = finish;if(elems_after > n){    //插入点后的元素个数大于插入的元素个数uninitialized_copy(finish - n, finish, finish);     //将插入点后的元素后移finish += n;copy_backward(position, old_finish - n, old_finish);fill(position,position + n,x_copy);    //从插入点填充新的元素}else{    //插入点后的元素个数小于等于插入的元素个数uninitialized_fill_n(finih, n - elems_after, x_copy);//将多出的插入元素填充到空闲内存中finish += n;uninitialized_copy(position, old_finish, finish);    //将插入点后的元素后移finish += elems_after;fill(position, old_finish, x_copy);    //将插入的元素填充至插入点}}else{const size_type old_size = size();    //当前容器大小const size_type len = old_size + max(old_size,n);//计算新的容器大小iterator new_start = data_allocator::allocate(len);iterator new_finish = mew_start;_STL_TRY{    //尝试将元素移动至新的空间new_finish = uninitialized_copy(start,position,new_stars);//将插入点前的当前容器的元素复制到新的空间new_finish = uninitialized_fill_n(new_finish,n,x);//将插入的元素填充至新的空间new_finish = uninitialized_copy(position,finish,new_finish);//将插入点后的当前容器的元素复制到新的空间}#ifdef _STL_USE_EXCEPTIONScatch(...){    //捕获异常destroy(new_stzrt,new_finish);data_allocator::deallocate(ner_start,len);throw;}#endif /*_STL+USE_EXCEPTIONS*/    //无异常destroy(start,finish);deallocate();start = new_start;finish = new_finish;end_of_atorage = new_start + len;}}
}

        以上便是本章的内容,针对insert()函数,个人觉得关键点在于插入的元素的个数,如果插入的元素个数大于当前插入点到尾指针的元素个数,则向把旧元素分配至新的空间内再插入新的元素。如果插入的元素个数小于等于当前插入点到尾指针的元素个数,则先将要插入的多出来的元素填充至尾指针后多余的空间内,再将旧元素复制到新的空间内,最后吧插入的元素再填充进去

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/16180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker Hub注册及上传自定义镜像

说明&#xff1a;本文介绍如何注册Docker Hub&#xff0c;及将自己自定义镜像上传到Docker Hub上&#xff1b; 注册Docker Hub 浏览器输入&#xff1a;http://hub.docker.com/&#xff0c;进入Docker Hub官网 注&#xff1a;如果无法访问&#xff0c;可在GitHub上下载一个Ste…

Git--本地仓库

文章目录 工作区和暂存区工作区&#xff08;Working Directory&#xff09;版本库&#xff08;Repository&#xff09; 初始化git仓库添加文件到版本库步骤 查看修改内容查看工作区和暂存区状态已add文件已修改/新增 的未add文件git跟踪修改原理 查看提交历史版本回退撤销修改撤…

信息学奥赛初赛天天练-12-数论-整除问题

更多资源请关注纽扣编程微信公众号 整除的性质 1 整除性 若 &#x1d44e; 和 &#x1d44f; 都为整数&#xff0c; &#x1d44e; 整除 &#x1d44f; 是指 &#x1d44f; 是 &#x1d44e; 的倍数&#xff0c;&#x1d44e; 是 &#x1d44f; 的约数&#xff08;或者叫 因…

基于Arduino IDE的ESP32开发环境搭建

文章目录 一. Arduino IDE安装二. Arduino IDE安装ESP开发包 一. Arduino IDE安装 Arduino官网下载IDE软件 解压下载好的安装包&#xff0c;以管理员身份运行Arduino IDE软件 IDE第一次启动会安装各种驱动&#xff0c;直接点击确定就行 二. Arduino IDE安装ESP开发包 将…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-22讲 RTC 时钟设置

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

头歌结构化分析方法-数据流图

第1关&#xff1a;数据流图-画出外部实体 第2关&#xff1a;数据流图-画出加工 第3关&#xff1a;数据流图-画出数据存储 第4关&#xff1a;数据流图-画出数据流 第5关&#xff1a;数据流图-机票预定系统

VXLAN小结

1.VXLAN:(组件虚拟网络的架构核心)虚拟扩展本地局域网&#xff0c;通过隧道的形式&#xff0c;将物理上有隔离的资源&#xff0c;在逻辑上连通起来&#xff0c;使其二层互通。 a.物理网络:指的是构成 VXLAN 连接的基础 IP 网络 b.逻辑网络:指的是通过 VXLAN 构建的虚拟网络 C.N…

DragonKnight CTF2024部分wp

DragonKnight CTF2024部分wp 最终成果 又是被带飞的一天&#xff0c;偷偷拷打一下队里的pwn手&#xff0c;只出了一题 这里是我们队的wp web web就出了两个ez题&#xff0c;确实很easy&#xff0c;只是需要一点脑洞(感觉)&#xff0c; ezsgin dirsearch扫一下就发现有ind…

ROS参数服务器

一、介绍 参数服务器是用于存储和检索参数的分布式多机器人配置系统&#xff0c;它允许节点动态地获取参数值。 在ROS中&#xff0c;参数服务器是一种用于存储和检索参数的分布式多机器人配置系统。它允许节点动态地获取参数值&#xff0c;并提供了一种方便的方式来管理和共享配…

基于Python Selenium web测试工具 - 基本用法详解

这篇文章主要介绍了Selenium&#xff08;Python web测试工具&#xff09;基本用法,结合实例形式分析了Selenium的基本安装、简单使用方法及相关操作技巧,需要的朋友可以参考下 本文实例讲述了Selenium基本用法。分享给大家供大家参考&#xff0c;具体如下&#xff1a; Seleni…

如何查看热门GPT应用?

1、登陆chatgpt 2、访问 https://chatgpt.com/gpts 3、在该界面&#xff0c;可以搜索并使用image generator, Write For Me&#xff0c;Language Teature等热门应用。

嵌入式C语言中结构体使用详解

各位开发者大家好,今天给大家分享一下,嵌入式C语言中结构体的使用方法。 第一个:内存对齐 内存对齐是指一个数据类型在内存中存放时,对其地址的要求。简单来说内存对齐就是使得其内存地址是该类型大小的整数倍,例如 double 类型的变量,其内存地址需要是8的倍数(double大…

深入理解 Mysql 分层架构:从存储引擎到查询优化器的内部机制解析

一、基础架构 1.连接器 1.会先连接到这个数据库上&#xff0c;这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接 2.用户密码连接成功之后&#xff0c;会从权限表中拿出你的权限&#xff0c;后续操作权限都依赖于此时拿出的权限,这就意味着当链…

SVM兵王问题

1.流程 前面六个就是棋子的位置&#xff0c;draw就是逼和&#xff0c;后面的数字six就代表&#xff0c;白棋最少用六步就能将死对方。然后呢&#xff0c;可以看一下最后一个有几种情况&#xff1a; 2.交叉测试 leave one out&#xff1a; 留一个样本作测试集&#xff0c;其余…

Django 里的静态资源调用

静态资源&#xff1a;图片&#xff0c;CSS, JavaScript 一共有两种方法 第一种方法 在项目的文件夹里创建名为 static 文件夹 在该文件夹里&#xff0c;添加静态资源 在 settings.py 里添加路径 import os# Static files (CSS, JavaScript, Images) # https://docs.djan…

Oracle Graph 入门 - RDF 知识图谱

Oracle Graph 入门 - RDF 知识图谱 0. 引言1. 查看 RDF Semantic Graph 安装情况2. 创建一个语义网络4. 创建一个模型5. 加载 RDF 文件6. 配置 W3C 标准的 SPARQL 端点 0. 引言 Oracle Graph 的中文资料太少了&#xff0c;只能自己参考英文资料整理一篇吧。 Oracle 数据库包括…

搭建Harbor镜像仓库

前言 1、系统版本&#xff1a;CentOS9 2、harbor版本&#xff1a;v2.9.4 3、提前安装好docker和docker-compose&#xff0c;参考地址。我这里安装的版本是docker&#xff1a;26.1.3 docker-compose&#xff1a;v2.27.1 安装步骤 下载安装包 1、下载地址&#xff1a;ha…

fastadmin 树状菜单展开,合并;简要文件管理系统界面设计与实现

一&#xff0c;菜单合并效果图 源文件参考&#xff1a;fastadmin 子级菜单展开合并、分类父级归纳 - FastAdmin问答社区 php服务端&#xff1a; public function _initialize() {parent::_initialize();$this->model new \app\admin\model\auth\Filetype;$this->admin…

企业如何做好 SQL 质量管理?

研发人员写 SQL 操作数据库想必一定是一类基础且常见的工作内容。如何避免 “问题” SQL 流转到生产环境&#xff0c;保证数据质量&#xff1f;这值得被研发/DBA/运维所重视。 什么是 SQL 问题&#xff1f; 对于研发人员来说&#xff0c;在日常工作中&#xff0c;大部分都需要…

代码随想录算法训练营第三十六天 | 1005.K次取反后最大化的数组和、134.加油站、135.分发糖果

目录 1005.K次取反后最大化的数组和 思路 代码 代码 134.加油站 思路 代码 135.分发糖果 思路 代码 1005.K次取反后最大化的数组和 本题简单一些&#xff0c;估计大家不用想着贪心 &#xff0c;用自己直觉也会有思路。 代码随想录 思路 直觉&#xff0c;直接写&…