【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.rhkb.cn/news/297843.html

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

相关文章

Redis缓存穿透和缓存雪崩

一、缓存穿透 1 什么是缓存穿透 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中&#xff0c;导致请求直接到了数据库上&#xff0c;根本没有经过缓存这一层。举个例子&#xff1a;某个黑客故意制造我们缓存中不存在的 key 发起大量请求&#xff0c;导致大量请求落到数…

Spoon Taking Problem(c++题解)

题目描述 &#xfffd;N 人が円卓に座っており&#xff0c;各人は反時計回りに順に 1, …, &#xfffd;1, …, N と番号付けられています&#xff0e;各人はそれぞれ左右どちらか一方の利き手を持っています&#xff0e; 円卓上には 1, …, &#xfffd;1, …, N と番号付け…

Golang Gin框架

1、这篇文章我们简要讨论一些Gin框架 主要是给大家一个基本概念 1、Gin主要是分为路由和中间件部分。 Gin底层使用的是net/http的逻辑&#xff0c;net/http主要是说&#xff0c;当来一个网络请求时&#xff0c;go func开启另一个协程去处理后续(类似epoll)。 然后主协程持续…

Vue 样式技巧总结与整理[中级局]

SFC&#xff08;单文件组件&#xff09;由 3 个不同的实体组成&#xff1a;模板、脚本和样式。三者都很重要&#xff0c;但后者往往被忽视&#xff0c;即使它可能变得复杂&#xff0c;且经常导致挫折和 bug。 更好的理解可以改善代码审查并减少调试时间。 这里有 7 个奇技淫巧…

VB 通过COM接口解析PSD文件

最近有PS测评的需求&#xff0c;故而想到了解析psd文件&#xff0c;目的就是为了获取文档信息和图层信息&#xff1b;获取PS的图像信息有很多方式&#xff0c;有过程性的&#xff0c;比如监听PS的各种操作事件&#xff1b;有结果性的&#xff0c;比如本文写的解析PSD文件。 0.…

【算法练习】28:选择排序学习笔记

一、选择排序的算法思想 弄懂选择排序算法&#xff0c;先得知道两个概念&#xff1a;未排序序列&#xff0c;已排序序列。 原理&#xff1a;以升序为例&#xff0c;选择排序算法的思想是&#xff0c;先将整个序列当做未排序的序列&#xff0c;以序列的第一个元素开始。然后从左…

libusb Qt使用记录

1.libusb 下载 &#xff0c;选择编译好的二进制文件&#xff0c;libusb-1.0.26-binaries.7z libusb Activity 2. 解压 3. 在 Qt Widgets Application 或者 Qt Console Application 工程中导入库&#xff0c;Qt 使用的是 minggw 64编译器&#xff0c;所以选择libusb-MinGW-x64。…

Kubernetes(k8s):精通 Pod 操作的关键命令

Kubernetes&#xff08;k8s&#xff09;&#xff1a;精通 Pod 操作的关键命令 1、查看 Pod 列表2、 查看 Pod 的详细信息3、创建 Pod4、删除 Pod5、获取 Pod 日志6、进入 Pod 执行命令7、暂停和启动 Pod8、改变 Pod 副本数量9、查看当前部署中使用的镜像版本10、滚动更新 Pod11…

保研线性代数复习3

一.基底&#xff08;Basis&#xff09; 1.什么是生成集&#xff08;Generating Set&#xff09;&#xff1f;什么是张成空间&#xff08;Span&#xff09;&#xff1f; 存在向量空间V(V&#xff0c;&#xff0c;*)&#xff0c;和向量集&#xff08;xi是所说的列向量&#xff…

集合/容器

集合概念 当我们保存一组一样(类型相同)的元素时候&#xff0c;我们应该使用一个容器来存储&#xff0c;就可以采用数组&#xff0c;但是数组存在以下缺点&#xff1a; 1、长度开始时必须指定&#xff0c;一旦指定就不能更改。 2、使用数组进行增加元素的步骤比较麻烦 这时候…

深入PostgreSQL中的pg_global表空间

pg_global表空间的位置 在PG当中&#xff0c;一个实例(cluster)初始化完以后&#xff0c;你会看到有下边两个与表空间相关的目录生成&#xff1a; $PGDATA/base $PGDATA/global 我们再用元命令\db以及相关视图看看相应的表空间信息&#xff1a; postgres# \db …

Golang | Leetcode Golang题解之第10题正则表达式匹配

题目&#xff1a; 题解&#xff1a; func isMatch(s string, p string) bool {m, n : len(s), len(p)matches : func(i, j int) bool {if i 0 {return false}if p[j-1] . {return true}return s[i-1] p[j-1]}f : make([][]bool, m 1)for i : 0; i < len(f); i {f[i] m…

重构智能防丢产品,苹果Find My技术引领市场发展

目前市场上最主要的防丢技术是蓝牙防丢和GPS防丢&#xff0c;蓝牙防丢是通过感应防丢器与绑定手机的距离来实现防丢的。一般防丢会默认设置一个最远安全距离&#xff0c;超过这个安全距离后&#xff0c;与手机蓝牙信号断开&#xff0c;触发防丢报警&#xff0c;用户根据防丢报警…

JS详解-手写Promise!!!

前言&#xff1a; 针对js的深入理解&#xff0c;作者学习并撰写以下文章&#xff0c;由于理解认知有限难免存在偏差&#xff0c;请大家指正&#xff01;所有定义来自mdn。 Promise介绍&#xff1a; 对象表示异步操作最终的完成&#xff08;或失败&#xff09;以及其结果值. 描…

设计模式 --5观察者模式

观察者模式 观察者模式的优缺点 优点 当一个对象改变的时候 需要同时改变其他对象的相关动作的时候 &#xff0c;而且它不知道有多少具体的对象需要改变 应该考虑使用观察者模式 。观察者模式的工作就是解除耦合 让耦合双方都依赖与抽象 而不是具体 是的各自改变都不会影响另…

Leetcode_2两数相加

文章目录 前言一、两数相加1.1 问题描述1.2 解法一&#xff1a;分别将链表转为数字&#xff0c;然后相加1.3 代码实现1.4 解法二&#xff1a;分别将对应位置数字相加1.5 代码实现 二、使用步骤1.引入库2.读入数据 前言 链表是一种物理内存非连续存储&#xff0c;非顺序的线性数…

golang 和java对比的优劣势

Golang&#xff08;或称Go&#xff09;和Java都是非常流行的编程语言&#xff0c;被广泛应用于各种领域的软件开发。尽管它们都是高级编程语言&#xff0c;但它们具有许多不同的特性和适用场景。本文将重点比较Golang和Java&#xff0c;探讨它们的优势和劣势。 性能方面&#…

Django之关系模型的序列化

一、关系模型的序列化-多查1 1.1、模型准备 from django.db import models# Create your models here. class Classes(models.Model):name = models.CharField(max_length=20, verbose_name=班级)class Student(models.Model):SEX_CHOICES = ((1,男)), (2, 女)name = models.C…

Python:百度AI开放平台——OCR图像文字识别应用

一、注册百度AI开放平台 使用百度AI服务的步骤为&#xff1a; 注册&#xff1a;注册成为百度AI开放平台开发者&#xff1b;创建AI应用&#xff1a;在百度API开放平台上创建相关类型的的AI应用&#xff0c;获得AppID、API Key和Secret Key&#xff1b;调用API&#xff1a;调用…

Mac删除软件,动一动手指,几秒就彻底删除 mac删除软件删不掉的解决方法 mac删除软件后怎么删除软件数据

当你入职新公司&#xff0c;接手前任员工使用的Mac电脑时&#xff0c;很可能会遇到一个非常普遍的问题&#xff1a;电脑中装有大量你不需要的软件。这些软件不仅占用宝贵的硬盘空间&#xff0c;还可能影响电脑的运行速度和效率。为了获得一个干净、清爽的使用体验&#xff0c;删…