【C++】学习笔记——vector_3

文章目录

  • 七、vector
    • 3. vector的模拟实现
    • 4. vector实现代码整合
  • 未完待续


七、vector

3. vector的模拟实现

上篇文章我们讲解了非常 玄幻 的拷贝构造函数,同样的方法,我们也能用这种方法来实现 赋值重载函数

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}vector<T>& operator=(vector<T> v)
{// 通过传参时的拷贝构造,直接与其交换swap(v);return *this;
}

传值传参会调用拷贝构造,所以我们只需要将其与我们要赋值的给交换即可。
我们来测试一下:

// 测试区
void test06()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);vector<int> v1 = v;for (auto e : v1){std::cout << e << " ";}std::cout << std::endl;
}
// test.cpp
#include<iostream>
#include"vector.h"
using namespace std;int main()
{my::test06();return 0;
}

在这里插入图片描述

接下来实现一个比较奇怪的东西:迭代器区间构造 。一般容器都会支持这个函数。

// 类模板的成员函数可以是函数模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{//
}

是的,类模板里面又加了一个函数模板,这样的作用是什么呢?这样的作用就是,这个函数模板可以不局限于类内的迭代器,只要符合模板参数,其他类的迭代器同样可以调用该函数 。这样可以使容器与容器之间发生交互。原理就是这样,实现起来也很简单:

// 类模板的成员函数可以是函数模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

我们来测试一下:

// 测试区
void test07()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);// 这里也可以使用其他容器的迭代器vector<int> v1(v.begin() + 1, v.end() - 1);for (auto e : v1){std::cout << e << " ";}std::cout << std::endl;
}
// test.cpp
#include<iostream>
#include"vector.h"
using namespace std;int main()
{my::test07();return 0;
}

在这里插入图片描述
所以说这个迭代器区间构造也是非常好用的。

还有这个构造函数:构造 n 个给定值

// 和resize差不多
vector(size_t n, const T& val = T())
{reserve(n);for (size_t = 0; i < n; ++i){push_back(val);}
}

我们来测试一下:

void test08()
{// 初始化 10 个 1vector<int> v(10, 1);for (auto e : v){std::cout << e << " ";}std::cout << std::endl;
}

在这里插入图片描述
啊?哪出问题了?看看错误列表:
在这里插入图片描述
通过错误行数,我们找到了:
在这里插入图片描述

啊?咋调用到这来了,误把我的参数看成迭代器了?该怎么办呢?其实是这样的:我们实现的 构造n个给定值 的构造函数里,第一个参数是 size_t ,而在迭代器区间构造函数里,两个参数是相同的 ,这样会导致,我构造 10 个 1 所给的参数更加符合 迭代器区间构造函数 ,因为对另一个来说,有一个 size_t 参数不匹配,所以优先调用迭代器区间构造函数了。所以怎么办?简简单单:size_t 换成 int 就行 ,虽然对于个数来说, size_t 更加符合,毕竟没有负数,但是 int 也不影响。

vector(int n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}
}

在这里插入图片描述
OK,完美解决。
我来给出一个情景:

void test09()
{vector<std::string> v;v.push_back("11111");v.push_back("22222");v.push_back("33333");for (auto e : v){std::cout << e << " ";}std::cout << std::endl;
}

这段程序的结果是什么?
在这里插入图片描述
没错,是不是非常简单?再来看看:

void test09()
{vector<std::string> v;v.push_back("11111");v.push_back("22222");v.push_back("33333");v.push_back("44444");v.push_back("55555");for (auto e : v){std::cout << e << " ";}std::cout << std::endl;
}

这样呢?
在这里插入图片描述

在这里插入图片描述
蛤,报错了。打印倒是打印出来了,那么问题肯定不是出在打印上面,那就是析构。析构出了问题,我们首先想到的就是深浅拷贝,我们所有的函数都避免了浅拷贝,为什么最后还是会出现析构问题?
在这里插入图片描述
其实问题出在 reserve 函数里的 memcpy 。memcpy其实是浅拷贝,按照字节一个一个拷贝。就出现了如下图的情况。

在这里插入图片描述
这样导致我们在析构 _start 时,将内容也给析构了,所以后面生命周期结束时的析构就出现了多重析构的问题。既然知道了问题所在,那该如何解决问题呢?

void reserve(size_t n)
{// 比当前容量大才允许扩容if (n > capacity()){size_t old_size = size();// 开空间T* tmp = new T[n];// for循环赋值for (size_t i = 0; i < old_size; ++i){tmp[i] = _start[i];}// 释放旧空间delete[] _start;// 更改地址_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}
}

换成 for循环赋值就可以了
在这里插入图片描述
最后再给大家阐述一下 迭代器失效的问题。我们在 insert 中第一次遇到了迭代器失效的问题,但是我们通过更新地址的问题解决了迭代器失效的问题。

void insert(iterator pos, const T& val)
{assert(pos >= _start);assert(pos <= _finish);// 记录相对位置size_t len = pos - _start;if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}// 如果发生异地扩容,需要更新 迭代器 ,否则将会发生迭代器失效pos = _start + len;iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = val;++_finish;
}

但是,大家有没有想过,这里的迭代器传入的是临时变量,临时变量更新了后,实参并不会修改,所以在外面这个迭代器的值已经 不可信 了。有人说可以将这个迭代器使用 引用传参 ,这样也不可以!

v.insert(v.begin(), 1);

这里传参都是传的临时变量,然而 临时变量具有常性,加了引用就用不了了,除非再加 const ,加了 const 就更不行了,不能修改的迭代器算什么迭代器?所以这里没有一种好的解决办法,想告诉大家的是:迭代器在修改容器的内容后就已经不再可信了 ,若需要再次使用迭代器,重新创建一个即可。
我们知道了 insert 可能产生 迭代器失效,那么 erase 会产生吗?有小伙伴可能会说,erase 根本就不会扩容,没有空间移动,那就不会出现迭代器失效。我再给大家看看一个场景:我们来删除容器里的偶数

void test10()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}++it;}for (auto e : v){std::cout << e << " ";}std::cout << std::endl;
}

在这里插入图片描述
没出现问题,那这样呢:

void test10()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);// 这里有两个 4v.push_back(4);v.push_back(4);v.push_back(5);auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}++it;}for (auto e : v){std::cout << e << " ";}std::cout << std::endl;
}

在这里插入图片描述
?这怎么出问题了?

void test10()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);// 这样呢?v.push_back(4);auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}++it;}for (auto e : v){std::cout << e << " ";}std::cout << std::endl;
}

在这里插入图片描述
啊?这样程序还能挂掉?
聪明的小伙伴们可以想到,erase 函数删除后会将后面的数据前往移动,删除后迭代器的位置是一个新的数据,然后 ++it 就会错过元素,就会出现答案不对的情况,如果错过了 end 迭代器表示的位置,将会导致循环终止不了,所以引发了一系列问题。那么,该如何解决呢?

auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){v.erase(it);}else{++it;}
}

这样可以吗?说可以也可以,说不可也不可以。在一般情况下,erase 都不会产生 缩容 的情况,但是没有规定不可以缩容,假如真的有个容器 erase 后会发生缩容问题,那就会导致迭代器失效,就不能使用了。那么库里的 vector 是怎么解决这个问题的呢?我们来看看函数原型:
在这里插入图片描述
在这里插入图片描述
是的,库里 vectorerase 会返回一个迭代器,指向刚刚被删除的位置。所以库里的 erase 应该这样使用:

auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){// 更新迭代器it = v.erase(it);}else{++it;}
}

那我们的 erase 就要做出相应的修改:

iterator erase(iterator pos)
{assert(pos >= _start);// 不要等于 _finishassert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;
}

再来测试一下:
在这里插入图片描述ok,实现完毕。到这里我们的 vector 已经非常优秀了。

4. vector实现代码整合

vector.h 头文件:

#pragma once#include<assert.h>namespace my
{template<class T>class vector{public:typedef T* iterator;typedef T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}// 类模板的成员函数可以是函数模板template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(){}vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}vector(int n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> v){// 通过传参时的拷贝构造,直接与其交换swap(v);return *this;}~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}size_t size() const{// 数据个数return _finish - _start;}size_t capacity() const{// 容量大小return _endofstorage - _start;}// 下标 + [] 访问T& operator[](size_t pos){assert(pos < size());return _start[pos];}void reserve(size_t n){// 比当前容量大才允许扩容if (n > capacity()){size_t old_size = size();// 开空间T* tmp = new T[n];// for循环赋值for (size_t i = 0; i < old_size; ++i){tmp[i] = _start[i];}// 释放旧空间delete[] _start;// 更改地址_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}void resize(size_t n, const T& val = T()){// 插入数据if (n > size()){reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}// 删除数据else{_finish = _start + n;}}void push_back(const T& val){ 判断扩容//if (_finish == _endofstorage)//{//	reserve(capacity() == 0 ? 4 : 2 * capacity());//} 插入数据//*_finish = val;//++_finish;insert(end(), val);}// 尾删void pop_back(){//assert(!empty());////--_finish;erase(end() - 1);--_finish;}// 判断容器是否为空bool empty(){return _start == _finish;}void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);// 记录相对位置size_t len = pos - _start;if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}// 如果发生异地扩容,需要更新 迭代器 ,否则将会发生迭代器失效pos = _start + len;iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = val;++_finish;}iterator erase(iterator pos){assert(pos >= _start);// 不要等于 _finishassert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;}private:// 数据起始地址iterator _start = nullptr;// 数据末尾的下一个地址iterator _finish = nullptr;// 容量末尾的下一个地址iterator _endofstorage = nullptr;};// 测试区void test01(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (int i = 0; i < v.size(); ++i){std::cout << v[i] << " ";}std::cout << std::endl;}void test02(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);vector<int>::iterator it = v.begin();while (it != v.end()){std::cout << *it << " ";++it;}std::cout << std::endl;for (auto e : v){std::cout << e << " ";}std::cout << std::endl;}void test03(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);// 头部插入一个0v.insert(v.begin(), 0);// 尾删v.pop_back();for (auto e : v){std::cout << e << " ";}std::cout << std::endl;}void test04(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.resize(10);for (auto e : v){std::cout << e << " ";}std::cout << std::endl;v.resize(30, 7);for (auto e : v){std::cout << e << " ";}std::cout << std::endl;v.resize(3);for (auto e : v){std::cout << e << " ";}std::cout << std::endl;}void test05(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (auto e : v){std::cout << e << " ";}std::cout << std::endl;vector<int> v1(v);for (auto e : v1){std::cout << e << " ";}std::cout << std::endl;}void test06(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);vector<int> v1 = v;for (auto e : v1){std::cout << e << " ";}std::cout << std::endl;}void test07(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);// 这里也可以使用其他容器的迭代器vector<int> v1(v.begin() + 1, v.end() - 1);for (auto e : v1){std::cout << e << " ";}std::cout << std::endl;}void test08(){// 初始化 10 个 1vector<int> v(10, 1);for (auto e : v){std::cout << e << " ";}std::cout << std::endl;}void test09(){vector<std::string> v;v.push_back("11111");v.push_back("22222");v.push_back("33333");v.push_back("44444");v.push_back("55555");for (auto e : v){std::cout << e << " ";}std::cout << std::endl;}void test10(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(4);auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);}else{++it;}}for (auto e : v){std::cout << e << " ";}std::cout << std::endl;}
}

tset.cpp 源文件

#include<iostream>
#include"vector.h"
using namespace std;int main()
{my::test10();return 0;
}

未完待续

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

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

相关文章

overflow:hidden对解决外边距塌陷的个人理解

外边距塌陷&#xff1a; 子元素的上外边距大于父元素的上外边距&#xff0c;导致边距折叠&#xff0c;取两者之间最大值&#xff0c;即子元素外边距&#xff0c;导致父元素上外边距失效。 解决办法&#xff1a;在父元素样式添加overflow:hidden;或者border:1px solid black;(不…

前端开发攻略---介绍HTML中的<dialog>标签,浏览器的原生弹框。

1、演示 2、介绍 <dialog> 标签用于定义对话框&#xff0c;即一个独立的窗口&#xff0c;通常用来显示对话框、提示框、确认框等弹出式内容。在对话框中&#xff0c;可以包含文本、表单元素、按钮等内容&#xff0c;用户可以和这些内容进行交互。 3、兼容性 4、示例代码 …

【C语言回顾】数据在内存中的存储

前言1. 概述2. 大小端字节序和字节序判断2.1 大端字节序&#xff08;Big-Endian&#xff09;2.2 小端字节序&#xff08;Little-Endian&#xff09;2.3 判断字节序的示例 3. 数据在内存中的存储3.1 整数在内存中的存储3.2 浮点数在内存中的存储 结语 ↓ 上期回顾: 【C语言回顾】…

【小菜鸟之---Ansible基础详解】

文章目录 1 【Ansible简介】1.1简介1.2 Ansible 特点1.3 Ansible的工作机制1.4Ansible任务工作模式 2【安装部署】2.1安装命令2.2 Ansible配置文件2.3主机清单配置2.4 基于ssh免密登录2.5常用命令 3【Ansible常用模块】3.1 ping模块3.2 shell模块3.3 command模块3.4 copy模块3.…

webox微信群发器多少钱?电脑微信群发软件哪个好用?微信群发助手一次能发多少人?最强稳定群发器来袭

今天给大家推荐一款我们目前在使用的电脑群发工具WeBox&#xff0c;不仅可以无限多开&#xff0c;方便你同时管理多个账号&#xff0c;群发功能更是十分强大&#xff0c;轻松释放你的双手。 软件下载地址>>密码&#xff1a;4as1 WeBox群发功能 下载WeBox打开登录&#x…

Golang | Leetcode Golang题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; func climbStairs(n int) int {sqrt5 : math.Sqrt(5)pow1 : math.Pow((1sqrt5)/2, float64(n1))pow2 : math.Pow((1-sqrt5)/2, float64(n1))return int(math.Round((pow1 - pow2) / sqrt5)) }

《苍穹外卖》电商实战项目(java)知识点整理(P1~P65)【上】

史上最完整的《苍穹外卖》项目实操笔记&#xff0c;跟视频的每一P对应&#xff0c;全系列10万字&#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳&#xff0c;参考这篇&#xff0c;相信会带给你极大启发。 《苍穹外卖》项目实操笔记【中】&#xff1a;P66~P…

SiteServer 插件之 用户登录插件-用户注册

1、请确保已经安装了“用户登录插件”,如下图。 2、 显示管理->包含文件管理->include/header.html->编辑,如下图。 3、代码如下。 <header><div class="wrap"><div class="top-box clearfix"><div class="left-box…

cordova build android 下载gradle太慢

一、 在使用cordova run android / cordova build android 的时候 gradle在线下载 对于国内的链接地址下载太慢。 等待了很长时间之后还会报错。 默认第一次编译在线下载 gradle-7.6.1-all.zip 然后解压缩到 C:\Users\Administrator\.gradle 文件夹中,下载慢导致失败。 二…

前端工程化06-JavaScript模块化CommonJS规范ES Module

7、JavaScript模块化 在js开发中&#xff0c;他并没有拆分的概念&#xff0c;并不像java一样他可以拆分很多的包&#xff0c;很多的类&#xff0c;像搭积木一样完成一个大型项目的开发&#xff0c;所以js在前期的时候并不适合大型后端的项目开发&#xff0c;但是这些问题在后来…

CNN实现卫星图像分类(tensorflow)

使用的数据集卫星图像有两类&#xff0c;airplane和lake&#xff0c;每个类别样本量各700张&#xff0c;大小为256*256&#xff0c;RGB三通道彩色卫星影像。搭建深度卷积神经网络&#xff0c;实现卫星影像二分类。 数据链接百度网盘地址&#xff0c;提取码: cq47 1、查看tenso…

CentOS常用命令有哪些?

目录 一、CentOS常用命令有哪些&#xff1f; 二、不熟悉命令怎么办&#xff1f; 场景一&#xff1a;如果是文件操作&#xff0c;可以使用FileZilla工具来完成 场景二&#xff1a;安装CentOS桌面 一、CentOS常用命令有哪些&#xff1f; CentOS 系统中有许多常用命令及其用法…

leetcode尊享面试100题(549二叉树最长连续序列||,python)

题目不长&#xff0c;就是分析时间太久了。 思路使用dfs深度遍历&#xff0c;先想好这个函数返回什么&#xff0c;题目给出路径可以是子-父-子的路径&#xff0c;那么1-2-3可以&#xff0c;3-2-1也可以&#xff0c;那么考虑dfs返回两个值&#xff0c;对于当前节点node来说&…

JavaScript —— APIs(五)

一、Window对象 1. BOM&#xff08;浏览器对象模型&#xff09; 2. 定时器-延时函数 ①、定义 ②、定时器比较 ③、【案例】 3. JS执行机制 4. location对象 注意&#xff1a;hash应用 不点击页面刷新号&#xff0c;点击刷新按钮也可以实现页面刷新 【案例】 5. navig…

电机控制系列模块解析(16)—— 电流环

一、FOC为什么使用串联控制器 在此说明&#xff0c;串联形式&#xff08;内外环形式&#xff0c;速度环和电流环控制器串联&#xff09;并不是必须的&#xff0c;但是对于线性控制系统来说&#xff0c;电机属于非线性控制对象&#xff0c;早期工程师们为了处理电机的非线性&am…

【ARM】ARM寄存器和异常处理

1.指令的执行过程 &#xff08;1&#xff09;一条指令的执行分为三个阶段 1.取址&#xff1a; CPU将PC寄存器中的地址发送给内存&#xff0c;内存将其地址中对应的指令返回 到CPU中的指令寄存器&#xff08;IR&#xff09; 2.译码&#xff1a; 译码器对IR中的指令…

神经网络中的算法优化(皮毛讲解)

抛砖引玉 在深度学习中&#xff0c;优化算法是训练神经网络时至关重要的一部分。 优化算法的目标是最小化&#xff08;或最大化&#xff09;一个损失函数&#xff0c;通常通过调整神经网络的参数来实现。 这个过程可以通过梯度下降法来完成&#xff0c;其中梯度指的是损失函数…

Grafana:云原生时代的数据可视化与监控王者

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Grafana&#xff1a;让数据说话的魔术师》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Grafana简介 2、Grafana的重要性与影响力 …

全方位了解 Meta Llama 3

本文将为您提供 Llama 3 的全面概览&#xff0c;从其架构、性能到未来的发展方向&#xff0c;让您一文了解这一革命性大语言模型的所有要点。 Meta Llama 发展历程 Llama 1 Llama 是由 Meta(FaceBook) AI 发布的一个开源项目&#xff0c;允许商用&#xff0c;影响力巨大。Lla…

力扣每日一题111:二叉树的最小深度

题目 简单 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;2示例 2&#x…