【C++】:C++中的STL序列式容器vector源码剖析

news2024/11/15 19:45:14

在这里插入图片描述
在这里插入图片描述

⛅️一 vector概述

vector的使用语法可以参考文章:​
总的来说:vector是可变大小数组
特点:
支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
元素保存在连续的内存空间中,因此通过下标取值非常快
在容器中间位置添加或删除元素非常耗时
一旦vector内存不足,重新申请内存之后,和原vector相关的指针,引用,迭代器都失效。内存重分配耗时很长
通常,使用vector是最好的选择,如果没有什么特殊要求,最好使用vector
与其他容器的比较:

在这里插入图片描述

⛅️二、vector定义摘要

vector定于与<stl_vector.h>头文件中

//alloc是SGI STL的空间配置器
template <class T, class Alloc = alloc>
class vector {
public:
    // vector 的嵌套类型定义
    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;

protected:
    // simple_alloc是SGI STL的空间配置器,见前面空间适配器文章的介绍
    typedef simple_alloc<value_type, Alloc> data_allocator;
    iterator start; // 表示目前使用空间的头
    iterator finish; // 表示目前使用空间的尾
    iterator end_of_storage; // 表示目前可用空间的尾

    void insert_aux(iterator position, const T& x);
    void deallocate() {
        if (start)
        data_allocator::deallocate(start, end_of_storage - start);
    }

    void fill_initialize(size_type n, const T& value) {
            start = allocate_and_fill(n, value);
            finish = start + n;
            end_of_storage = finish;
    }

public:
    iterator begin() { return start; }
    iterator end() { return finish; }
    size_type size() const { return size_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); }

    vector() : start(0), finish(0), end_of_storage(0) {}
    vector(size_type n, const T& value) { fill_initialize(n,value); } 
    vector(int n, const T& value) { fill_initialize(n,value); } 
    vector(long n, const T&value) { fill_initialize(n,value); } 
    explicit vector(size_type n) { fill_initialize(n,T()); }

    ~vector()
        destroy(start, finish); //全局函式,见前面文章destroy函数的介绍
        deallocate(); //这是 vector的㆒个 member function
    }

    reference front() { return *begin(); } // 第一个元素
    reference back() { return *(end() - 1); } // 最后一个元素
    void push_back(const T& x) { // 将元素安插至最尾端
        if (finish != end_of_storage) {
            construct(finish, x); //全局函式,见前面文章construct函数的介绍
            ++finish;
        }
        else
            insert_aux(end(), x); //这是 vector的一个member function
        }

    void pop_back() { // 将最尾端元素取出
        --finish;
        destroy(finish); // 全局函式,见前面文章destroy函数的介绍
    }

    iterator erase(iterator position) { // 清除某位置上的元素
        if (position + 1 != end())
        copy(position + 1, finish, position); // 后续元素往前搬移
        --finish;
        destroy(finish); // 全局函式,见前面文章destroy函数的介绍
        return position;
    }

    void resize(size_type new_size, const T& x) {
        if (new_size < size())
            erase(begin() + new_size, end());
        else
            insert(end(), new_size - size(), x);
    }
        void resize(size_type new_size) { resize(new_size, T()); }
        void clear() { erase(begin(), end()); }

protected:
    // 配置空间并填满内容
    iterator allocate_and_fill(size_type n, const T& x) {
        iterator result = data_allocator::allocate(n);
        uninitialized_fill_n(result, n, x); // 全局函式,见前面uninitialized_fill_n函数的介绍
        return result;
    }

⛅️三、vector的迭代器

vector维护的是一个连续线性空间,所以不论其元素类别是什么,普通指针都可以作为vector的迭代器而满足所有必要条件
vector迭代器支持有操作有(普通指针也具备):
operator*、operator->、operator++、operator–、operator+、operator-、operator+=、operator-=
vector支持随机存取,而普通指针正有着这样的能力,所以,vector提供的是随机访问迭代器(Random Access iterators)
vector的迭代器定义如下:

template <class T, class Alloc = alloc>
class vector {
public:
    typedef T value_type;
    typedef value_type* iterator; //vector的迭代器是原生指标
    ...
};

例如:

vector<int>::iterator ivite;   //等同于int* ivite;
vector<Shape>::iterator svite; //等同于Shape* svite;

⛅️四、vector的数据结构

vector的数据结构非常简单:一个线性连续空间
下面介绍vector的3个数据结构:
start:表示目前使用空间的头
finish:表示目前使用空间的尾
end_of_storage:表示目前可用空间的尾

template <class T, class Alloc = alloc>
class vector {
...
protected:
    iterator start; //表示目前使用空间的头
    iterator finish; //表示目前使用空间的尾
    iterator end_of_storage; //表示目前可用空间的尾
    ...
};

说明:为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充。这便是容量的概念。也就是说,一个vector的容量永远大于或等于其大小。一旦容量等于大小,下次再新增元素时就需要新开辟一块空间。如下图所示
在这里插入图片描述
运用start、finish、end_of_storage三个迭代器,vector提供了首尾标示、大小、容量、空容器判断、注标[]运算符、最前端元素值、最后端元素值…等机能,如下:

template <class T, class Alloc = alloc>
class vector {
...
public:
    iterator begin() { return start; }
    iterator end() { return finish; }
    size_type size() const { return size_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); }
    ...
};

⛅️五、vector的构造与内存管理(constructor、push_back)

vector的内存管理:vector默认使用alloc做为空间配置器,并据此另外定义了一个data_allocator,为的是更方便以元素大小为配置单位:

template <class T, class Alloc = alloc>
class vector {
protected:
    // simple_alloc<>见前面文章介绍
    typedef simple_alloc<value_type, Alloc> data_allocator;
    ...
};

于是,data_allocator::allocate(n) 表示配置n个元素空间
构造函数:vector提供许多构造函数,其中一个允许我们指定空间大小及初值:

//构造函数,允许指定vector大小n和初值value
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 n, const T& x) {
    iterator result = data_allocator::allocate(n); //配置n个元素空间
    uninitialized_fill_n(result, n, x); //全局函式,会根据第1个参数类型特性决定使用算法fill_n()或反复调用construct()来完成任务
    return result;
}

push_back()函数:当我们以push_back() 将新元素安插入于vector尾端时,该函式首先检查是否还有备用空间,如果有就直接在备用空间上建构元素,并调整迭代器finish,使vector变大。如果没有备用空间了,就扩充空间(重新配置、搬移数据、释放原空间)。push_back()原型如下:

void push_back(const T& x) {
    if (finish != end_of_storage) { //还有备用空间
        construct(finish, x); //全局函式
        ++finish; //调整水位高度
    }
    else //已无备用空间
        insert_aux(end(), x); // vector member function,见下
}
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
    if (finish != end_of_storage) { //还有备用空间
        // 在备用空间起始处建构一个元素,并以 vector 最后一个元素值为其初值。
        construct(finish, *(finish - 1));
        // 调整水位。
        ++finish;
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);
        *position = x_copy;
    }
    else { // 已无备用空间
        const size_type old_size = size();
        const size_type len = old_size != 0 ? 2 * old_size : 1;
        // 以上配置原则:如果原大小为0,则配置 1(个元素大小)
        // 如果原大小不为 0,则配置原大小的两倍,
        // 前半段用来放置原数据,后半段准备用来放置新数据
        iterator new_start = data_allocator::allocate(len); // 实际配置
        iterator new_finish = new_start;
        try {
            // 将原 vector 的内容拷贝到新vector
            new_finish = uninitialized_copy(start, position, new_start);
            // 为新元素设定初值 x
            construct(new_finish, x);
            // 调整水位
            ++new_finish;
            // 将原vector的备用空间中的内容也忠实拷贝过来
            new_finish = uninitialized_copy(position, finish, new_finish);
        }
        catch(...) {
            // "commit or rollback" semantics.
            destroy(new_start, new_finish);
            data_allocator::deallocate(new_start, len);
            throw;
        }
        //析构并释放原vector
        destroy(begin(), end());
        deallocate();

        // 调整迭代器,指向新vector
        vector start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
    }
}

⛅️六、vector内存重分配策略

vector的内存重分配策略:
vector是以数组的形式存储的,当往vector中增加元素时,如果vector的容量不足,那么vector就会进行扩容
扩容的规则是:并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是申请一块比现在大的新的内存空间(gcc和vc申请规则不同,见下面介绍),然后原来内存中的内容拷贝到新内存中,然后释放原来的内存
重点:在gcc和vc的环境下,vector的扩容规则是不一样的
注意(重点):对vector 的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。这是程序员易犯的一个错误,务需小心

#include <iostream>
#include <vector>

using namespace std;

int main()
{
  std::vector<int> iv;
  iv.push_back(1);

  cout << iv.capacity() << endl; //1

  iv.push_back(1);
  cout << iv.capacity() << endl; //2

  iv.push_back(1);
  cout << iv.capacity() << endl; //3

  iv.push_back(1);
  cout << iv.capacity() << endl; //4

  iv.push_back(1);
  cout << iv.capacity() << endl; //6

  iv.push_back(1);

  iv.push_back(1);
  cout << iv.capacity() << endl; //9

  return 0;
}

⛅️七、vector的元素操作(pop_back、erase、clear、insert)

所提供的元素操作动作很多,不就在此文章中一一说明
pop_back:

//将尾端元素拿掉,并调整大小
void pop_back() {
    --finish; //将尾端标记往前移一格,表示将放弃尾端元素
    destroy(finish); // destroy是全局函式
}

erase:

// 清除[first,last)中的所有元素
iterator erase(iterator first, iterator last) {
    iterator i = copy(last, finish, first); //copy是全局函式
    destroy(i, finish); //destroy是全局函式
    finish = finish - (last - first);
    return first;
}

下图是上面这个erase函数的版本
在这里插入图片描述

// 清除某个位置上的元素
iterator erase(iterator position) {
    if (position + 1 != end())
        copy(position + 1, finish, position); //copy是全局函式
    --finish;
    destroy(finish); //destroy是全局函式
    return position;
}

clear:

//清除容器内所有元素
void clear() { erase(begin(), end()); }

insert:

//从position开始,插入n个元素,元素初值为x
template<class T,class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
    if (n != 0) { //当n!= 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; // 将vector尾端标记后移
                copy_backward(position, old_finish - n, old_finish);
                fill(position, position + n, x_copy); //从插入点开始填入新值
            }
        }
        else {
            //“插入点之后的现有元素个数”小于等于“新增元素个数”
            uninitialized_fill_n(finish, n - elems_after, x_copy);
            finish += n - elems_after;
            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);
        // 以下配置新的vector空间
        iterator new_start = data_allocator::allocate(len);
        iterator new_finish = new_start;
        
        STL_TRY {
            // 以下首先将旧vector的安插点之前的元素复制到新空间
            new_finish = uninitialized_copy(start, position, new_start);
            // 以下再将新增元素(初值皆为n)填入新空间
            new_finish = uninitialized_fill_n(new_finish, n, x);
            // 以下再将旧 vector 的插入点之后的元素复制到新空间
            new_finish = uninitialized_copy(position, finish, new_finish);
        }
# ifdef STL_USE_EXCEPTIONS
        catch(...) {
        // 如有异常发生,实现"commit or rollback" semantics.
        destroy(new_start, new_finish);
        data_allocator::deallocate(new_start, len);
        throw;
        }
# endif /* STL_USE_EXCEPTIONS */

        // 以下清除并释放旧的vector
        destroy(start, finish);
        deallocate();
        // 以下调整水位标记
        start = new_start; finish =
        new_finish; end_of_storage =
        new_start + len;
    }
}

注意,插入完成后,新节点将位于哨兵迭代器(即上缪按的position,标示出插入点) 所指之节点的前方——这是STL对于“插入操作”的标准规范。下面的图片展示了insert(position,n,x)的操作
备用空间>=新增元素个数的情况:
①备用空间2>=新增元素个数2
②插入点之后的现有元素个数3>新增元素个数2
在这里插入图片描述
③插入点之后的现有元素个数2<=新增元素个数3
在这里插入图片描述
备用空间>=新增元素个数的情况:例如下面备用空间2<新增元素个数n==3
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

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

相关文章

SpringIOC之support模块GenericApplicationContext

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

【Spring 篇】基于注解的Spring事务控制详解

嗨&#xff0c;亲爱的读者朋友们&#xff01;欢迎来到这篇关于基于注解的Spring事务控制的博客。如果你曾为事务处理而头痛&#xff0c;那么这里将为你揭开事务的神秘面纱。我们将一步步深入探讨Spring事务的世界&#xff0c;用简单易懂的语言、充满情感色彩的文字&#xff0c;…

fastadmin 框架如何移除图片上传后预览中的删除按钮

在FastAdmin中&#xff0c;当我们启用了图片上传预览时&#xff0c;在预览区域会自动生成预览图和删除按钮&#xff0c;如下图&#xff1a; 如果我们想上移除掉这里的删除按钮&#xff0c;则需要启用自定义预览模板的功能。 首先我们找到视图中我们的预览容器&#xff0c;比如…

C++内存管理机制(侯捷)笔记4(完结)

C内存管理机制&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youtube: 侯捷-C内存管理机制 Github课程视频、PPT和源代码: https://github.com/ZachL1/Bilibili-plus 介绍 下面是第四讲和第五讲…

STM32F103RCT6开发板M3单片机教程07-TIMER1CH1输出 PWM做LED呼吸灯

概述 本教程使用是&#xff08;光明谷SUN_STM32mini开发板&#xff09; 免费开发板 在谷动谷力社区注册用户&#xff0c;打卡&#xff0c;发帖求助都可以获取积分&#xff0c;当然最主要是发原创应用文档奖励更多积分&#xff0e; (可用积分换取&#xff0c;真的不用钱&…

Mysql InnoDB行锁深入理解

Record Lock记录锁 Record Lock 称为记录锁&#xff0c;锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的&#xff1a; 当一个事务对一条记录加了 S 型记录锁后&#xff0c;其他事务也可以继续对该记录加 S 型记录锁&#xff08;S 型与 S 锁兼容&#xff09;&#xff0c;…

NAND SCA接口对性能影响有多大?

在多LUN场景下&#xff0c;SCA接口尤其有助于提高随机读取性能。通过合理安排读取命令和等待时间&#xff08;如tR&#xff09;&#xff0c;SCA接口可以在一个LUN完成读取后立即开始另一个LUN的读取操作&#xff0c;而无需等待整个DQ总线空闲&#xff0c;从而减少了延迟和提高了…

设计一个简易版的数据库路由

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

双指针问题——求只包含两个元素的最长连续子序列(子数组)

一&#xff0c;题目描述 你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而&#xff0c;农场的主人设定了一些严格的规矩&#xff0c;你必…

vue前端开发自学,组件的生命周期函数介绍001

vue前端开发自学,组件的生命周期函数介绍001&#xff01;今天介绍一下&#xff0c;组件自身的生命周期函数。又叫做&#xff0c;钩子函数。可以借助于这些钩子函数&#xff0c;实现很多我们预想的效果。比如&#xff0c;在组件渲染 之前&#xff0c;就做一些特殊的操作等等。 …

什么是 CAS

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

Javaweb之SpringBootWeb案例查询部门以及前后端联调的详细解析

2.1 查询部门 2.1.1 原型和需求 查询的部门的信息&#xff1a;部门ID、部门名称、修改时间 通过页面原型以及需求描述&#xff0c;我们可以看到&#xff0c;部门查询&#xff0c;是不需要考虑分页操作的。 2.1.2 接口文档 部门列表查询 基本信息 请求路径&#xff1a;/depts …

内存卡为什么会提示格式化,内存卡提示格式化还能恢复吗

对于许多电脑用户来说&#xff0c;执行内存卡格式化操作导致数据丢失是一个常见的问题。在日常生活中&#xff0c;数据丢失的情况并不少见&#xff0c;但内存卡格式化后的数据恢复相对较难。目前&#xff0c;能够使用的方法较少&#xff0c;且成功率较低&#xff0c;但并不是没…

NAND系统性能提升常见方案

随着NAND的发展&#xff0c;针对NAND系统性能提升&#xff0c;业内目前主要的做法有以下几种方案&#xff1a; 1.提升总线频率和优化AC时序&#xff1a; 提高NAND闪存接口的工作频率可以显著加快数据传输速度。通过不断改进工艺和技术&#xff0c;缩短了信号稳定时间、降低了延…

Linux第29步_虚拟机连接(与主机断开连接)U盘选项为灰色解决方法

在WIN11中&#xff0c;虚拟机“连接(与主机断开连接)U盘”选项为灰色&#xff0c;解决方法如下&#xff1a; 1、关闭虚拟机电源&#xff0c;得到下面的界面&#xff1a; 2、根据上述提示&#xff0c;找到虚拟机所在磁盘 3、配置文件属性见下图&#xff1a; 4、使用记事本打开…

vim基本操作命令

一、vi简介 vi是“Visual interface”的简称&#xff0c;它在Linux上的地位就仿佛Edit程序在DOS上一样。它可以执行输出、删除、查找、替换、块操作等众多文本操作&#xff0c;而且用户可以根据自己的需要对其进行定制。Vi不是一个排版程序&#xff0c;它不象Word或WPS那样可以…

“具身智能”浪潮中,达闼机器人的商业化“奇点”已然到来?

当前&#xff0c;人形机器人产业正在快速发展&#xff0c;而2023年必将会是载入史册的一年。 具体来看&#xff0c;2023年&#xff0c;AI技术大爆发&#xff0c;可在语言、视觉、运动控制、降低研发成本等多方面赋能人形机器人产业发展。与此同时&#xff0c;特斯拉、波士顿动…

NAND Separate Command Address (SCA) 接口命令解读

CA output packet和CA input packet是Separate Command Address (SCA) NAND接口协议中用于命令和地址传输的关键数据结构。 CA Input Packet: 在SCA接口中&#xff0c;输入到NAND器件的命令和地址信息被组织成并行至串行转换的CA&#xff08;Command and Address&#xff09;输…

linux创建文件并分配权限

linux中对文件的定义 在Linux中&#xff0c;文件是一个具有符号名字的一组相关联元素的有序序列。文件可以包含的内容十分广泛&#xff0c;操作系统和用户都可以将具有一定独立功能的一个程序模块、一组数据或一组文字命名为一个文件。文件名是数据有序序列集合&#xff08;文…

Asp .Net Core 系列: 集成 CORS跨域配置

文章目录 什么是CORS?Asp .Net Core 中如何配置CORS?CorsPolicyBuilder类详解注册以及使用策略三种方式EnableCors 和 DisableCors 特性关于带证书与不带证书代码的实现跨源&#xff08;cross-origin&#xff09;不带请求证书(Credentials)跨源&#xff08;cross-origin&…