网站备案编号查询/seo基础视频教程
文章目录
- vector的模拟实现
- vector 结构定义
- 1. vector的迭代器的实现
- 2. vector四个默认成员函数
- 2.1 构造函数
- 2.1.1 无参
- 2.1.2 n个val初始化
- 2.1.3 迭代器初始化
- 2.2 析构函数
- 2.3 拷贝构造函数
- 2.3.1 传统写法
- 2.3.2 现代写法
- 2.4 赋值重载运算符
- 3. 管理数组相关接口
- 3.1 reserve
- 3.2 resize
- 3.3 push_back
- 3.3 pop_back
- 3.4 empty
- 3.5 insert
- 3.6 erase
- 3.7 size
- 3.8 capacity
- 3.9 swap
- 4. 运算符重载
- 4.1 []
- 5. 迭代器失效问题
- 6. 深浅拷贝问题
- 6.1 简单介绍深浅拷贝
- 6.2 vector中深浅拷贝的几种情况
vector的模拟实现
vector 结构定义
vector要实现成模板的形式, 使vector中可以存储任意类型的数据; 成员变量不再像string给一些具体的数据类型, 而是采用指针模拟的迭代器来实现, 这里使用C+11语法给成员变量缺省值, 在构造和拷贝构造时不用去初始化列表中初始化, 直接用缺省值
template<class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start=nullptr; //指向首元素 iterator _finish=nullptr; //sizeiterator _end_of_storage=nullptr; //capacity
}
1. vector的迭代器的实现
vector的迭代器有两种, 一种迭代器可读指向内容可以修改, 一种是const类型的迭代器,只读不可修改指向内容。用指针实现即可。
template<class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish; }//指向内容不能修改, 只能读不能写const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}
}
2. vector四个默认成员函数
2.1 构造函数
按照库中的构造函数, 我们提供3个版本的
2.1.1 无参
vector() //给缺省值 --- 初始化列表使用
{}
2.1.2 n个val初始化
直接向数组中尾插数据,用reserve提前扩容, 提高效率
注意点: 这里传参的第二个参数使用匿名对象,
const T& val = T()
这种写法会调用默认构造(可以是任意类型),我们前面讲内置类型是没有默认构造函数的, 理论而言是没有的, 但是调用模板之后必须要支持默认构造
补充匿名对象
vector(size_t n, const T& val = T()) //匿名对象
{reserve(n); //提前扩容, 提高效率for (int i = 0; i < n; ++i){push_back(val);}
}
2.1.3 迭代器初始化
这里又要使用模板实现, 要实现一个任意类型的迭代器允许任意类型的数据使用,直接用迭代器遍历数组, 尾插数据即可
//任意类型的迭代器 --- 允许类的成员函数再设函数模板
//[first, last)
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
2.2 析构函数
直接将start指向的空间释放, 并且将三个指针置空即可
~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}
2.3 拷贝构造函数
2.3.1 传统写法
开辟新的空间,使即将被赋值的对象 _start指向新空间,后赋值(深拷贝)将原空间内容拷贝到新空间, 改变新空间的 _finish和 _end_of_storage
一定要使用赋值的方法深拷贝, memcpy是浅拷贝
vector(const vector<T>& v)
{_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T) * v.size());//深拷贝for (size_t i = 0; i < v.size(); ++i){_start[i] = v._start[i]; //赋值就是深拷贝}_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}
2.3.2 现代写法
直接用迭代区间初始化去构造临时对象 tmp ,然后交换tmp 和原对象即可
vector(const vector<T>& v)
{vector<T> tmp(v.begin(), v.end());swap(tmp);
}
2.4 赋值重载运算符
这里要实现深拷贝, v1=v2其实是v2去拷贝构造v,然后交换v1和v,便可对v1赋值
//v1 = v2
vector<T>& operator=(vector<T> v) //v2拷贝构造v, 交换v1和v
{swap(v);return *this;
}
3. 管理数组相关接口
3.1 reserve
在n>capacity时再去扩容, 这种做法是防止缩容
- 提前记录好原数组大小, 防止扩容拷贝数据后数组大小改变;开辟n个空间大小的tmp数组
- 遍历将原数组中元素拷贝到新数组中, 一定要使用赋值的方法深拷贝,不能使用memcpy(memcpy是浅拷贝)
- 释放原数组,改变3个指针的指向
void reserve(size_t n)
{if (n > capacity()) //防止缩容{size_t sz = size(); //提前记录好原数组大小, 防止扩容拷贝数据后数组大小改变T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * size()); //错误写法,memcpy浅拷贝for (size_t i = 0; i <sz; ++i){tmp[i] = _start[i]; //赋值就是深拷贝}delete[] _start;}_start = tmp;_finish = _start+sz;_end_of_storage = _start + n;}
}
3.2 resize
n < size()
就是删除数据,直接改变 _finish的指向即可n > size()
调用reserve函数扩容, 后遍历给数组赋值
void resize(size_t n, T val=T()) // T val=T() 调用默认构造(可以是任意类型)
{if (n < size()) //删除数据{_finish = _start + n;}else if (n > size()){if (n > capacity())reserve(n);//初始化while (_finish != _start + n){*_finish = val;++_finish;}}
}
3.3 push_back
- 检查容量, 容量不够时需要扩容
- 直接向 _finsh指向位置插入元素, 后改变 _finish的指向
void push_back(const T& x)
{if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
3.3 pop_back
直接finish指向前移, – _finsh即可
注意: 数组为空无法删除
void pop_back()
{assert(!empty());--_finish;
}
3.4 empty
直接判断 _start是否等于 _finish
bool empty()
{return _start == _finish;
}
3.5 insert
- 检查容量,观察是否需要扩容, 扩容前计算出pos与start之间,pos与start之间相对距离不变,扩容后更新pos位置(这里存在迭代器失效的问题)
- 遍历挪动数据
- 将val插入pos位置
注意: 检查pos位置的合法性
iterator insert(iterator pos, const T&val)
{assert(pos >= _start && pos<=_finish);//1. 检查容量if (_finish == _end_of_storage){size_t len = pos - _start; //pos与start之间相对距离不变reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容后更新pos, 解决pos失效的问题pos = _start + len;}//2. 挪动数据iterator end = _finish - 1;while (end>=pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}
3.6 erase
- 遍历挪动数据
- 改变 _ finish的指向
注意: 检查pos位置的合法性
iterator erase(iterator pos)
{assert(pos >= _start && pos < _finish);//挪动数据iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos; //返回被删除元素的下一个元素, 对迭代器进行更新
}
3.7 size
size_t size() const
{return _finish - _start;
}
3.8 capacity
size_t capacity() const
{return _end_of_storage - _start;
}
3.9 swap
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}
4. 运算符重载
4.1 []
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos)const
{assert(pos < size());return _start[pos];
}
5. 迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于vector可能会导致其迭代器失效的操作有:
1. 指定位置前插入元素操作 — insert
如下代码, 错误演示:
void insert(iterator pos, const T&val)
{assert(pos >= _start && pos<=_finish);//1. 检查容量if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}//2. 挪动数据iterator end = _finish - 1;while (end>=pos){*(end + 1) = *end;--end;}*pos = val;++_finish;
}
此代码存在两个大问题:
- 迭代器失效的问题,插入元素扩容时, 可能存在异地扩容, 扩容后pos的指向没有更新, 就可能会出现如下图扩容后pos处指向随机值,扩容后无法找到pos指向的元素**。所以我们利用pos和 _start之间的相对距离不变的特点, 在扩容前先算出pos和 _start之间的距离,扩容后更新pos指向即可**
- 函数形参pos是实参的临时拷贝, 形参的改变不会影响外部的实参。有两种结局方式: 1. 引用传参,
iterator begin()
迭代器实现是传值返回, 会返回临时对象, 临时对象具有常性, 这里相当于权限放大 2. 按照库里的实现方式给返回值。这里只能使用给返回值的方式更新pos正确代码,就是上面单独实现的insert
2. 指定位置元素的删除操作–erase
如下代码
void test_vector4()
{std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//auto pos = find(v1.begin(), v1.end(), 2); //程序可能不会崩溃auto pos = find(v1.begin(), v1.end(), 4); //一定会崩溃if (pos != v1.end()){v1.erase(pos);}(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;
}
对于删除元素2来说, 删除2后, 去更改
*pos
位置的值,VS下程序会崩溃, Linux下不会对于删除元素4来说, 删除4后, 去更改
*pos
位置的值,VS和Linux下都会崩溃, 删除pos位置后, 该位置已不再数组的范围中了, 再去更改访问就是野指针的问题了,程序一定会崩溃分析:
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了
结论: erase之后, pos是否失效? 失效,不要访问,行为结果未定义
删除元素后, 要对迭代器指向位置进行更新, 防止迭代器失效
如下代码, 删除数组中的偶数元素, 删除后要对it进行更新,所以erase删除元素后返回的是删除元素的下一个位置
void test_vector5()
{vector<int> v1;v1.push_back(10);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(50);vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0)//v1.erase(it);it = v1.erase(it); //每次删完后更新itelseit++;}for (auto e : v1){cout << e << " ";}cout << endl;
}
6. 深浅拷贝问题
6.1 简单介绍深浅拷贝
浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
深拷贝
深拷贝: 给每个对象独立分配资源, 保证多个对象之间不会因资源而造成多次释放,造成程序崩溃问题
6.2 vector中深浅拷贝的几种情况
情况1: 不写拷贝构造函数, 直接使用默认的拷贝构造函数
代码如下, 实现将v1拷贝到v2, 不写拷贝构造函数使用默认的拷贝构造函数时,默认的拷贝构造会对内置类型完成浅拷贝, 会使v1,v2指向同一块空间, 并且析构时会析构两次, 程序会崩溃。
情况2: 写了拷贝构造函数, 拷贝构造函数中使用memcpy和reserve中的memcpy
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
- 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
代码如下, 将字符串拷贝到一个字符串数组中, 使用memcpy实现了外层深拷贝,内层浅拷贝, 程序会崩溃
在使用memcpy函数前, 实现了外层的深拷贝
使用memcpy函数后, 实现了内层的浅拷贝
我们要实现的是外层深拷贝,内层深拷贝, 直接 使用赋值的方法实现深拷贝
情况3: 拷贝构造函数中使用赋值深拷贝,不写赋值重载运算符, 使用默认生成的
代码如下, 实现打印杨辉三角,运行此代码程序会崩溃
调试发现实现的是外层深拷贝,内层浅拷贝
在拷贝构造时外层的vv和ret实现了深拷贝后, 内层的 vector< int >
调用默认的赋值重载, 实现的是浅拷贝, 而我们的内层需要实现深拷贝,写赋值重载函数,内层用交换来实现深拷贝
相关文章:

vector的模拟实现
文章目录vector的模拟实现vector 结构定义1. vector的迭代器的实现2. vector四个默认成员函数2.1 构造函数2.1.1 无参2.1.2 n个val初始化2.1.3 迭代器初始化2.2 析构函数2.3 拷贝构造函数2.3.1 传统写法2.3.2 现代写法2.4 赋值重载运算符3. 管理数组相关接口3.1 reserve3.2 res…...

【无标题】compose系列教程-4.相对布局ConstraintLayout的使用
相对布局在Compose中被称为ConstraintLayout,它可以让您以相对于其他元素的方式放置元素。 以下是使用ConstraintLayout实现相对布局的示例代码: Composable fun ConstraintLayoutExample() { ConstraintLayout(modifier Modifier.fillMaxSize()…...

JavaEE简单示例——Bean管理
简单介绍: 在这一章节我们会比较详细的介绍我们在之前的测试类中以及Bean管理XML配置文件中所使用到的类和方法,以及XML中配置的属性所代表的详细含义。以及之前我们反复提到但是一直没有详细的讲解的一个东西:容器。我们可以大致的有一个概…...

react+antdpro+ts实现企业级项目四:注册页面实现及useEmotionCss的介绍
创建文件路径并注册register路由 在pages/User下创建Register文件夹并创建index.tsx文件 然后在config/routes创建register注册路由。注册完后,当在登陆页面点击注册按钮时就可以跳转到此注册页面而不会报404了。 export default [{path: /user,layout: false,rou…...

Shifu基础功能:数据采集
数据采集 我们可以通过HTTP/gRPC与deviceShifu进行通信,deviceShifu会将我们发送的请求转换成设备所支持协议的形式,并发送给设备。 当设备接收到指令之后,数据会传输到deviceShifu中,之后deviceShifu将数据作为我们请求的返回值…...

代码随想录算法训练营day54 | 动态规划之子序列 392.判断子序列 115.不同的子序列
day54392.判断子序列1.确定dp数组(dp table)以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组115.不同的子序列1.确定dp数组(dp table)以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺…...

MCAL知识点(三):Port与Dio配置
目录 1、概述 2、 Port的EB-tresos配置 2.1、创建模块 2.2 General配置 2.3、PortCnfigSet 2.4、Port的属性...

初识C++需要了解的一些东西(1)
目录🥇命名空间🏅存在原因🏵命名空间定义🎧命名空间的3种使用方式🏆C输入和输出🌝缺省参数🌜缺省参数概念⭐️缺省参数分类☀️函数重载🔥引用🌚引用概念🌓引…...

友元函数的使用大全
概述 我们知道,C的类具有封装和信息隐藏的特性。一般情况下,我们会封装public的成员函数供用户调用,而将成员变量设置为private或protected。但在一些比较复杂的业务情况下,可能需要去访问对象中大量的private或protected成员变量…...

QT学习笔记-QT多项目系统中如何指定各项目的编译顺序
QT学习笔记-QT多项目系统中如何指定各项目的编译顺序背景环境解决思路具体操作背景 为了更好的复用程序功能以及更优雅的管理程序,有经验的程序员通常要对程序进行分层和模块化设计。在QT/C这个工具中同样可以通过创建子项目的方式对程序进行模块化,在这…...

JWT令牌解析及刷新令牌(十一)
写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!如果我的博客对你有帮助,欢迎进行评论✏️✏️、点赞👍👍、收藏⭐️⭐️&#…...

Hibernate学习(一)
Hibernate学习(一) Hibernate框架的概述: 一:什么是框架:指软件的半成品,已经完成了部分功能。 二:EE的三层架构: 1.EE的三层经典架构: 我在这里主要学的是ssh框架。 三…...

Go的 context 包的使用
文章目录背景简介主要方法获得顶级上下文当前协程上下文的操作创建下级协程的Context场景示例背景 在父子协程协作过程中, 父协程需要给子协程传递信息, 子协程依据父协程传递的信息来决定自己的操作. 这种需求下可以使用 context 包 简介 Context通常被称为上下文ÿ…...

微服务为什么要用到 API 网关?
本文介绍了 API 网关日志的价值,并以知名网关 Apache APISIX 为例,展示如何集成 API 网关日志。 作者程小兰,API7.ai 技术工程师,Apache APISIX Contributor。 原文链接 什么是微服务 微服务架构(通常简称为微服务&a…...

SWUST OJ 1042: 中缀表达式转换为后缀表达式【表达式转逆波兰表达式】
题目描述 中缀表达式是一个通用的算术或逻辑公式表示方法,操作符是以中缀形式处于操作数的中间(例:3 4),中缀表达式是人们常用的算术表示方法。后缀表达式不包含括号,运算符放在两个运算对象的后面&#…...

Matlab基础知识
MATLAB批量读入文件和导出文件一、 批量读入文件1.若文件名称有序,则按照文件名称规律循环读取文件(1)读入不同的excelfor i1:1:10strstrcat(F:\数据\v,int2str(i),.xlsx); %连接字符串形成文件名Axlsread(str); end注:变量i为整数时,可以用i…...

动手学深度学习【2】——softmax回归
动手学深度学习网址:动手学深度学习 注:本部分只对基础知识进行简单的介绍并附上完整的代码实现,更多内容可参考上述网址。 前言 前面一节我们谈到了线性回归,它解决的是预测某个值的问题。但是在日常生活这,除了预测…...

深入理解Activity的生命周期
之前学习安卓的时候只是知道生命周期是什么,有哪几个,但具体的详细的东西却不知道,后来看过《Android开发艺术探索》和大量博客之后,才觉得自己真正有点理解生命周期,本文是我对生命周期的认识的总结。废话少说先上图。…...

Go语言刷题常用数据结构和算法
数据结构 字符串 string 访问字符串中的值 通过下标访问 s1 : "hello world"first : s[0]通过切片访问 s2 : []byte(s1) first : s2[0]通过for-range循环访问 for i, v : range s1 {fmt.Println(i, v) }查询字符是否属于特定字符集 // 判断字符串中是否包含a、b、…...

深入vue2.x源码系列:手写代码来模拟Vue2.x的响应式数据实现
前言 Vue响应式原理由以下三个部分组成: 数据劫持:Vue通过Object.defineProperty()方法对data中的每个属性进行拦截,当属性值发生变化时,会触发setter方法,通知依赖更新。发布-订阅模式:Vue使用发布-订阅…...

Linux线程控制
本篇我将学习如何使用多线程。要使用多线程,因为Linux没有给一般用户直接提供操作线程的接口,我们使用的接口,都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此,需要引入-lpthread,即连接原生…...

【LeetCode】剑指 Offer(20)
目录 题目:剑指 Offer 38. 字符串的排列 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 38. 字符串的…...

FutureTask中的outcome字段是如何保证可见性的?
最近在阅读FutureTask的源码是发现了一个问题那就是源码中封装结果的字段并没有使用volatile修饰,源码如下:public class FutureTask<V> implements RunnableFuture<V> {/*** 状态变化路径* Possible state transitions:* NEW -> COMPLET…...

直播回顾 | 聚焦科技自立自强,Bonree ONE 助力国产办公自动化平稳替代
3月5日,两会发布《政府工作报告》,强调科技政策要聚焦自立自强。 统计显示,2022年金融信创项目数同比增长300%,金融领域信创建设当前已进入发展爆发期,由国有大型银行逐渐向中小型银行、非银金融机构不断扩展。信创云…...

深入理解Linux进程
进程参数和环境变量的意义一般情况下,子进程的创建是为了解决某个问题。那么解决问题什么问题呢?这个就需要进程参数和环境变量来进行决定的。子进程解决问题需要父进程的“数据输入”(进程参数 & 环境变量)设计原则:3.1 子进程启动的时候…...

Vue3之组件间的双向绑定
何为组件间双向绑定 我们都知道当父组件改变了某个值后,如果这个值传给了子组件,那么子组件也会自动跟着改变,但是这是单向的,使用v-bind的方式,即子组件可以使用父组件的值,但是不能改变这个值。组件间的…...

Java语法基础(一)
目录 代码注释方法 编码规范 基本数据类型及取值范围 变量和常量的声明与赋值 变量 常量 标识符 基本数据类型的使用 整数类型的使用 浮点类型的使用 布尔类型的使用 字符类型的使用 代码注释方法 单行注释:使用“//”进行单行注释多行注释:使…...

优思学院|零质量控制是什么概念?
零质量控制(Zero Quality Control)是指一个理想的系统,可以生产没有任何缺陷的产品,因此不需要频繁的检查,从而节省时间和金钱。那些追求过程优化并致力于持续过程改进的组织将零质量控制(Zero Quality Con…...

2023-03-09 CMU15445-Query Execution
摘要: CMU15445, Project #3 - Query Execution 参考: Project #3 - Query Execution | CMU 15-445/645 :: Intro to Database Systems (Fall 2022) https://github.com/cmu-db/bustub 要求: OVERVIEW At this point in the semester, you have implemented the internal co…...

vuedraggable的使用
Draggable为基于Sortable.js的vue组件,用以实现拖拽功能。 特性 支持触摸设备 支持拖拽和选择文本 支持智能滚动 支持不同列表之间的拖拽 不以jQuery为基础 和视图模型同步刷新 和vue2的国度动画兼容 支持撤销操作 当需要完全控制时,可以抛出所有变化 可…...