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

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()函数,个人觉得关键点在于插入的元素的个数,如果插入的元素个数大于当前插入点到尾指针的元素个数,则向把旧元素分配至新的空间内再插入新的元素。如果插入的元素个数小于等于当前插入点到尾指针的元素个数,则先将要插入的多出来的元素填充至尾指针后多余的空间内,再将旧元素复制到新的空间内,最后吧插入的元素再填充进去

相关文章:

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

目录 1.序列式容器和关联式容器 2.vector的定义和结构 3.vector的构造函数和析构函数的实现 4.vector的数据结构以及实现源码 5.vector的元素操作 前言 本系列将重点对STL中的容器进行讲解&#xff0c;而在容器的分类中&#xff0c;我们将容器分为序列式容器和关联式容器。本章…...

Flutter 中的 AbsorbPointer 小部件:全面指南

Flutter 中的 AbsorbPointer 小部件&#xff1a;全面指南 在Flutter中&#xff0c;AbsorbPointer是一个特殊的小部件&#xff0c;用于吸收&#xff08;或“吞噬”&#xff09;所有传递到其子组件的指针事件&#xff08;如触摸或鼠标点击&#xff09;。这在某些情况下非常有用&…...

Web开发学习总结

学习路线 Web 全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站 初识Web前端 Web标准也称为网页标准&#xff0c;由一系列的标准组成&#xff0c;大部分由W3C(World Wide Web Consortium&#xff0c;万维网联盟)负责制定。三个组…...

springboot相关知识集锦----1

一、springboot是什么&#xff1f; springboot是一个用于构建基于spring框架的独立应用程序的框架。它采用自动配置的原则&#xff0c;以减少开发人员在搭建应用方面的时间和精力。同时提升系统的可维护性和可扩展性。 二、springboot的优点 约定优于配置 版本锁定&#xf…...

App推广新境界:Xinstall助你轻松突破运营痛点,实现用户快速增长!

在移动互联网时代&#xff0c;App已经成为企业营销不可或缺的一部分。然而&#xff0c;如何有效地推广App&#xff0c;吸引并留住用户&#xff0c;成为了众多企业面临的难题。今天&#xff0c;我们将为您揭秘一款神奇的App推广工具——Xinstall&#xff0c;它将助您轻松突破运营…...

YOLOv10 论文学习

论文链接&#xff1a;https://arxiv.org/pdf/2405.14458 代码链接&#xff1a;https://github.com/THU-MIG/yolov10 解决了什么问题&#xff1f; 实时目标检测是计算机视觉领域的研究焦点&#xff0c;目的是以较低的延迟准确地预测图像中各物体的类别和坐标。它广泛应用于自动…...

[Spring Boot]baomidou 多数据源

文章目录 简述本文涉及代码已开源 项目配置pom引入baomidouyml增加dynamic配置启动类增加注解配置结束 业务调用注解DS()TransactionalDSTransactional自定义数据源注解MySQL2 测试调用查询接口单数据源事务测试多数据源事务如果依然使用Transactional会怎样&#xff1f;测试正…...

Drone+Gitee自动执行构建、测试和发布工作流

拉取Drone:(至于版本&#xff0c;你可以下载最新的) sudo docker pull drone/drone:2 拉取runner&#xff1a; sudo docker pull drone/drone-runner-docker 在Gitee中添加第三方应用&#xff1a; 进入个人主页&#xff0c;点击设置&#xff1a; 往下翻&#xff0c;找到数…...

Unity3D MMORPG 主城角色动画控制与消息触发详解

Unity3D是一款强大的游戏开发引擎&#xff0c;它提供了丰富的功能和工具&#xff0c;使开发者能够轻松创建出高质量的游戏。其中&#xff0c;角色动画控制和消息触发是游戏开发中非常重要的一部分&#xff0c;它们可以让游戏角色表现出更加生动和多样的动作&#xff0c;同时也能…...

【Text2SQL 经典模型】HydraNet

论文&#xff1a;Hybrid Ranking Network for Text-to-SQL ⭐⭐⭐ arXiv:2008.04759 HydraNet 也是利用 PLM 来生成 question 和 table schema 的 representation 并用于生成 SQL&#xff0c;并在 SQLova 和 X-SQL 做了改进&#xff0c;提升了在 WikiSQL 上的表现。 一、Intro…...

Mysql-根据字段名查询字段在哪些表里

SELECT * FROM information_schema.COLUMNS WHERE COLUMN_NAMElabel_name;...

牛逼!50.3K Star!一个自动将屏幕截图转换为代码的开源工具

1、背景 在当今快节奏的软件开发环境中&#xff0c;设计师与开发者之间的协同工作显得尤为重要。然而&#xff0c;理解并准确实现设计稿的意图常常需要耗费大量的时间和沟通成本。为此&#xff0c;开源社区中出现了一个引人注目的项目——screenshot-to-code&#xff0c;它利用…...

八种单例模式

文章目录 1.单例模式基本介绍1.介绍2.单例模式八种方式 2.饿汉式&#xff08;静态常量&#xff0c;推荐&#xff09;1.基本步骤1.构造器私有化&#xff08;防止new&#xff09;2.类的内部创建对象3.向外暴露一个静态的公共方法 2.代码实现3.优缺点分析 3.饿汉式&#xff08;静态…...

禅道密码正确但是登录异常处理

禅道密码正确&#xff0c;但是登录提示密码错误的异常处理 排查内容 # 1、服务器异常&#xff0c;存储空间、数据库异常 # 2、服务异常&#xff0c;文件丢失等异常问题定位 # 1、df -h 排查服务器存储空间 # 2、根据my.php排查数据库连接是否正常 # 3、修改my.pho,debugtrue…...

Go微服务: Nacos的搭建和基础API的使用

Nacos 概述 文档&#xff1a;https://nacos.io/docs/latest/what-is-nacos/搭建&#xff1a;https://nacos.io/docs/latest/quickstart/quick-start-docker/有很多种搭建方式&#xff0c;我们这里使用 docker 来搭建 Nacos 的搭建 这里&#xff0c;我们选择单机模式&#xf…...

基于Hadoop的城市公共交通大数据时空分析

基于Hadoop的城市公共交通大数据时空分析 “Spatio-temporal Analysis of Urban Public Transportation Big Data based on Hadoop” 完整下载链接:基于Hadoop的城市公共交通大数据时空分析 文章目录 基于Hadoop的城市公共交通大数据时空分析摘要第一章 引言1.1 研究背景1.2 …...

DNS服务的部署与配置(2)

1、dns的安装及开启 dnf install bind.x86_64 -y #安装 #Berkeley Internet Name Domain (BIND) systemctl enable --now named #启用dns服务&#xff0c;服务名称叫named firewall-cmd --permanent --add-servicedns #火墙设置 firewall-cmd --reload …...

MySql--SQL语言

目录 SQl---DDL 结构定义 创建、删除 数据库 代码 运行 设计表 数据类型 整数 浮点数 主键 约束 主键自增长 默认值 字段注释 创建、删除 表 代码 运行 代码 代码 运行 SQL---DML 数据操纵 插入数据 代码 运行 代码 运行 代码 运行 代码 …...

【网络安全】2030年十大新兴网络安全威胁

欧盟网络安全局&#xff08;ENISA&#xff09;已发布了一份全面的清单&#xff0c;列出了预计到2030年将影响数字领域的十大新兴网络安全威胁。 该预测是为期八个月的广泛研究的成果&#xff0c;融合了ENISA前瞻专家小组、CSIRTs网络以及欧盟CyCLONe专家的见解。 这项研究突显…...

python数据分析-CO2排放分析

导入所需要的package import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import datetime %matplotlib inline plt.rcParams[font.sans-serif] [KaiTi] #中文 plt.rcParams[axes.unicode_minus] False #负号 数据清洗…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...