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;
    }
    else
        insert_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后的元素复制到first
    destroy(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){    //插入的元素个数不为0
        if(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_EXCEPTIONS
                catch(...){    //捕获异常
                    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()函数,个人觉得关键点在于插入的元素的个数,如果插入的元素个数大于当前插入点到尾指针的元素个数,则向把旧元素分配至新的空间内再插入新的元素。如果插入的元素个数小于等于当前插入点到尾指针的元素个数,则先将要插入的多出来的元素填充至尾指针后多余的空间内,再将旧元素复制到新的空间内,最后吧插入的元素再填充进去

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

xcode按下delete键不能删除不能使用,解决办法

有可能是按键冲突导致的问题&#xff0c;就是你不小心把delete键绑定了不同的快捷键&#xff0c;所以需要恢复所有的偏好设置和快捷键才可以&#xff0c;我这里就是这样的提示内容&#xff0c;在xcode中按delete键完全无效&#xff1a; 而且还会报红色提示&#xff1a;意思是不…

民国漫画杂志《时代漫画》第22期.PDF

时代漫画22.PDF: https://url03.ctfile.com/f/1779803-1248634856-2c7010?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

JUC框架(CAS、ATOMIC、AQS)

文章目录 JUC之CASJUC之ATOMICJUC之AQSAQS简介AQS原理 更多相关内容可查看 JUC之CAS **CAS&#xff08;compareAndSwap&#xff09;**也叫比较交换&#xff0c;是一种无锁原子算法&#xff0c;其作用是让**CPU**将内存值更新为新值&#xff0c;但是有个条件&#xff0c;内存值…

vue列表数据添加和删除实例

运行效果如下&#xff1a; 详细代码&#xff1a; 自行添加vue.min.js文件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0&…

程序员的那些经典段子

哈喽&#xff0c;大家好&#xff0c;我是明智&#xff5e; 本周咱们已经解决了在面试中经常碰到的OOM问题&#xff1a; 《美团一面&#xff0c;发生OOM了&#xff0c;程序还能继续运行吗&#xff1f;》 《美团一面&#xff1a;碰到过OOM吗&#xff1f;你是怎么处理的&#xff1…

【Linux学习】进程

下面是有关进程的相关介绍&#xff0c;希望对你有所帮助&#xff01; 小海编程心语录-CSDN博客 目录 1. 进程的概念 1.1 进程与程序 1.2 进程号 2. 进程的状态 2.1 fork创建子进程 2.2 父子进程间的文件共享 3. 进程的诞生与终止 3.1 进程的诞生 3.2 进程的终止 1. 进…

基于深度学习的入侵检测系统综述文献概述

好长时间不发博客了&#xff0c;不是因为我摆烂了&#xff0c;是我换研究方向了&#xff0c;以后我就要搞科研了。使用博客记录我的科研故事&#xff0c;邀诸君共同见证我的科研之路。 1、研究方向的背景是什么&#xff1f; &#xff08;1&#xff09;互联网发展迅速&#xff…

基于springboot+vue的4S店车辆管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

动态规划之背包问题中如何确定遍历顺序的问题-组合or排列?

关于如何确定遍历顺序 322. 零钱兑换中&#xff0c;本题求钱币最小个数&#xff0c;那么钱币有顺序和没有顺序都可以&#xff0c;都不影响钱币的最小个数。 所以本题并不强调集合是组合还是排列。 如果求组合数就是外层for循环遍历物品&#xff0c;内层for遍历背包。 如果求…

链式二叉树的前,中,后序遍历 AND 结点个数及高度等 文末附带全部代码

目录 前言1. 前序遍历2. 中序遍历3. 后续遍历4. 二叉树结点的个数5. 二叉树叶子结点个数6. 二叉树的高度7. 二叉树第K层结点的个数8. 二叉树查找值为x的结点全部代码总结 正文开始 前言 本文旨在介绍二叉树的链式存储中一些函数的实现 博客主页: 酷酷学!!! 更多文章, 期待关…

dubbo复习:(9)配置中心的大坑,并不能像spring cloud那样直接从配置中心读取自定义的配置

配置中心只是为 Dubbo 配置提供管理使用的&#xff08;比如配置服务超时时间等)。不要尝试通过Value类似的方式从dubbo 配置中心(比如nacos、zookeeper、Apollo)来获取数据 https://github.com/apache/dubbo/issues/11200可以在application.yml中主要写注册中心的配置&#xf…

【LeetCode刷题】滑动窗口思想解决:最大连续1的个数 III、将x减到0的最小操作数

【LeetCode刷题】Day 8 题目1&#xff1a;1004.最大连续1的个数 III思路分析&#xff1a;思路1&#xff1a;暴力枚举zero计数器思路2&#xff1a;滑动窗口zero计数器 题目2&#xff1a;1658. 将x减到0的最小操作数思路分析&#xff1a;思路1&#xff1a;暴力枚举思路2&#xff…

前端中 dayjs 时间的插件使用(在vue 项目中)

Day.js中文网 这是dayjs的中文文档 里面包括了使用方法 下面我来详细介绍一下这个插件的使用 Day.js 可以运行在浏览器和 Node.js 中。 一般咱直接是 npm 安装 npm install dayjs 目前应该使用的是Es6 的语法 import dayjs from dayjs 当前时间 直接调用 dayjs() 将返回…

CobaltStrike渗透框架进阶之扩展脚本和MSF联动

CobaltStrike扩展脚本 扩展是Cobaltstrike一个极为重要的模块&#xff0c;它有效地丰盈了cobaltstrike的功能 选择菜单栏的CobaltStrike–>脚本管理器&#xff0c;点击load&#xff0c;然后选择cna扩展文件即可&#xff0c;旁边的unload为去除该扩展&#xff0c;&#xff…

Octo 精武门? :开源的通用机器人模型

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提供了大模型领域最新技…

01主动安全系统

“安全”一直是车主对车辆考核的重要指标。车辆安全可以分为从主动安全和被动安全两个方面进行分类。今天就来说说汽车主动安全系统的那些事儿。 01.什么是主动安全系统&#xff1f; 主动安全是指尽量自如地操纵控制汽车的安全系统措施。无论是直线上的制动与加速还是左右打方…

X2Doris使用指南:界面化数据迁移工具 - 轻松实现整库迁移至Doris

什么是X2Doris X2Doris 是 SelectDB 团队开发的&#xff0c;专门用于将各种离线数据迁移到 Apache Doris 中的核心工具&#xff0c;该工具集 自动建 Doris 表 和 数据迁移 为一体&#xff0c;目前支持了 Apache Doris/Hive/Kudu/StarRocks 数据库往 Doris 或 SelectDB Cloud 迁…

AI预测福彩3D采取888=3策略+和值012路一缩定乾坤测试5月26日预测第2弹

昨天的8883大底成功命中&#xff0c;但是由于昨天杀了对子&#xff0c;结果昨天开了对子&#xff0c;导致最终与中奖号码擦肩而过。今天继续基于8883的大底&#xff0c;使用尽可能少的条件进行缩号&#xff0c;同时&#xff0c;今天将准备两套方案&#xff0c;一套是我自己的条…

Dockerfile文件详细介绍

前言 Dockerfile是一个文本文件&#xff0c;包含了用于构建Docker镜像的所有命令和说明。它定义了容器的运行环境、依赖以及启动方式&#xff0c;是创建Docker镜像的核心部分。 由于制作镜像的过程中&#xff0c;需要逐层处理和打包&#xff0c;比较复杂&#xff0c;所以Docke…

Day 3:1738. 找出第 K 大的异或坐标值

Leetcode 1738. 找出第 K 大的异或坐标值 给你一个二维矩阵 matrix 和一个整数 k &#xff0c;矩阵大小为 m x n 由非负整数组成。 矩阵中坐标 (a, b) 的 值 可由对所有满足 0 < i < a < m 且 0 < j < b < n 的元素 matrix[i][j]&#xff08;下标从 0 开始计…