C++ STL: vector使用及源码剖析

news2024/10/7 10:21:42

vector使用

vector定义

语句

作用  

vector<int> a(n);

指定容器大小为n     

vector<int> a(n, x);

指定容器大小为n,并初始化所有元素为x

vector<vector<int>> a(m, vector<int>(n));

m行n列的二维数组,可以直接用a[i][j]访问

访问vector容器中元素的操作

语句

作用

vec.at(index)

返回由index指定的位置上的元素

vec[index]

返回由index指定的位置上的元素

vec.front()

返回第一个元素(不检查容器是否为空)

vec.back()

返回最后一个元素(不检查容器是否为空)

vector容器大小相关的操作

语句

作用

vec.capacity()

返回不重新分配空间可以插入到容器vec中的元素的最大个数

vec.empty()

容器vec为空,返回true;否则,返回false

vec.size()

返回容器vec中当前的元素个数

vec.max_size()

返回可以插入到容器vec中的元素的最大个数

vec.resize(num)

将元素个数改为num。如果size()增加,默认的构造函数负责创建这些新元素

vec.resize(num, elem)

将元素个数改为num。如果size()增加,这些新元素初始化为elem

vec.reserve(num)

保留足够空间以容纳num个元素,避免在达到num之前的插入操作中重新分配空间

元素插入、元素删除、遍历向量容器中的元素

语句

作用

vec.clear()

从容器中删除所有元素

vec.erase(position)

删除由position指定的位置上的元素

vec.erase(beg, end)

删除从beg到end-1之间的所有元素

vec.insert(position, elem)

将elem的一个拷贝插入到由position指定的位置上,并返回新元素的位置

vec.insert(position, n, elem)

将elem的n个拷贝插入到由 position指定的位置上

vec.insert(position, beg, end)

将从beg到end-1之间的所有元素的拷贝插入到vec中由position指定的位置上

vec.push_back(elem)

将elem的一个拷贝插入到vector的末尾

vec.pop_back()

删除最后元素


vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新的元素。vector的实现技术,关键在于对大小的控制以及重新配置时的数据移动效率。

vector的数据结构:

 vector采用线性连续空间,以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。

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

vector的构造函数和析构函数:

emplate <class T, class Alloc = alloc>
class vector {
...
protected:
  typedef simple_alloc<value_type, Alloc> data_allocator;
  ....

  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);
        uninitialized_fill_n(result, n, x);
        return result;
    }
...
public:
    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部分构造函数,其中默认构造函数只是把所有的迭代器都初始化为0,但它并没有申请内存空间。

另外4个大同小异,都向堆申请了大小为n的内存空间,只是初始化这些空间的时候进行的操作不一样而已。

这4个构造函数都用同一个函数fill_initialize()来进行堆空间的申请并且初始化。

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

它接受两个参数n和value,n指明了要申请的堆空间大小,value指明了要初始化这些堆空间的内容,并把它们传给另外一个函数allocate_and_fill() ,该函数才是真正的申请堆空间和初始化。

allocate_and_fill() 函数。
 // 分配空间并填满内容
iterator allocate_and_fill(size_type n, const T& x) {
         iterator result = data_allocator::allocate(n);
         uninitialized_fill_n(result, n, x);
         return result;
 }

函数接受来自fill_initialize()的两个参数然后申请堆空间并初始化。data_allocator实质上就是simple_alloc<value_type, Alloc>,simple_alloc是SGI STL的空间配置器。若果是SGI STL第一级配置器那么data_allocator::allocate()实质上就是直接调用c语言中的malloc()来申请堆空间;若是第二级配置器,就先考察申请区块是否大于128bytes,若是大于则转调用第一级配置器,否则就以内存池来管理,目的是为了避免太多小额区块造成内存碎片化 

vector的析构函数:

~vector() {
    destroy(start, finish);
dellocate();
}

析构函数很简单,就调用两个函数:destroy()和dellocate()。destroy()负责对象的析构,dellocate()负责释放申请的堆空间。这里释放的方式又与空间配置器相关。若果是第一级配置器,就直接调用c语言中free()函数,这正如申请时的简便。但若果是第二级配置器,则比较复杂一些上面的内容就是关于vector申请和释放堆空间的大概过程,但仅仅是申请和释放堆空间而已。

// 第一个版本
template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}

 第一个版本接受一个指针,调用析构函数将该指针所指的对象析构。

// 第二个版本
template<class ForwardIterator, class T>
inline void destroy(ForwardIterator first, ForwardIterator last, T*) {
    __destroy(first, last, value_type(first));
}

第二版本接受first和last两个迭代器,将[first, last)范围内的所有对象析构掉。 

 

vector的push_back函数:

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

 当我们把元素push_back到vector的尾端后,函数首先检查是否还有备用的空间,如果有的话就调用construct()函数,在finish迭代器指定的位置上构建x对象,同时改变finish迭代器,使其自增1。没有备用空间,就需要扩充空间,调用insert_aux()函数。

insert_aux()如下:

template <class T, class Alloc>
    void 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
            {
                // uninitialized_copy()的第一个参数指向输入端的起始位置
                // 第二个参数指向输入端的结束位置(前闭后开的区间)
                // 第三个参数指向输出端(欲初始化空间)的起始处
                // 将原vector的安插点以前的内容拷贝到新vector
                new_finish = uninitialized_copy(start, position, new_start);
                // 为新元素设定初值 x
                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;
            }
            // 析构并释放原vector
            destroy(begin(), end());
            deallocate();
            // 调整迭代器,指向新vector
            start = new_start;
            finish = new_finish;
            end_of_storage = new_start + len;
        }
}

Vector动态扩容:

当备用空间不足时,vector做了以下的工作:

1重新分配空间:若原来的空间大小为0,则扩充空间为1,否则扩充为原来的两倍。

2移动数据释放原空间更新迭代器当调用默认构造函数构造vector时,其空间大小为0但当我们push_back一个元素到vector尾端时,vector就进行空间扩展,大小为1,以后每当备用空间用完了,就将空间大小扩展为原来的两倍。

动态增加大小,并不是在原空间之后接续新空间,(因为无法保证原空间之后上有可供分配的空间),而是以原大小的两倍来另外分配一块较大空间,因此,一旦空间重新分配,指向原vector的所有迭代器就会失。

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

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

相关文章

游戏开发-会飞的小鸟(已完结,附源码)

游戏开发-会飞的小鸟&#xff08;已完结&#xff0c;附源码&#xff09; 你将学到的课程链接详细介绍 你将学到的 掌握Java编程的基本技能开发出自己的“会飞的小鸟”游戏对面向对象编程有深刻的理解学会运用常见算法和数据结构解决问题能够独立调试和优化自己的代码 课程链接…

(2)(2.14) SPL Satellite Telemetry

文章目录 前言 1 本地 Wi-Fi&#xff08;费用&#xff1a;30 美元以上&#xff0c;范围&#xff1a;室内&#xff09; 2 蜂窝电话&#xff08;费用&#xff1a;100 美元以上&#xff0c;范围&#xff1a;蜂窝电话覆盖区域&#xff09; 3 手机卫星&#xff08;费用&#xff…

Android.mk 语法详解

一.Android.mk简介 Android.mk 是Android 提供的一种makefile 文件,注意用来编译生成&#xff08;exe&#xff0c;so&#xff0c;a&#xff0c;jar&#xff0c;apk&#xff09;等文件。 二.Android.mk编写 分析一个最简单的Android.mk LOCAL_PATH : $(call my-dir) //定义了…

[Python] opencv - 什么是直方图?如何绘制图像的直方图?如何对直方图进行均匀化处理?

什么是直方图&#xff1f; 直方图是一种统计图&#xff0c;用于展示数据的分布情况。它将数据按照一定的区间或者组进行划分&#xff0c;然后计算在每个区间或组内的数据频数或频率&#xff08;即数据出现的次数或占比&#xff09;&#xff0c;然后用矩形或者柱形图的形式将这…

『运维备忘录』之 TAR 命令详解

运维人员不仅要熟悉操作系统、服务器、网络等只是&#xff0c;甚至对于开发相关的也要有所了解。很多运维工作者可能一时半会记不住那么多命令、代码、方法、原理或者用法等等。这里我将结合自身工作&#xff0c;持续给大家更新运维工作所需要接触到的知识点&#xff0c;希望大…

绕过安全狗

本节我们想要绕过的安全狗版本为v4.023957 &#xff0c;它是网站安全狗的Apache版。 首先搭建环境。渗透环境选用DVWA漏洞集成环境&#xff0c;下载地址 为http://www.dvwa.co.uk/ 。DVWA是一款集成的渗透测试演练环境&#xff0c;当刚刚入门 并且找不到合适的靶机时&#xff…

c++ 类,第一篇章,初始化列表 (详细)

快过年啦&#xff01;雀儿在这里提前祝大家新年快乐&#xff01; 初始化&#xff0c;就是在一个变量在创建的时候被赋值&#xff0c;一共有四种可能 //X是类名&#xff0c;a是对象名&#xff0c;v是初始值 X a{v}; X a1{v}; X a2v; X a3(v);一共四种写法&#xff0c;如上。 第…

正点原子--STM32基本定时器学习笔记(1)

目录 1. 定时器概述 1.1 软件定时原理 1.2 定时器定时原理 1.3 定时器分类 1.4 定时器特性表 1.5 基本、通用、高级定时器的功能整体区别 2. 基本定时器简介 3. 基本定时器框图 时钟树分析 这部分是笔者对基本定时器的理论知识进行学习与总结&#xff01;主要记录学习…

Leaf——美团点评分布式ID生成系统

0.普通算法生成id的缺点 1.Leaf-segment数据库方案 第一种Leaf-segment方案&#xff0c;在使用数据库的方案上&#xff0c;做了如下改变&#xff1a; - 原方案每次获取ID都得读写一次数据库&#xff0c;造成数据库压力大。改为利用proxy server批量获取&#xff0c;每次获取一…

基于spring cloud alibaba的微服务平台架构规划

平台基础能力规划&#xff08;继续完善更新…&#xff09; 一、统一网关服务&#xff08;独立服务&#xff09; 二、统一登录鉴权系统管理&#xff08;独立服务&#xff09; 1.统一登录 2.统一鉴权 3.身份管理 用户管理 角色管理 业务系统和菜单管理 部门管理 岗位管理 字典管…

一步步建立一个C#项目(连续读取S7-1200PLC数据)

这篇博客作为C#的基础系列,和大家分享如何一步步建立一个C#项目完成对S7-1200PLC数据的连续读取。首先创建一个窗体应用。 1、窗体应用 2、配置存储位置 3、选择框架 拖拽一个Button,可以选择视图菜单---工具箱 4、工具箱 拖拽Lable控件和TextBook控件 5、拖拽控件 接下来…

算法day12

算法day12 二叉树理论基础114 二叉树的前序遍历145 二叉树的后序遍历94 二叉树的中序遍历迭代法 二叉树理论基础 直接看代码随想录就完事了&#xff0c;之前考研也学过&#xff0c;大概都能理解 我这里就说说代码层面的。 二叉树的存储&#xff1a; 1、链式存储&#xff1a;这…

简单实验 spring cloud gateWay 路由实验 实验

1.概要 1.1 说明 微服务统一网关实验&#xff0c;这里简单实验一下路由的功能 1.2 实验步骤&#xff0c;使用下面这个工程作为基础工程添加了一个gateWay做如下使用 简单实践 spring cloud nacos nacos-server-2.3.0-CSDN博客 2 代码 2.1 工程文件 <?xml version&quo…

【Linux取经路】探寻shell的实现原理

文章目录 一、打印命令行提示符二、读取键盘输入的指令三、指令切割四、普通命令的执行五、内建指令执行5.1 cd指令5.2 export指令5.3 echo指令 六、结语 一、打印命令行提示符 const char* getusername() // 获取用户名 {return getenv("USER"); }const char* geth…

生成式学习,特别是生成对抗网络(GANs),存在哪些优点和缺点,在使用时需要注意哪些注意事项?

生成对抗网络&#xff08;GANs&#xff09; 1. 生成对抗网络&#xff08;GANs&#xff09;的优点&#xff1a;2. 生成对抗网络&#xff08;GANs&#xff09;的缺点&#xff1a;3. 使用生成对抗网络&#xff08;GANs&#xff09;需要注意的问题 1. 生成对抗网络&#xff08;GANs…

学生管理系统(javaSE第一阶段项目)

JavaSE第一阶段项目_学生管理系统 1.项目介绍 此项目是JavaSE第一阶段的项目,主要完成学生对象在数组中的增删改查,大家可以在此项目中发挥自己的想象力做完善,添加其他功能等操作,但是重点仍然是咱们前9个模块的知识点2.项目展示 2.1.添加功能 2.2.查看功能 2.3.修改功能 2…

第二证券:大涨5%,这一指数爆发!

A股商场今日上午进一步上行&#xff0c;各大指数持续上涨&#xff0c;其间上证指数克复2800点。小市值股票体现更佳&#xff0c;中证1000指数上午大涨5%。 港股商场方面&#xff0c;今日上午一度大幅上涨&#xff0c;后涨幅有所回落。港股百胜我国今日上午体现抢眼&#xff0c…

jvm垃圾收集器之七种武器

1.回收算法 1.1 标记-清除算法(Mark-Sweep) 分为两个阶段&#xff0c;标注和清除。标记阶段标记出所有需要回收的对象&#xff0c;清除阶段回收被标记的对象所占用的空间。 该算法最大的问题是内存碎片化严重&#xff0c;后续可能发生大对象不能找到可利用空间的问题。 1.2 …

10.0 Zookeeper 权限控制 ACL

zookeeper 的 ACL&#xff08;Access Control List&#xff0c;访问控制表&#xff09;权限在生产环境是特别重要的&#xff0c;所以本章节特别介绍一下。 ACL 权限可以针对节点设置相关读写等权限&#xff0c;保障数据安全性。 permissions 可以指定不同的权限范围及角色。 …