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. 基本函…...
“八股文”在实际工作中是助力、阻力还是空谈?
前言:在当今快速发展的技术时代,程序员的角色变得日益重要。随着技术的不断进步,招聘流程也在不断演变以适应新的需求。在程序员的招聘过程中,“八股文”作为一种面试现象,已成为不可忽视的一部分。所谓“八股文”&…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...