【STL】vector的底层原理及其实现

vector的介绍

vector是一个可变的数组序列容器。
在这里插入图片描述
1.vector的底层实际上就是一个数组。因此vector也可以采用连续存储空间来存储元素。也可以直接用下标访问vector的元素。我们完全可以把它就当成一个自定义类型的数组使用。
2.除了可以直接用下标访问元素,vector还支持动态开辟空间以提高其灵活度。这也是vector与数组最大的区别之一。
3.为了减少不断插入元素带来的扩容消耗问题,vector会分配一些额外的空间以适应可能的增长,因此实际开辟的空间需要比已存在元素占有空间要大。vector的底层采用每次扩容原来空间的1.5或者2倍(不同编译器的扩容策略可能不同)。
4.由于底层是用数组实现的,不可避免地也具有数组的缺点,即除了尾插的插入、删除的效率低下

vector的使用

关于vector的使用在cplusplus上一览无余。我接下来介绍常用的一些接口。

vector的定义

在这里插入图片描述
vector一共有4个构造函数,分别对应不同的构造场景。

1.vector().无参构造
2.vector(size_type n,const value_type& val=value_type()).构造并且初始化n个元素的值为value.
3.vector(const vector& x).拷贝构造
4.vector(InputIterator first,InputIterator last).迭代器初始化构造

#include <iostream>
#include <vector>
int main()
{// constructors used in the same order as described above:std::vector<int> first;                                // 无参构造std::vector<int> second(4, 100);                       // 构造的同时初始化4个元素都为100std::vector<int> third(second.begin(), second.end());  // 迭代器初始化构造std::vector<int> fourth(third);                       // 拷贝构造//迭代器初始化构造示例int myints[] = { 16,2,77,29 };std::vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));std::cout << "The contents of fifth are:";for (std::vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';return 0;
}

vector迭代器的使用

在这里插入图片描述
其中rbegin和rend是反向迭代器reverse_iterator类型,rbegin指向的是vecotr对象的最后一个元素,遍历方式和begin一样,只不过反向迭代器++的时候是往前移动。rend则指向vector第一个元素。
在这里插入图片描述
带c表示用const修饰了thsi指针。比如cbegin和cend都用const修饰迭代器指向的内容,即不可以修改迭代器指向元素的值。

const_iterator cbegin() const noexcept;

vector空间增长问题

跟vector空间有关的成员函数有
在这里插入图片描述

1.size()返回当前元素个数
2.max_size()返回vector最多能容纳元素的个数

3.关于resize

void resize (size_type n, value_type val = value_type());

resize可以调整vector的元素个数为n。
如果n<size(),删除后面的元素.
如果n>size(),增加元素个数到n,新增元素的值为val。
值得注意的是,如果n>size()并且大于capacity(),则会先扩容到n。

4.capacity()返回当前容量大小。vs下capacity是按1.5倍增长的,g++是按2倍增长的。

5.reserve()

void reserve (size_type n);

reserve(n)要求扩容到至少可以容纳n个元素。

6.shrink_to_fit()要求缩容到合适的容量。这个函数一般不常用。

vector扩容机制

用以下代码观察vs下的vector的扩容机制

int main() {vector<int>A;int t = A.capacity();for (int i = 0; i < 20; i++) {A.push_back(i);if (A.capacity() != t) {//每次容量发生改变就打印并输出cout << A.capacity() << endl;t = A.capacity();}}
}

在这里插入图片描述
每次扩容都是1.5倍,向下取整。

同样的代码,来看g++环境下的扩容机制:
在这里插入图片描述
每次扩容都是原来的2倍!

vector的增删查改

在这里插入图片描述
assign:
在这里插入图片描述
range(1)版本的assign函数可以用迭代器给vector对象重新赋值。会覆盖原来的内容,相当于赋值操作。
在这里插入图片描述

fill(2)版本的assign函数可以赋值给调用对象n个value的值。
在这里插入图片描述
push_back:尾插一个元素
pop_back:弹出最后一个元素

insert:
在这里插入图片描述
single element(1)版本,在迭代器position指向的位置处插入一个val.
在这里插入图片描述
fill(2)版本,可以在迭代器position指向的位置处插入n个值为val的元素。
在这里插入图片描述
range(3)版本,在position指向的位置处,插入一段首尾迭代器指向的所有元素。
在这里插入图片描述
erase:
在这里插入图片描述
可以删除position指向位置的元素,或者删除first-last所有指向元素。
在这里插入图片描述
swap:交换两个vector对象。
clear:清空vector对象的所有元素,并不会清空容量。
emplace:
在这里插入图片描述
可以在position指向位置处插入一个以args为参数构造出来的新元素,并返回插入成功后指向该位置的迭代器。
在这里插入图片描述
emplace_back:作用跟empace函数类似,只不过是尾插。

迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

简单来说就是我们在扩容的时候会开辟新空间,如果原来的迭代器没有被更新,还是指向原来的地址,那么就会造成程序崩溃。也有可能是在

具体有以下几种情况造成迭代器失效:

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
    在这里插入图片描述
  2. 指定位置元素的删除操作–erase
    在这里插入图片描述
    erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代
    器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是
    没有元素的,那么pos就失效了
    。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效
    了。

3.与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效。

迭代器失效解决办法:使用之前更新迭代器。

vector的模拟实现

vector.h包含了类的声明与定义。我这里没有分离。

#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<string>
#include<algorithm>
#include<iostream>
namespace bit
{template<class T>class vector{public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;template<class T> friend void Swap(vector<T>& A, vector<T>& B);iterator begin() {return _start;}iterator end() {return _finish;}const_iterator begin() const{return _start;}const_iterator end() const {return _finish;}// construct and destroyvector() {_start = _finish = _endOfStorage = nullptr;}vector(int n, const T& value = T()) {reserve(n);for (size_t i = 0; i < n; i++)push_back(value);}template<class inputiterator>vector(inputiterator first, inputiterator last) {size_t n = last - first;reserve(n);memcpy(_start, first, n * sizeof(T));_finish = _start + n;_endOfStorage = _start + n;}vector(const vector<T>& v) {reserve(v.capacity());for (auto& it : v) {push_back(it);}}vector<T>& operator= (vector<T> v) {reserve(v.capacity());memcpy(_start, v._start, v.size() * sizeof(T));_finish = _start + v.size();return *(this);}~vector() {delete[] _start;_start = _finish = _endOfStorage = nullptr;}// capacitysize_t size() const {return _finish - _start;}size_t capacity() const {return _endOfStorage - _start;}void reserve(size_t n) {if (n>capacity()) {T* temp = new T[n];size_t sz = size();//  memcpy(temp, _start, sz * sizeof(T));for (size_t i = 0; i < sz; i++) {temp[i] = _start[i];}delete[] _start;_start = temp;_finish = _start + sz;_endOfStorage = _start + n;}}void resize(size_t n, const T& value = T()) {if (n > size()) {reserve(n);T* begin = _start + size();while (begin < _start + n) {*begin = value;begin++;}}else {_finish = _start + n;}}///access///T& operator[](size_t pos) {assert(pos < size()); return _start[pos];}const T& operator[](size_t pos)const {assert(pos < size());return _start[pos];}///modify/void push_back(const T& x) {if (size() == capacity()) {reserve(capacity()==0?4:capacity()*2);}*_finish = x;_finish++;}void pop_back() {assert(size() > 0);_finish--;}void Swap(vector<T>& v) {std::swap(v._start, _start);std::swap(v._finish, _finish);std::swap(v._endOfStorage, _endOfStorage);}bool empty() {return size() == 0;}iterator insert(iterator pos, const T& x) {assert(pos <= _finish&&pos>=_start);size_t len = pos - _start;reserve(size() + 1);pos = _start + len;iterator end = _finish-1;while (end >= pos) {*(end+1) = *end;end--;}*pos = x;_finish++;return _start;}iterator erase(iterator pos) {assert(pos < _finish && pos >= _start);iterator begin = pos+1;while (begin <_finish) {*(begin - 1) = *(begin);begin++;}_finish--;return _start;}private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endOfStorage; // 指向存储容量的尾};template<class T>void  Swap(vector<T>& A, vector<T>& B) {std::swap(A._start, B._start);std::swap(A._finish, B._finish);std::swap(A._endOfStorage, B._endOfStorage);}}

这里值得注意的是,如果对象中涉及到资源管理时(比如用reserve扩容),千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

比如vector<string>,在扩容拷贝原来的数据的时候就不能使用memcpy,因为memcpy对于string底层的指针只是值拷贝。也就意味着,新空间里的string指针和旧空间里的指针指向的空间是一样的。

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

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

相关文章

掌握数据相关性新利器:基于R、Python的Copula变量相关性分析及AI大模型应用探索

在工程、水文和金融等各学科的研究中&#xff0c;总是会遇到很多变量&#xff0c;研究这些相互纠缠的变量间的相关关系是各学科的研究的重点。虽然皮尔逊相关、秩相关等相关系数提供了变量间相关关系的粗略结果&#xff0c;但这些系数都存在着无法克服的困难。例如&#xff0c;…

解决win7作为虚拟机无法复制粘贴共享文件的问题

win7作为虚拟机经常会出现无法与主机的剪切板共享、文件共享。 归根结底是win7虚拟机里面没有安装VMware Tools 能够成功安装vmware tools的条件&#xff1a; 1&#xff09;win7版本为win7 sp1及以上 2&#xff09;安装KB4490628&#xff0c;KB4474419补丁 因此下面来详细介绍…

【LeetCode题解】2192. 有向无环图中一个节点的所有祖先+1026. 节点与其祖先之间的最大差值

文章目录 [2192. 有向无环图中一个节点的所有祖先](https://leetcode.cn/problems/all-ancestors-of-a-node-in-a-directed-acyclic-graph/)思路&#xff1a;BFS记忆化搜索代码&#xff1a; 思路&#xff1a;逆向DFS代码&#xff1a; [1026. 节点与其祖先之间的最大差值](https…

为什么说AI的尽头是生物制药?

AI的尽头究竟是什么&#xff1f;有投资者说是光伏&#xff0c;也有投资者说是电力&#xff0c;而英伟达给出的答案则是生物制药。 在英伟达2023年投资版图中&#xff0c;除AI产业根基算法与基础建设外&#xff0c;生物制药是其重点布局的核心赛道。英伟达医疗保健副总裁Kimber…

FastEI论文阅读

前言 研究FastEI&#xff08;Ultra-fast and accurate electron ionization mass spectrum matching for compound identification with million-scale in-silico library&#xff09;有很长时间了&#xff0c;现在来总结一下&#xff0c;梳理一下认知。PS&#xff1a;为什么要…

【LeetCode: 21. 合并两个有序链表 + 链表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

axios快速入门

一、环境配置 1.1概述 上古浏览器页面在向服务器请求数据时&#xff0c;因为返回的是整个页面的数据&#xff0c;页面都会强制刷新一下&#xff0c;这对于用户来讲并不是很友好。并且我们只是需要修改页面的部分数据&#xff0c;但是从服务器端发送的却是整个页面的数据&#…

攻防世界 Broadcast 题目解析

Broadcast 一&#xff1a;题目 二&#xff1a;解析 将压缩包解压&#xff0c;得到如上图所示&#xff0c;打开task.py&#xff0c;之后得到flag 这个有点简单了&#xff0c;不要被解压后文件太多所迷惑。

InnoDB中的索引方案

文章目录 InnoDB中的索引方案 InnoDB支持多种类型的索引&#xff0c;包括B-tree索引、全文索引、哈希索引等。B-tree索引是InnoDB存储引擎的默认索引类型&#xff0c;适用于所有的数据类型&#xff0c;包括字符串、数字和日期等。 以下是创建InnoDB表及其B-tree索引的示例代码…

VBA数据库解决方案第九讲:把数据库的内容在工作表中显示

《VBA数据库解决方案》教程&#xff08;版权10090845&#xff09;是我推出的第二套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;是学完字典后的另一个专题讲解。数据库是数据处理的利器&#xff0c;教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…

2024年阿里云4核8G服务器多少钱一年?4C8G服务器955元

阿里云服务器4核8G租用优惠价格955元一年&#xff0c;配置为云服务器ECS通用算力型u1实例4核8G配置、ESSD Entry盘20G-40G、1M-3M带宽&#xff0c;实例规格为ecs.u1-c1m2.xlarge&#xff0c;阿里云优惠活动 yunfuwuqiba.com/go/aliyun 活动链接打开如下图&#xff1a; 阿里云4核…

【数据结构】ArrayList详解

目录 前言 1. 线性表 2. 顺序表 3. ArrayList的介绍和使用 3.1 语法格式 3.2 添加元素 3.3 删除元素 3.4 截取部分arrayList 3.5 其他方法 4. ArrayList的遍历 5.ArrayList的扩容机制 6. ArrayList的优缺点 结语 前言 在集合框架中&#xff0c;ArrayList就是一个…

spring boot学习第十六篇:配置多数据源

1、代码参考&#xff1a; dynamic-ds/spring-boot-dynamic-ds at main veminhe/dynamic-ds GitHub 2、验证 2.1调用POST接口http://localhost:8081/hmblogs/blog/addBlog 2.2改动数据源为BJ 然后调用接口添加数据 然后查看ds0库的博客数据

【最新可用】Claude国内镜像,可上传图片,可用Claude3全系模型(包括Pro版本的Opus)!亲测比GPT好用!

亲测可用&#xff0c;镜像地址&#xff1a;Claude 3 镜像 使用方法 访问镜像&#xff1a;Claude 3 镜像 2. 点击设置&#xff0c;配置授权码&#xff0c;关闭设置。这里免费赠送一个体验版的授权码 sk-SZcJyvx3RXRID624E2D3795578Df44C7Af03F2909a8f5eA0 即可发起对话啦&…

Android Studio学习9——使用Logcat打印日志

在Android开发中&#xff0c;Logcat是一个工具&#xff0c;它允许开发者查看设备或模拟器的日志信息。开发者可以使用Log类来打印日志信息&#xff0c;这对于调试和错误排查非常有帮助。 v 或 verbose: 最低等级&#xff0c;显示所有消息。d 或 debug: 用于调试消息。i 或 info…

深入浅出 -- 系统架构之负载均衡Nginx动静分离

一、Nginx动静分离 动静分离应该是听的次数较多的性能优化方案&#xff0c;那先思考一个问题&#xff1a;为什么需要做动静分离呢&#xff1f;它带来的好处是什么&#xff1f; 其实这个问题也并不难回答&#xff0c;当你搞懂了网站的本质后&#xff0c;自然就理解了动静分离的重…

Docker安装mysql并且设置主从

Docker安装部署mysql 下载镜像 docker pull mysql:5.7.35查看镜像 docker images启动 直接启动不挂载文件 docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD123456 -d mysql:5.7.35挂载文件 docker run -p 3306:3306 --name mysql \ -v /usr/local/docker/m…

【Linux】环境基础开发工具使用——gcc/g++使用

Linux编译器-gcc/g使用 1. 背景知识 1. 预处理&#xff08;进行宏替换 ) 2. 编译&#xff08;生成汇编 ) 3. 汇编&#xff08;生成机器可识别代码&#xff09; 4. 连接&#xff08;生成可执行文件或库文件 ) 2. gcc如何完成 格式 gcc [ 选项 ] 要编译的文件 [ 选…

代码随想录第19天

654. 最大二叉树 已解答 中等 相关标签 相关企业 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀…

Linux shell编程学习笔记45:uname命令-获取Linux系统信息

0 前言 linux 有多个发行版本&#xff0c;不同的版本都有自己的版本号。 如何知道自己使用的Linux的系统信息呢&#xff1f; 使用uname命令、hostnamectl命令&#xff0c;或者通过查看/proc/version文件来了解这些信息。 我们先看看uname命令。 1 uname 命令的功能和格式 …