vector深度剖析及模拟实现
目录
- 前言
- vector核心框架模拟实现
- 1. 前期准备
- 2. 构造和销毁
- 补充: 隐式类型转换和多参数构造的区别
- 3. 迭代器相关
- 4. 容器相关
- 补充: memcpy拷贝问题
- 5. 元素访问
- 6. vector的修改
- 测试代码
- 总结
前言
本文重点模拟实现vector的核心接口, 帮助我们更好的理解底层逻辑, 以及对vector的深度剖析.
博客主页: 酷酷学!!! 期待关注~
正文开始


vector核心框架模拟实现
1. 前期准备
首先, 可以查看到STL源码, 底层vector的实现并不是我们通常顺序表那样定义成员变量, 而是通过迭代器也就是指针进行实现, 那么我们也按照STL来进行模拟实现.
T* _a;
size_t _size;
size_t _capacity;

#pragma once#include<iostream>
#include<assert.h>
using namespace std;namespace my
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//核心接口的实现private:iterator _start; //指向数据块开始iterator _finish; //指向有效数据的尾iterator _endOfStorage; //指向存储容量的尾};
}
2. 构造和销毁

- 默认无参构造
//默认无参构造vector():_start(nullptr), _finish(nullptr), _endOfStorage(nullptr){}
默认的无参构造, 因为在声明的时候我们并没有给缺省值, 所以我们也可以直接在初始化列表进行初始化.
- 迭代器区间初始化
//迭代器区间初始化//若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)//只能是vector的迭代器//重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}
- 初始化n个相同的值
//n个相同的值,使用默认的构造函数进行初始化, //对于内置类型,C++对这方面也进行了支持//vector(size_ n, const T& value = 0)//这里缺省值不能给0,如果对于自定义类型就有问题了vector(size_t n, const T& value = T())//匿名对象//相当于缺省值给了一个临时的匿名对象:_start(nullptr), _finish(nullptr), _endOfStorage(nullptr){reserve(n);while (n--){push_back(value);}}/** 理论上讲, 提供了vector(size_t n,const T& value = T())之后,* vector(int n,const T& value = T())就不需要提供了,但是对于:* vector<int> v(10,5);* 编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型* 就不会走vector(size_t n,const T& value = T())这个构造方法* 最终选择的是:vector(InputIterator first, InputIterator last)* 因为编译器觉得区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int* 但是10和5根本不是一个区间,编译时就报错了* 故需要增加该构造方法*/vector(int n, const T& value = T()): _start(new T[n]), _finish(_start + n), _endOfStorage(_finish){for (int i = 0; i < n; ++i){_start[i] = value;}}
这里可能不太理解为什么 const T& value = T() 这个缺省值要给T(), 要给默认构造函数, C++对于内置类型也进行了升级, 内置类型也可以使用构造初始化, 所以这个值, 不管自定义类型还是内置类型都可以适用
// C++内置类型进行了升级,也有构造int i = 0;int j(1);int k = int();int x = int(2)

有时候, 我们会发现有些代码使用vector是这样写的
vector<int> v{ 1, 2, 3, 4 };
查看C++文档, 可以发现这是C++11的新语法让vector用起来更加方便, 使用<initializer_list>这个类进行初始化.

initializer_list中有两个成员变量. begin指针和end指针, 记录了列表的开始位置和结尾位置, 记录列表这段区间, 进行初始化.

- initializer_list进行初始化
vector(initializer_list<T> il):_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){reserve(il.size());for (auto e : il){push_back(e);}}
首先扩容, 然后遍历il, 将里面的值尾插到vector.
- 拷贝构造
//拷贝构造vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){reserve(v.capacity());iterator it = begin();const_iterator vit = v.cbegin();while (vit != v.cend()){*it++ = *vit++;}_finish = it;}//vector(const vector<T>& v)// :_start(nullptr)// ,_finish(nullptr)// ,_endOfStorage(nullptr)//{// reverse(v.capacity());// for (auto e : v)// {// push_back(e);// }//}
- 赋值运算符重载
void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);}//赋值运算符重载// v1 = v3vector<T>& operator=(vector<T> v){swap(v);return *this;}
- 析构函数
//析构函数~vector(){if (_start){delete[] _start;_start = _finish = _endOfStorage = nullptr;}}
补充: 隐式类型转换和多参数构造的区别
class A{public:A(int a1 = 0):_a1(a1), _a2(0){}A(int a1, int a2):_a1(a1),_a2(a2){}private:int _a1;int _a2;};void test()
{//单参数构造和多参数对象隐式类型转换A aa1(1); //这里是单参数构造A aa2 = 1; //这里是隐式类型转换A aa3(1,1); //这里是多参数构造A aa4 = {1,1}; //这里是多参数隐式类型转换A aa5{1,1}; //这里是多参数隐式类型转换省略=, 一般不要这种写法A aa6{1};A aa7 = {1}; //这两个是C++为了想让{}进行统一, 所以单参数也可以使用{}进行构造, 比较冗余,一般不要这样写/////自定义类型动态开辟调用构造函数A* p1 = new A;//无参构造A* p2 = new A(2); //单参数传参构造A* p3 = new A(2,3); //多参数传参构造A* p1 = newA[10]; //连续申请10个空间,无参构造A aa1(1);A aa2(2);A aa3(3);A *p2 = new A[10]{aa1,aa2,aa3};//拷贝构造,其余为0A* p3 = new A[10]{1,2,3,4,{6,7}};//也可以直接写,进行隐式类型转换///这里的隐式类型转换,跟上面不一样,这里参数个数不固定vector<int> v1({ 1,2,3,4,5,6 });vector<int> v2 = { 10, 20, 30};//()可以省略for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;vector<A> v3 = { 1, A(1), A(2,2), A{1}, A{2,2}, {1}, {2,2} };//这个是首先创建一个存储A的列表然后进行构造}
3. 迭代器相关
这里分别对应静态和非静态vector
iterator begin(){return _start;}iterator end(){return _finish;}const_iterator cbegin() const{return _start;}const_iterator cend() const{return _finish;}
4. 容器相关
- size和capacity
size_t size() const{return _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}
- 修改空间容量, 默认增容
void reserve(size_t n){if (n > capacity()){size_t oldSize = size();//1.开辟新的空间T* tmp = new T[n];//2.拷贝元素/*if (_start){memcpy(tmp, _start, sizeof(T) * size);}*///不可以用memcpy拷贝if (_start){for (size_t i = 0; i < oldSize; ++i){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + oldSize;_endOfStorage = _start + n;}}
补充: memcpy拷贝问题
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中.
- 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。(比如拷贝int类型既高效又不会出错, 那如果vector里面存储的都是一个个的指针类型呢? 比如string类)
比如下面这段代码
vector<string> v1;v1.push_back("111111111111111111");v1.push_back("111111111111111111");v1.push_back("111111111111111111");v1.push_back("111111111111111111");v1.push_back("111111111111111111");for (auto e : v1){cout << e << " ";}cout << endl;
当插入第五个值的时候, vector需要进行扩容, memcpy会继续拷贝, 但这是浅拷贝, 将_start里面的值拷贝到tmp中, 此时tmp中成员_str也指向了原来的那段空间, 当_start释放后, 区间就会被销毁, 此时tmp里面的内容就指向了野指针.

而这里使用赋值拷贝, 会调用我们的赋值拷贝函数, 就不会出现浅拷贝的问题了.

结论: 如果对象中涉及到资源管理时, 千万不能使用memcpy进行对象之间的拷贝, 因为memcpy是浅拷贝, 否则可能会引起内存泄漏甚至程序崩溃
- rsize()函数
void resize(size_t n, const T& value = T()){//1.如果n小于当前的size,则数据缩小到nif (n <= size()){_finish = _start + n;return;}//2.空间不够则增容if (n > capacity())reserve(n);//3.将size扩大到n,并使用value填充后面的值iterator it = _finish;_finish = _start + n;while (it != _finish){*it = value;++it;}}
5. 元素访问
T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}T& front(){return *_start;}const T& front() const{return *_start;}T& back(){return *(_finish - 1);}const T& back() const{return *(_finish - 1);}
6. vector的修改
void push_back(const T& x){insert(end(), x);}void pop_back(){erase(end() - 1);}iterator insert(iterator pos, const T& x){assert(pos <= _finish);assert(pos >= _start);//空间不够先进性增容if (_finish == _endOfStorage){size_t newcapacity = (capacity() == 0) ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + size();}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}//返回删除数据的下一个数据,//方便解决迭代器失效的问题iterator erase(iterator pos){iterator begin = pos + 1;while (begin != _finish){*(begin - 1) = *begin;++begin;}--_finish;return pos;}
测试代码
void TestVector1()
{my::vector<int> v1;my::vector<int> v2(10, 5);int array[] = { 1,2,3,4,5 };my::vector<int> v3(array, array + sizeof(array) / sizeof(array[0]));my::vector<int> v4(v3);for (size_t i = 0; i < v2.size(); i++){cout << v2[i] << " ";}cout << endl;
}void TestVector2()
{my::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << v.size() << endl;cout << v.capacity() << endl;cout << v.front() << endl;cout << v.back() << endl;cout << v[0] << endl;for (auto e : v){cout << e << " ";}cout << endl;v.pop_back();v.pop_back();for (auto e : v){cout << e << " ";}cout << endl;v.insert(v.begin(), 0);for (auto e : v){cout << e<<" ";}cout << endl;v.erase(v.begin() + 1);for (auto e : v){cout << e << " ";}cout << endl;
}


总结
本篇对vector的核心接口进行了模拟实现和探究, C++这门语言本身就比较偏向底层, 希望能够帮助大家进一步理解底层逻辑以及实现思路, 感谢三连!!!
完
相关文章:
vector深度剖析及模拟实现
目录 前言vector核心框架模拟实现1. 前期准备2. 构造和销毁补充: 隐式类型转换和多参数构造的区别 3. 迭代器相关4. 容器相关补充: memcpy拷贝问题 5. 元素访问6. vector的修改测试代码 总结 前言 本文重点模拟实现vector的核心接口, 帮助我们更好的理解底层逻辑, 以及对vecto…...
spring 中包自动扫描之 component-scan 解析
在 spring 中,为简化 bean 的配置,在 spring-context 模块下提供了包的自动扫描功能,将配置的包及其子包下的所有符合条件的类都注册到 BeanFactory 中。下面来看下具体是怎么实现的。 配置 <context:component-scan base-package"…...
【C语言】Linux 飞翔的小鸟
【C语言】Linux 飞翔的小鸟 零、环境部署 安装Ncurses库 sudo apt-get install libncurses5-dev壹、编写代码 代码如下: bird.c #include<stdio.h> #include<time.h> #include<stdlib.h> #include<signal.h> #include<curses.h>…...
mcasttest-tool组播检测工具
作者:广大 检测组播 mcasttest-tool是oracle组播检测工具,组播是oracle 11.2.0.2开始的新功能。 1、上传mcasttest工具解压并授权 [rootrac1 soft]# cd /u01/soft/ [rootrac1 soft]# tar -xvf mcasttest.tgz[rootrac1 soft]# chown -R grid:oinstall…...
ncnn 库编译的一些问题,使用交叉编译
一开始的问题是编译完程序,但是部分工具没有编译出来。 主要的问题是: 1. ncnn2in8 程序没有编译出来:主要原因应该是cmakelists.txt文件中对于的模块没打开on,或者这个模块没加进去编译: 添加以下 -DNCNN_BUILD_EXAMPLESON -…...
Python基础教程(一)
1.编程基础 1.1标识符 标识符是变量、函数、模块和其他对象的名称。Python中标识符的命名不是随意的,而是要遵守一定的命名规则,比如说: 1、标识符是由字母 (A~Z 和 a~z) 、下划线和数字组成,但第一个字符不 能是数字。 2、标识符不…...
基于C51和OLED12864实现贪吃蛇小游戏
引言 在微电子技术飞速发展的今天,单片机作为智能控制的核心,广泛应用于各种电子设备中。C51系列单片机以其高效、稳定的特性,成为众多电子爱好者和工程师的首选平台。而OLED显示屏以其轻薄、低功耗、响应速度快等优点,在显示设备…...
JVM性能调优全指南:高流量电商系统的最佳实践
1.G1(Garbage-First) 官网: G1 Garbage Collection G1收集器是Java 7中引入的垃圾收集器,用于替代CMS(Concurrent Mark-Sweep)收集器。它主要针对大内存、多核CPU环境下的应用场景,具有以下特点: 分代收集:G1仍然保留了分代的概念,但新生代和老年代不再是物理隔离的,…...
前端常见场景、JS计算精度丢失问题(Decimal.js 介绍)
目录 一. Decimal.js 介绍 二. 常用方法 1. 创建 Decimal 实例 2.加法 add 或 plus 3.减法 sub 或 minus 4.乘法 times 或 mul 5.除法 div 或 dividedBy 6.取模 7.幂运算 8.平方根 9.保留小数位 toFixed方法(四舍五入) 三.项目应用 前端精度丢失问题通常由以下原因…...
Python写UI自动化--playwright(点击操作)
本篇介绍playwright点击操作,click()方法的常用参数 目录 0. selector (必需) 1. modifiers(可选) 2. position(可选) 3. button(可选) 4. click_count(可选) 5. delay 6. timeout(可选) 7. forceTrue(可选) 8. trialTrue(可选) 9. no_wait_after(可选) …...
[C#面对对象] 之抽象方法 虚方法 接口
1.虚方法 我的理解 "法国的“巴黎公社”,俄国的“十月革命”,都是把主要战略方向首先夺取中心城市 " 设计为 一个父类中的虚方法(virtual),这个虚方法已经有实现了(就是通过暴力革命夺取的方法 最终返回 城市)然而秋收暴动(子类)失败…...
docker 发布geoserver服务添加字体
1. 创建容器时可直接挂载到系统字体库 2. 已发布的容器挂载字体目录 关闭docker服务 : systemctl stop docker.socket 修改config.v2.json :位置在 cd /var/lib/docker/containers/容器id 重新启动docker服务:systemctl start docker...
数据赋能(162)——开发:数据整理——技术方法、主要工具
技术方法 从商业角度来看,从前未知的数据分析模式或趋势的发现为企业提供了非常有价值的洞察力。数据整理技术能够为企业对未来的发展具有一定的预见性。数据整理技术可以分成3类:群集、分类和预测。 群集技术: 这是一种将相似的数据项进行…...
安全服务面试
对安全服务是怎么理解的 安全服务对象是人, 渗透测试对象是网站。(我的理解) 安全概念和资讯 安全工具使用 渗透测试 安全基线检查 应急响应 代码审计 安全边界建设 安全规范 1.拿到一个待检测的站,你觉得应该先做什么&…...
昇思25天学习打卡营第23天|LSTM+CRF序列标注
Mindspore框架CRF条件随机场概率图模型实现文本序列命名实体标注|(一)序列标注与条件随机场的关系 Mindspore框架CRF条件随机场概率图模型实现文本序列命名实体标注|(二)CRF模型构建 Mindspore框架CRF条件随机场概率图模型实现文本…...
抖音直播弹幕数据逆向:websocket和JS注入
🔍 思路与步骤详解 🕵️♂️ 思路介绍 首先,我们通过抓包工具进入的直播间,捕获其网络通信数据,重点关注WebSocket连接。发现直播弹幕数据通过WebSocket传输,这种方式比传统的HTTP更适合实时数据的传输。…...
AIGC diffusers文生图模型optimum量化使用案例
参考: https://github.com/huggingface/blog/blob/main/quanto-diffusers.md 安装 pip install optimum-quanto %pip install optimum使用 from optimum.quanto import freeze, qfloat8, quantize from diffusers import PixArtSigmaPipeline import torchpipeline = PixArt…...
PDF怎么转换成Word?这些工具一键搞定!
在日常生活中,我们经常遇到需要将PDF文件转换成Word文档的情况。PDF怎么转换成Word?一些工具的使用十分重要!下文中就为大家推荐几个亲测好用的PDF转换工具。 一、Foxit PDF转换大师(365客户端) 链接:www…...
【TS】TypeScript函数类型:提升函数的类型安全性和可读性
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 TypeScript函数类型:提升函数的类型安全性和可读性1. 引言2. 基本函…...
“八股文”在实际工作中是助力、阻力还是空谈?
前言:在当今快速发展的技术时代,程序员的角色变得日益重要。随着技术的不断进步,招聘流程也在不断演变以适应新的需求。在程序员的招聘过程中,“八股文”作为一种面试现象,已成为不可忽视的一部分。所谓“八股文”&…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
