STL中vector怎么扩容

news2024/11/19 7:33:22

STL vector扩容

vector容器

vector被称为向量容器,该容器擅长在尾部插入或删除元素,时间复杂度为O(1);而对于在vector容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)。使用vector容器,需要包含<vector>,注意这里无扩展名。

STL中规定容器的区间遵循前闭后开原则,即容器中的一对迭代器start和finish标识的前闭后开区间,从start开始,直到finish-1.迭代器finish所指的是“最后一个元素的下一位置”。这样定义的好处主要有两点:

  1. 为“遍历元素时,循环的结束时机”提供一个简单的判断依据。只要尚未到达end(),循环就可以继续下去;

  2. 不必对空区间采取特殊处理手段。空区间的begin()就等于end();

class vector {
    ……
public:
    /**
     * begin()函数源码
     * 用于返回start迭代器
     **/
    iterator begin() {
        return start;
    }

    /**
     * end()函数源码
     * 用于返回finish迭代器
     **/
    iterator end() {
        // STL容器的前闭后开原则,end迭代器并不指向最后一个元素 而是最后一个元素的后面
        return finish;
    }

    /**
     * size()函数源码
     * 用于返回start-end的值——已保存的元素个数
     **/
    size_type size() const {  // 返回start-end的值 即已保存的元素个数
        return size_type(end() - begin());
    }

    /**
     * capacity()函数源码
     * 返回end_of_storage - start的值——vector能够保存元素上限
     **/
    size_type capacity() const {  // 返回end_of_storage - start的值, 即vector能够保存元素上限
        return size_type(end_of_storage - begin());
    }

    /**
     * empty()函数源码
     * 通过判断start迭代器与finish迭代器是否相等,返回vector是否为空
     **/
    bool empty() const { 
        return begin() == end();
    }

    /**
     * []操作符重载
     * 实现根据下标取元素
     **/
    reference operator[](size_type n) {
        return *(begin() + n);
    }

    /**
     * front函数源码
     * 根据start迭代器,返回第一个元素的值
     **/
    reference front() {  
        return *begin();
    }

    /**
     * back函数源码
     * 根据finish迭代器,返回最后一个元素的值
     * 由于finish迭代器指向的是最后一个元素的后一个位置(STL容器的前闭后开原则),因此这里需要用finish-1获取最后一个元素的位置
     **/
    reference back() {  // 返回最后一个要素
        return *(end() - 1);
    }
};
扩容策略

vector属于序列式容器(sequence_container):其中的元素都可序,但未必有序。

如图所示,vector的内存模型与数组较为相似,但vector的内存模型是动态增长的线性空间,动态增长的实质是:需求空间超过当前可用空间后,不是在原空间之后接续新空间。这是因为线性空间后不一定有足够大小的空间,因此重新申请一块更大的空间来作为载体,然后复制已有数据到新申请的内存空间。

  • start:表示目前使用空间的头。
  • finish:目前使用空间的尾。
  • end_of_storage:目前可用空间的尾。

注意,目前使用空间和目前可用空间是有区别的

img

具体操作为:首先配置一块新空间,然后将元素从原空间搬到新的空间上,再把原空间释放。(涉及到了新空间的配置和旧空间的释放)

新空间的大小为一般为原空间大小的二倍。注意:二倍增长并不是必然的,不同的编译环境可以有不同的实现,但若增长倍数小于2则可能会导致扩容频繁;增长倍数较大则可能导致申请了较大的空间而未使用,从而造成浪费。 此外,vector为了降低空间扩容的速度,在配置空间时留有一部分空间以便之后扩容,这就是size()和capacity()的区别。size()返回使用了多少空间,capacity()返回了配置了多少空间。当两者相等时说明vector容器已经满了,再插入元素需要扩容。

vector如何做到随机访问?‘[]’操作符

vector可以像数组一样,支持使用’[]'操作符根据下标获取元素。简单来讲,vector中元素大小固定,在知道start的基础上,我们只需要在其基础上进行地址偏移就能找到所需元素。

reference operator[](size_type n){
    return *(begin() + n);
}
迭代器失效

需要注意的是,对vector的操作如果引起的空间重新分配,那么原vector的所有迭代器就都失效。因此,如果我们将vector的迭代器保存到变量中时,可能会因为空间重新分配导致该变量保存的迭代器失效,解决迭代器失效可以参考:c++迭代器失效的几种情况总结

#include <vector>
#include <iostream>
using namespace std;

int main()
{
    vector<int> vec;
    vec.push_back(0);
    auto iter = vec.begin();  // 使用变量保存迭代器
    cout<< *iter << endl;
    for(int i = 0; i < 10; i++)  // 可能会引起vector空间重新分配的代码段
    {
        vec.push_back(i);
    }
    cout<< *iter << endl;  // 原迭代器失效,此处异常中断
}
vector常用元素的操作方法
push_back(const T& x)

将新元素插入vector的尾端,在插入时需要关注两种情况:即vector当前是否还有空间,如果有则直接在备用空间上构造元素,调整迭代器finish;若没有,则需要扩充空间。

push_back源码:

void push_back(const T& x) {
    if(finish != end_of_storage)
    {
        constrcut(finish,x);
        ++finish;
    }
    else
    {
        insert_aux(end(),x);
    }
}

insert_aux源码:

template<class T>
void insert_aux(iterator position, const T& x) {
    if(finish != end_of_storage) {										
        // 有备用空间	
    }
    else {
        // 获取当前vector的元素个数
        const size_type old_size = size();  
        // 计算扩展后的vector空间
        // 若扩充前vector是空的,则配置申请一个元素的空间
        // 若扩充前vector非空,则按照二倍扩充原则进行扩充
        const size_type len = old_size != 0 ? 2 * old_size : 1;  

        // 分配器allocator配置新空间
        // new_start和new_finish迭代器指向新分配的空间起点
        iterator new_start = data_allocator::allocate(len);  
        iterator new_finish = new_start;
        try {
            // 拷贝原vector中start到position区间的元素到new_start迭代器所指处 
            // push_back函数中调用insert_aux(end(),x),因此position就是finish迭代器,这里就将原vector的所有元素拷贝过来
            // 执行后 new_finish迭代器就指向了原vector的所有元素拷贝到新位置的末尾
            new_finish = uninitialized_copy(start,position,new_start);	
            // 在new_finish处构造新元素x
            construct(new_finish,x);	
            // new_finish向后移一个位置	
            ++new_finish;
            // 再将原vector的position到finish区间的元素拷贝到new_finish迭代器所指处		
            // push_back函数中调用insert_aux(end(),x),因此position就是finish迭代器,这里元素区间是空										    
            new_finish = uninitialized_copy(position,finish,new_finish); 
        }
        catch(...) {
            // 异常时回滚
            destroy(new_start,new_finish);
            data_allocator::dellocate(new_start,len);
            throw;
        }
        // 析构并释放原vector
        destroy(begin(),end());
        deallocate();
        // 为start和finish迭代器赋新值
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
    }
}
pop_back()

删除尾端元素时,调整迭代器并释放即可,不需要空间分配

void pop_back() {
    --finish;		   //调整末端位置迭代器
    destroy(finish);	//释放尾端元素的资源
}
iterator erase(iterator first,iterator last)

用于删除vector容器中的一段区间的元素,返回指向删除的区间起点迭代器

// 删除某段元素 参数为需要删除段的迭代器起点和终点
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)

用于删除vector容器中的一个元素,返回指向被删除元素位置迭代器

// 删除某个元素 参数为需要删除的元素迭代器
iterator erase(iterator position) {	
    if(position + 1 != end()) {
        copy(position + 1,finish,position); // 将position后的元素整体向前移动
    }
    --finish;  // 迭代器前移
    destroy(finish);  
    return position;
}
void insert(iterator position, size_type n, const T& x)

在指定位置position前插入n个值为x的元素,返回指向这个元素的迭代器

由于vector中的元素顺序存储,所以随机插入元素的操作相对比较麻烦:

void insert(iterator position, size_type n, const T& x)
{
    if(n != 0)
    {
        if(size_type(end_of_storage - finish) >= n) {  // vector当前备用空间大于n时
            T x_copy = x;
            const size_type elems_after = finish - position;  // 计算需要移动元素的个数
            iterator old_finish = finish;
            if(elems_after > n) {  // 如果需要移动的元素个数 大于插入的元素数量
                // 将finish-n 到 finish区间内的数据拷贝到finish后 
                // 注意:这里只拷贝了倒数n个元素 而不是elems_after个元素
                uninitialized_copy(finish - n,finish,finish);  
                finish += n;  // 更新finish迭代器,此时finish的位置为插入n个元素后的为位置
                // 再将position 到 old_finish区间的元素移动到old_finish后
                copy_backward(postion,old_finish - n,old_finsh);
                // 最后 在position后填充n个x元素
                fill(position,position + n,x_copy);
                // 可参考下图情况1
            }
            else {  // 如果需要移动的元素个数 小于插入的元素数量
                // 在finish后 填充n - elems_after个元素,
                uninitialized_fill_n(finish,n - elems_after, x_copy); 
                finish += n - elems_after;  // 调整finish 此时finish的位置不是最终的位置
                //  将position 到 old_finish区间内的元素拷贝到finish后
                uninitialized_copy(position,old_finish,finish);
                finish += elems_after;  // 继续调整finish 
                fill(position,old_finish,x_copy);  // 将position到old_finish区间内填充为 x
                // 可参考下图情况2
            }
        }
        else {  // vector当前备用空间小于n时
            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 = new_start;
            // 分三段复制到新vector
            {
                //插入点之前的元素复制到新vector
                new_finish = uninitialized_copy(start,position,new_start);	
                //新增元素填入	
                new_finish = uninitialized_fill_n(new_finish,n,x);		
                // 插入点之后的元素复制到新vector		
                new_finish = uninitialized_copy(position,finish,new_finish);	
            }
            // 析构并释放原vector
            destroy(begin(),end());
            deallocate();
            // 调整迭代器
            start = new_start;
            finish = new_finish;
            end_of_storage = new_start + len;
            // 可参考下图情况3
        }
    }
}

情况1:

情况1

情况2:

情况2

情况3

情况3

倍数扩容和等长扩容

向vector中插入元素时,如果元素有效个数size与容量capacity相等时vector内部就会触发扩容机制

扩容机制:开辟新空间,拷贝元素,释放旧空间【频繁扩容会导致效率变低,所以在创建vector数组的时候要预估元素个数】

等长扩容
  • vector插入100个元素等长扩容(k=10)需要扩容十次,而且每次都需要将元素从旧空间搬到新空间
倍数扩容

原理:申请新空间、拷贝元素、释放空间

理想情况:在某一次扩容之后可以复用之前的空间

例如二倍扩容时,在第四次扩容(前面已经释放1+2的空间, 第三次空间释放后就有1+2+4空间,因为二倍扩容每次新内存一定大于前面的总和,所以不能实现复用)即如果倍数超过2(包括2),无法使用到已经释放的内存,而且可能空间浪费高(例如扩容申请64,但是只使用了33)所以使用1.5倍扩容(1。2。3。4。1+2+3)几次扩容之后就可以进行空间的复用

  • windows(vs)1.5倍扩容
  • Linux(gcc)2倍扩容

演示二倍扩容和1.5倍扩容

参考

vector扩容问题源代码剖析_kongkongkkk的博客-CSDN博客

《STL 源码剖析》学习笔记之容器(一)vector_51CTO博客_《STL源码剖析》

3-1 STL容器剖析(vector & list) _牛客网 (nowcoder.com)

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

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

相关文章

十九、Kubernetes中Pod调度第一篇

1、概述 在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达某些…

Gradle学习笔记之插件

文章目录插件的作用插件的分类和使用脚本插件对象插件第三方插件用户自定义插件buildSrc项目编写步骤其他项目/模块引用步骤java插件常用属性插件的作用 插件可以帮助我们促进代码的重用、项目更高程度的模块化、自动化和便捷化&#xff0c;减少功能类似代码的编写&#xff0c…

【数据结构与算法】 | 用队列实现栈

用队列实现栈 链接&#xff1a; https://leetcode.cn/problems/implement-stack-using-queues/ 1. 要求 2. 思路 栈的性质是先进后出&#xff0c;队列的性质是先进先出。 题目要求是用两个队列来完成栈的实现&#xff0c;那么大思路就是通过倒腾数据来完成。 3. 代码实现…

JAVA系列 多线程 Thread类 耦合 线程调度 如何创建线程 弱耦合 完全解耦 思维结构整理

这估计是我JAVA系列 基础知识点梳理的最后一篇文章了吧 之后一段时间 我打算去看看之前承诺更新的JAVA的一些项目了 然后我要好好玩一段时间游戏 然后跟某些好朋友见面 所以后面会忙一点 然后就是很多同学 六级还没有考 估计很多人是开学再考把 这个假期就不要玩的太厉害了 没事…

人员超员识别系统 yolov5

人员超员识别系统通过yolov5深度学习网络模型对现场画面进行实时监测&#xff0c;监测到画面中区域人数超过规定人数时&#xff0c;立即抓拍存档预警。YOLO系列算法是一类典型的one-stage目标检测算法&#xff0c;其利用anchor box将分类与目标定位的回归问题结合起来&#xff…

一篇彻底解决:Fatal error compiling: 无效的目标发行版: 11 -> [Help 1]

先在这声明,如果我的方法没有解决你的问题,那你直接私信我,我第一时间帮你解决,送佛送到西!!! 这个问题&#xff0c;主要原因就是JDK的版本问题&#xff0c;“无效的目标发行版&#xff1a;11”的意思是你在某个位置配置了JDK11&#xff0c;但是在其他位置配置的并不是11&…

靶机测试Gears of War笔记

靶机介绍Its a CTF machine that deals with the history of gears of war, where we must try to escape from prison and obtain root privileges. it has some rabbit holes, so you have to try to connect the tracks to get access.This works better with VirtualBox ra…

[ 数据结构 ] 二叉树详解--------前序、中序、后序、存储、线索化

0 前言 why?为什么需要树结构? 数组虽然查找快(通过下标访问),但无法动态扩容(拷贝到新的数组),而链表相反,树结构刚好结合两者优点 浅谈树? 树的存储和读取效率都很高,比如二叉排序树,理解树的抽象模型有助于理解递归的原理,树的模型接近于单向链表,父节点指向左右子树,而…

【因果发现】 针对时序数据的因果图学习

文章目录 一、任务二、数据集说明三、专业名词CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、任务 This track focuses on solving a causal structure learning problem in AIOps. AIOps 相关:主要目标是从事件序列数据中挖掘因果图关系,并辅助定位根因。 主要需要解…

php学习笔记-php数组的创建和使用,数组常用函数-day03

php数组的创建和使用&#xff0c;数组常用函数数组的概念一维数组的创建和使用1.直接将变量声明为数组元素2.通过array()函数来创建一维数组3.数组的元素值的访问4.数组元素的赋值方式5.数组的注意事项二维数组的创建和使用1.二维数组的创建2.二维数组的数组元素值访问3.二维元…

ArcGIS基础实验操作100例--实验67设置标注样式

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验67 设置标注样式 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

Java+Swing可视化图像处理软件

JavaSwing可视化图像处理软件一、系统介绍二、功能展示1.图片裁剪2.图片缩放3.图片旋转4.图像灰度处理5.图像变形6.图像扭曲7.图像移动三、项目相关3.1 乱码问题3.2 如何将GBK编码系统修改为UTF-8编码的系统&#xff1f;四、其它1.其他系统实现2.获取源码一、系统介绍 该系统实…

Allegro174版本新功能介绍之和172版本兼容设置

Allegro174版本新功能介绍之和172版本兼容设置 Allegro升级到了174版本的时候,如果按照常规操作用174版本保存PCB之后,用172版本是无法打开的。 但是174版本开放了一个和172版本兼容的窗口,即便是174版本保存了PCB,172同样还是能够打开 具体操作如下 选择Setup选择User p…

[Linux]git命令行

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文…

前端入门笔记 05 —— js基础

基于ES6 生成html文件&#xff0c;script标签引入js文件 简单语句直接用console面板 <!DOCTYPE html> <html><head><title>js核心语法</title><meta charset"UTF-8" /></head><body><script src "./1.5j…

多线程(4)

文章目录1.单例模式2. 阻塞队列3. 定时器4.线程池前言 :   前面的一些文章&#xff0c;我们已经将有关多线的基础知识了解了&#xff0c; 下面我们来写一些关于多线程的代码案例&#xff0c;来强化对多线程的理解&#xff0c;另外通过这些案例补充一下其他方面的知识。 1.单例…

印度如何在云计算中抓住千载难逢的机会

云对印度的影响大流行和后大流行时期的虚拟和混合世界越来越依赖于云&#xff0c;并随之提高灵活性、敏捷性和创新性。在这场快速的数字化转型中&#xff0c;印度企业也纷纷加入云解决方案以促进业务增长。NASSCOM-EY的调查显示&#xff0c;78%的印度IT公司、53%的医疗保健和BF…

【Javascript】数字和字符串常用方法

数字常用方法&#xff1a; toFixed&#xff1a;保留小数位数&#xff0c;不够自动补0. 注意&#xff1a;返回值是字符串&#xff0c;不能和数字直接相加减&#xff0c;需要把字符串转化为数字&#xff1a;-0即可 取整&#xff1a; // round:四舍五入取整console.log(Math.roun…

SQL IN 操作符

IN 操作符 IN 操作符允许您在 WHERE 子句中规定多个值。 SQL IN 语法 SELECT column1, column2, ... FROM table_name WHERE column IN (value1, value2, ...); 参数说明&#xff1a; column1, column2, ...&#xff1a;要选择的字段名称&#xff0c;可以为多个字段。如果…

STM32——USART串口

文章目录一、通信接口二、串口通信三、硬件电路四、电平标准五、串口参数及时序六、STM32的USART外设简介七、USART框图八、USART基本结构九、数据帧十、起始位侦测和采样位置对齐十一、数据采样十二、波特率发生器十三、数据模式十四、串口发送电路设计关键代码USART_SendData…