vector(二)vector模拟实现
vector成员变量是三个迭代器 vector的迭代器底层与string相同是使用 指针实现的 使用的是类模版T*指针
template<class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;
private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;
这里使用的是模版类 同时三个私有成员都是由指针的迭代器创建的
其中 _start是指向容器第一个元素位置 _finish是指向容器有效元素的最后一个位置的下一位
_end_of_storage 是指向容器整个空间的最后一个位置
vector<int>v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
在没写构造函数时会自动生成默认的构造函数
void push_back(const T& x){if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;}
push_back尾插 首先是判断是否扩容 之后将要插入的参数放在_finish的位置 之后++_finish 完成尾插 这里的扩容需要reserve函数
void reserve(size_t n)
{if (n > capacity()){size_t oldsize = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * oldsize);//对任意类型进行的都是浅拷贝for (size_t i = 0; i < oldsize; i++)tmp[i] = _start[i];//这里的赋值使用的是string的赋值//std::swap(tmp[i], start[i]);delete[] _start;}_start = tmp;_finish = tmp + oldsize;_end_of_storage = _start + n;}}
这里的size使用来查看容器中有效数据长度的
size_t capacity()
{return _end_of_storage - _start;
}size_t size()
{return _finish - _start;
}
首先必须先拿到size有效长度 并且赋值给一个变量 这样就可以防止后面size()出错
如果直接使用size()函数进行查找 那么在后面当_start重新指向新空间时 这是_finish 和_end_of_storage 还是指向旧的空间 这是通过size和capacity函数进行访问时 就会发生错误
所以要使用变量对size进行赋值存储 防止之后出错
对于vector容器进行遍历需要用到 []重载 以及迭代器begin和end
iterator begin()
{return _start;
}iterator end()
{return _finish;
}
_start和_finish所指向的位置与begin和end相同所以直接返回这两个指针就好
T& operator[](size_t i)
{assert(i < size());return _start[i];
}
[]返回的是对应下标的值 这里的访问不仅可以查看 还可以修改当前位置的值
在写好迭代器和[]重载后即可利用三种方式对vector容器进行遍历
void test1()
{vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (size_t i = 0; i < v1.size(); i++)//.h文件在运行时不会被编译而是在预处理阶段就会被展开(规则一){//规则二 编译器只会向上查找cout << v1[i] << " ";}cout << endl;vector<int>::iterator it = v1.begin();//这是使用int*作为类型也是可以通过的 因为这里的本质就是int* 但是这里最好是写成标准形式 方便调用其他命名空间和库时可以直接使用while (it != v1.end()){cout << *it << " ";it++;}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;
}
接下来模拟对容器插入和删除元素 insert和erase
iterator insert(iterator pos, const T& x)//出现随机值问题 扩容会导致pos迭代器失效(野指针)
{//扩容后续重新计算assert(pos >= _start);assert(pos < _finish);if (_finish == _end_of_storage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}
这里首先通过assert对pos进行检查是否合法 之后就要检查空间是否足够 空间不够则会先进行扩容 之后就是挪动数据 为插入的元素腾出空间 之后在pos位置插入对应元素 完成插入
这里在测试时可能会出现随机值问题 这是因为出现的pos迭代器失效的情况 原理是pos迭代器指向原本的空间 但是如果发生扩容 就会深拷贝 导致pos迭代器还是指向已经消失的空间 没有指向对应的新空间的对应位置上 解决方法 就是使用len记录pos在空间上距离_start的距离 之后在扩容结束后 使用新的_start对pos迭代器进行更新 这样就能 保证迭代器在函数体内正常使用
由于我们的参数是传值的 也就是说我们函数体外面的调用的这个pos迭代器也会失效 正常情况下是不能使用 如果非要使用 可以更改insert的返回类型 返回更新后的pos迭代器 对外面的pos进行一次更新 保证其可以使用
iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;it++;}--_finish;return pos;}
erase是删除pos位置的元素并且重新分配空间 首先通过assert来对pos迭代器进行是否合法检查 之后便是挪动数据 更新_finish完成删除 这里的pos也是有可能会发生失效的 如果刚好删除的是最后一个队尾元素 那么在删除完成后迭代器会处于失效 而在一些比遍历情况下 删除即使处于队中的迭代器也会发生失效
void test2(){bit::vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 0);for (auto e : v1){cout << e << " ";}cout << endl;v1.erase(v1.begin());for (auto e : v1){cout << e << " ";}cout << endl;v1.insert(v1.begin() + 2, 50);for (auto e : v1){cout << e << " ";}cout << endl;//只有string提供了find 而vector list等都是没有直接提供find 但是他们可以使用算法algorithm库中的find函数(复用)}
void test3()
{bit::vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);int x;cin >> x;//如果可以找到x 那么就在x的位置前面插入 如果找不到 那就不插入bit::vector<int>::iterator pos = find(v1.begin(), v1.end(), x);//insert以后这个实参会不会失效if (pos != v1.end())//也是会发生失效的 即使函数内部已经预防进行调整 但是形参改变 不会影响实参 依旧是扩容就会失效{//因为不知道什么时候扩容什么时候失效 所以迭代器的建议是 不要使用可能会失效的迭代器 也不能在参数上加引用 因为begin返回的是传值返回 会产生拷贝后临时变量 具有常性 此时引用会导致权限的放大pos = v1.insert(pos, 1000);//通过函数返回值 来更新一下pos迭代器防止失效 也就是建议失效迭代器不要访问 除非更新一下它}for (auto e : v1){cout << e << " ";}cout << endl;}
void test4()
{vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);int x;cin >> x;vector<int>::iterator pos = find(v1.begin(), v1.end(), x);if (pos != v1.end()){pos = v1.erase(pos);//可能是由删除后缩容引起的迭代器失效//也有可能在删除最后一个成员会导致非法访问if (pos != v1.end())//这里为了访问到队尾的最后一个元素cout << *pos << endl;//这里erase也是通过返回值来更新迭代器从而保证迭代器不会失效}cout << typeid(pos).name() << endl;//不论是在函数执行中形参失效 还是函数执行完成后实参都会失效 这里的vs做的比较绝 在执行完成后会统一将迭代器的类型做出修改 一旦访问这个类型的迭代器就会报错警告for (auto e : v1){cout << e << " ";}cout << endl;
}
void test5()
{vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);//如果最后一个是偶数 需要删除的也是可以的 在删除后 pos指向了_finish while循环检查之后就会直接结束了vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it);//每次删除后都会导致当前迭代器的失效 需要进行手动更新}else{it++;//不要存侥幸 如果偶尔在一个编译器上能跑 你要考虑这串代码是否在其他编译器上都能跑}}for (auto e : v1){cout << e << " ";}cout << endl;
}
vector() = default;//这里可以强制生成默认构造;vector(const vector<T>& v)
{for (auto e : v){push_back(e);}
}
这里是拷贝构造 通过遍历方式依次将内容拷贝给this指向的容器
在写完拷贝构造后 由于拷贝构造也是构造的一种 所以会被当做默认构造 所以通过default强行生成默认构造
void test6(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);vector<int> v2(v1);//如果不写 生成的是默认的浅拷贝 值拷贝就会导致这里析构两次的问题for (auto e : v2)//没有写 会自动构造 使用的是缺省值 但是没有写构造 写了拷贝构造 构造的规则是 拷贝构造也算构造 这时我们需要自己去写构造了{cout << e << " ";}cout << endl;vector<int>v3;v3.push_back(10);v3.push_back(20);v3.push_back(30);vector<int>v4;v4 = v3;for (auto e : v4){cout << e << " ";}cout << endl;}
//迭代器区间初始化
template <class InputIterator>//一个类模版的成员函数还可以写成函数模版
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
通过这个构造可以直接通过迭代器选择范围进行拷贝
void test7(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);vector<int> v2(v1.begin(),v1.end());//这是在没写迭代器区间初始化也能做到的for (auto e : v2){cout << e << " ";}cout << endl;vector<int> v3(v1.begin()+2, v1.end());//这是只有迭代器区间初始化后才能做到的for (auto e : v3){cout << e << " ";}cout << endl;string s1("hello");vector<int> v4(s1.begin() +1, s1.end());//char类型转换成整型后使用的是ascll码值for (auto e : v4)//这里因为是模版 谁的迭代器都可以使用{cout << e << " ";}cout << endl;}
vector(size_t n, const T& val = T())//这个地方由于是T模版 所以不能用0去初始化 这里是匿名对象
{//内置类型没有默认构造 这里是默认c++进行了升级reserve(n);for (size_t i = 0;i <n;i++){push_back(val);}
}vector(int n, const T& val = T())//这个地方由于是T模版 所以不能用0去初始化 这里是匿名对象
{//内置类型没有默认构造 这里是默认c++进行了升级reserve(n);for (int i = 0; i < n; i++){push_back(val);}
}
void test8()
{int i = 0;int j(1);int k = int();int x = int(2);string s("hello");vector<string> v1(10);//不给第二个参数 走的是缺省值vector<string> v2(10, "xxx");//这里是隐式类型转换vector<string>v3(10,s);for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;for (auto e : v3){cout << e << " ";}cout << endl;vector<int>v4(10, 1);//这里会调用错误的迭代器区间函数 编译器会认为那个编译器更加匹配for (auto e : v4)//也就是上述两个构造同时存在导致的{//解决问题 在输入是手动改为uint//第二就是写一个重载的版本cout << e << " ";}cout << endl;}
vector(initializer_list<int> e2)
{reserve(e2.size());for (auto e : e2){push_back(e);}}
void test9()
{vector<int>v2 ({ 1,2,3,4,5,6,7,8,9 });//这里参数数量不固定 即便是上面的也只能写一个或者两个 不能多个,直接构造for (auto e : v2){cout << e << " ";}cout << endl;vector<int>v1 = {1,2,3,4,5,6,7,8,9};//隐式类型转换for (auto e : v1){cout << e << " ";}cout << endl;auto e = { 1,2,3 };initializer_list<int> e2 = {1,2,3,5,7,8,9};//这两者是等价的
}
在c++11中添加一些关于隐式类型转化的新形式
在之前的隐式类型转换中最多只有2个进行隐式类型转换 在使用上述的构造后可以进行多个数据进行隐式类型转换
void test10()
{vector<string>v1;v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");for (auto e : v1){cout << e << " ";}cout << endl;
}
这里连续超过四个尾插会造成崩溃 原因是第五个插入是会进行扩容 而扩容使进行拷贝时如果使用的memcpy 由于memcpy对任何都是浅拷贝 之后删除_start会导致tmp指向的同一片空间被删除 这样就会导致崩溃 所以时候用swap或者赋值对内容进行拷贝
相关文章:

vector(二)vector模拟实现
vector成员变量是三个迭代器 vector的迭代器底层与string相同是使用 指针实现的 使用的是类模版T*指针 template<class T> class vector { public:typedef T* iterator;typedef const T* const_iterator; private:iterator _start nullptr;iterator _finish nullp…...

【Canvas与电脑桌面】用六角回旋镖铺满一个平面(1920*1080)
【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>六角回旋镖桌面1920x1080</title><style type"text/cs…...

创游系列开心娱乐完整组件
别人分享的一套东西,是个不错的娱乐源码,里面包含了很多小游戏。可以创建房间。 没测试自行研究吧,内含搭建教程。 代码免费下载:百度网盘...

高效驱动之选 ——KP85211ASGA 半桥栅极驱动器 内置互锁死区
KP85211A是一款 225V 耐压,具有 1A 拉电流和 1.5A 灌电流能力的半桥栅极驱动器,专用于驱动功率MOSFET或IGBT。采用高压器件工艺技术,具有良好的电流输出及出色的抗瞬态干扰能力。可保证开关节点 VS 瞬态 -7V 情况下系统正常工作。可支持开关节…...
建投数据获批安全生产许可证
9月1日,建投数据成功获批由北京市住房和城乡建设委员会核发的《安全生产许可证》。该资质的获得,是建投数据能力与实力的展现,更是对其企业规模、管理水平、项目业绩等的肯定。 《安全生产许可证》是国家为了加强安全生产监督管理,…...

MCU9.reg52.h的介绍
1.引用头文件的两种方式 #include <reg52.h> #include "reg52.h" 区别:优先搜索的位置不同! 在keil软件中 #include <reg52.h> 优先搜索软件安装的INC文件夹 #include "reg52.h" 优先搜索当前工程文件夹下的头文件,如果没有,则在软件安装的…...
Python知识点:如何使用Python进行二维码生成与识别
在Python中,生成和识别二维码可以使用不同的库来实现。最常用的库包括 qrcode 和 pyzbar。以下是如何使用这些库来生成和识别二维码的示例: 1. 生成二维码 你可以使用 qrcode 库来生成二维码。首先,你需要安装它: pip install …...

跨域问题(CORS)
文章目录 介绍解决一、添加跨域头,允许跨域1.后端配置CORS策略(4种方法)2.配置nginx 二、代理 介绍 跨域资源共享(CORS, Cross-Origin Resource Sharing)是浏览器的一个安全机制,用来防止来自一个域的网页对另一个域下的资源进行…...

评测AI写毕业论文软件排行榜前十名的网站
在当今信息爆炸的时代,AI智能写作工具已经成为我们写作过程中的得力助手。特别是对于学术论文的撰写,这些工具不仅能够提高写作效率,还能帮助用户生成高质量的文稿。以下是五款值得推荐的AI智能写论文软件,其中特别推荐千笔-AIPas…...
发邮件格式
邮件作为一种正式的沟通方式,其格式通常需要遵循一定的规范。 尊敬的xx:(无缩进) 您好!(缩进两个字符) 正文(缩进两个字符)xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xxx正文xx…...

解锁Web3.0——Scaffold-eth打造以太坊DApp的终极指南
🚀本系列文章为个人学习笔记,目的是巩固知识并记录我的学习过程及理解。文笔和排版可能拙劣,望见谅。 目录 前言 一、快速部署 1、前期准备: 2、安装项目: 二、配置部署运行环境 1、初始化本地链:…...

机器学习之监督学习(四)决策树和随机森林
机器学习之监督学习(四)决策树和随机森林 0. 文章传送1. 决策树 Decision Tree案例引入构建过程 2. 随机森林 Random Forest3. 决策树 vs 神经网络4. 代码实现手写版本sklearn版本 5. 案例Iris数据集介绍实验代码 0. 文章传送 机器学习之监督学习&#…...

Sky Takeaway
软件开发整体介绍 软件开发流程 角色分工 软件环境 苍穹外卖 项目介绍 定位:专门为餐饮企业定制的一款软件产品 技术选型 前端环境搭建 阅读readme文档 nginx.exe放入无中文目录运行并启动 后端环境搭建 项目结构 Nginx反向代理 优点 配置 Nginx反向代理 负…...
JavaScript 模板字符串
模板字符串(Template Literals)是 JavaScript ES6 引入的一项功能,它让字符串的处理变得更加灵活和直观。以下是对模板字符串的详细介绍,包括它的基本特性、用法以及一些高级用法。 一 基本特性 1. 多行字符串 模板字符串允许创…...
模拟new关键字时产生的问题,求解答!
目的:编写函数myNew来模拟new关键字 首先,我们知道new关键字的工作: 1.产生一个新对象 2.将新对象的__proto__属性指向构造函数的prototype属性 3.将新对象赋值给构造函数的this 4.执行构造函数中的代码 函数实现如下: fun…...

SpringBoot2:请求处理原理分析-接口参数解析原理(argumentResolvers)
一、知识回顾 我们知道,接口的参数,一般都要配上注解来一起使用。 不同的参数注解,决定了传参的方式不同。 为什么会这样? 如果让你设计接口参数解析,你会怎么做? 首先,我们知道方法参数是形…...

java实现文本相似度计算
需求 **文本推荐:**有多个文本字符串,如何设计一个简单的统计方法(从词频的角度设计),来计算出多个文本字符串两两之间的相似度,并输出大于指定相似度阈值的文本 分析理解 使用Java实现文本相似度计算的…...

基于无人机边沿相关 ------- IBUS、SBUS协议和PPM信号
文章目录 一、IBUS协议二、SBUS协议三、PPM信号 一、IBUS协议 IBUS(Intelligent Bus)是一种用于电子设备之间通信的协议,采用串行通信方式,允许多设备通过单一数据线通信,较低延迟,支持多主机和从机结构&a…...

django学习入门系列之第十点《A 案例: 员工管理系统4》
文章目录 6 部门管理(原始方式)6.6 添加界面的导入(数据库)6.7 删除按键的应用6.8 编辑按键的应用6.81 传值的另一种方式 6.9 提交按键的应用 往期回顾 6 部门管理(原始方式) 6.6 添加界面的导入ÿ…...

【2024】Math-Shepherd:无需人工注释即可逐步验证和强化法学硕士。
搜索词: Math-shepherd: Verify and reinforce llms step-by-step without human annotations P Wang, L Li, Z Shao, R Xu, D Dai, Y Li, D Chen, Y Wu, Z Sui Proceedings of the 62nd Annual Meeting of the Association for …, 2024•aclanthology.org 摘要…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...

Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
DiscuzX3.5发帖json api
参考文章:PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下,适配我自己的需求 有一个站点存在多个采集站,我想通过主站拿标题,采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...