C++初阶 | [八] (下) vector 模拟实现

摘要:vector 模拟实现讲解(附代码示例),隐藏的浅拷贝,迭代器失效

在进行 vector 的模拟实现之前,我们先粗略浏览一下 stl_vector.h 文件中的源码来确定模拟实现的大体框架。


这里提供一些粗略浏览源码的技巧:
1.不要一行一行地看

2.先不要研究细节,先看整体的框架(类:成员变量+成员函数)

3.理解的时候连蒙带猜,再验证自己的猜测

ps.在已经模拟实现过 string类的前提下,vector 的模拟实现的讲解在一些非必要的地方不多赘述,直接给出代码示例。

框架:👇

template<class T>
class vector
{
private:iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾(指向最后一个有效数据的下一个)iterator _endOfStorage = nullptr; // 指向存储容量的尾
};

首先,同 string 类的匿名实现一样,我们先创建一个自己的命名空间,将 vector 的模拟实现在这个自定义的命名空间中以区分库中的vector。

1. Constructor and Destructor

不多赘述。示例如下。

#pragma oncenamespace Bottle
{template<class T>class vector{public:// construct and destroyvector(){}~vector(){delete[]_start;_start = _finish = _endOfStorage = nullptr;}private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾(指向最后一个有效数据的下一个)iterator _endOfStorage; // 指向存储容量的尾};}

2. push_back

(1)这里需要顺便实现 size_t capacity()函数 size_t size()函数

(2)思路:检查容量 → 插入数据
(注意:检查容量时,如果是遵循两倍扩容的思路,就需要注意容量为0的情况,如果此时直接按两倍扩容会导致错误)

代码示例:

		size_t size() const{return  _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}//push_backvoid push_back(const T& x){if (_finish == _endOfStorage){//check capacitysize_t new_capacity = capacity() == 0 ? 4 : (capacity() * 2);reserve(new_capacity);}*(_finish) = x;//push back++_finish;}

3. reserve

思路:开新空间 → 拷贝数据到新空间 → 指向新空间(ps.原则上只扩容不缩容)

  • 关于指向新空间这个操作需要注意的问题:不同于 string类 size数据是由成员变量存储起来的,vector size是通过算两个指针之间的偏移量得出的。
    如图所示,当 _start 发生改变之后_finish ≠ _start + size(),而应该提前记录下 _finish 相对于 _start 的偏移量。

代码示例: 

//reserve	void reserve(size_t n){if (n > capacity()){T* tmp = new T[n + 1];//newsize_t sz = size();memcpy(tmp, _start, sizeof(T) * size());//copydelete[]_start;_start = tmp;_finish = tmp + sz;_endOfStorage = tmp + n;tmp = nullptr;}}

4. Access

1)operator[]

代码示例:

//operatorr[]T& operator[](const size_t pos){assert(pos < size());return *(_start + pos);}const T& operator[](const size_t pos)const{assert(pos < size());return *(_start + pos);}

2)iterator

(迭代器实现之后就可以使用范围for了)

代码示例:

	public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator cbegin()const{return _start;}const_iterator cend()const{return _finish;}

5. resize

有模板(template)之后,C++对内置类型进行了升级,即一切都是对象,以前在C语言里面只有变量,而在之后的C++中是变量,也是对象。
e.g. int vi = int(2);//该语句可看作调用默认构造函数用 ‘2’ 来构造一个 int 类型的对象 vi . (内置类型也会调用构造)

代码示例:

//resizevoid resize(size_t n, const T& value = T()){if (n <= size()){//delete_finish = _start + n;}else{reserve(n);//check capacitysize_t len = n - size();while (len--){*_finish = value;++_finish;//改变finish的指向就是改变size的大小}}}

6. insert

iterator insert(iterator pos, const T& x){……}

  • string 模拟实现 insert 

    思路:①检查 pos 位置的有效性;②检查容量;③挪动数据;④插入数据(strncpy)。

    特殊情况:头插(pos==0)

  • vector
    思路:①检查 pos 位置的有效性;②检查容量;③挪动数据;④插入数据。

    特殊情况:头插(pos==0)

  • 区别
    string中的挪动数据时比较的是下标和下标,即 size_t 数据类型的比较,头插时比较特殊,下标不断变小的过程中可能会出现“-1>0” 的情况(详情见string类模拟实现的文章)
    vector中挪动数据时比较的是迭代器和迭代器,即 T* (因为这里迭代器的底层实现用的是原生指针)数据类型的比较,所以这里头插不属于特殊情况。但是,这也由此引发了另外的问题——迭代器失效

迭代器失效

iterator pos 底层是指针。reserve 扩容之后原空间被释放,而 pos 还指向原空间。所以我们需要获取 pos 相对于 _start 的偏移量。

② iterator pos 是传值传参。如下图所示。(提醒💡:这里也不适合用 传引用传参 (iterator& pos),要引用只能是 const 引用 (const iterator& pos),因为在类似 v.insert(v.begin(),数据) 的函数调用中,begin()函数是传值返回,则它的返回值具有常属性,只能用 const 引用,但 const 引用会使 pos 无法被修改,则对于下图所示的迭代器是没有意义的)

代码示例:

//insertiterator insert(iterator pos, const T& x){//check posassert(pos >= _start);assert(pos <= _finish);size_t len = pos - _start;//偏移量//check capacityif (_finish == _endOfStorage){reserve(capacity() == 0 ? 4 : (capacity() * 2));pos = _start + len;//reserve之后更新pos}for (iterator end = _finish; end > pos; --end){*end = *(end - 1);//move data}*pos = x;//pos位置insert data++_finish;return pos;}

7. erase

思路:依次挪动数据覆盖

注意:erase 同样会导致迭代器失效

  • 关于erase导致的迭代器失效:

    如下图,删除数据中偶数的行为出错是因为 erase 导致的迭代器失效,删除符合条件的元素之后,数据的内容发生了改变,就导致原本指向该元素(被删除的这个元素)的迭代器的指向是未知的,当我们在 erase 之后再去使用这个迭代器,行为也是位置的。

    库里面的做法是 erase 函数会返回要删除的指定 pos 位置的元素的下一个元素的位置。
     

代码示例:

		iterator erase(iterator pos){//check posassert(pos >= _start);assert(pos <= _finish);//move datafor (iterator it = pos + 1; it < _finish; ++it){*(it - 1) = *(it);}--_finish;return pos;}

8. 隐藏的浅拷贝_reserve

memcpy:隐藏的浅拷贝 ( from reserve)(如下图所示,tip.如果这里运用 引用计数的浅拷贝就会很高效)

由上图可知,delete 释放空间,对于自定义类型 string 会自动调用析构函数,导致新空间指向已经被析构掉的空间。因此,拷贝数据最好通过赋值操作来实现,赋值会自动调用拷贝构造进行深拷贝

代码示例:

//reservevoid reserve(size_t n){if (n > capacity()){T* tmp = new T[n + 1];//newsize_t sz = size();for (size_t i = 0; i < sz; ++i)//copy{tmp[i] = *(_start + i);}//memcpy(tmp, _start, sizeof(T) * size());//copydelete[]_start;_start = tmp;_finish = tmp + sz;_endOfStorage = tmp + n;tmp = nullptr;}}

9. Copy Constructor

思路:
1)初始化成员变量
2)reserve
3)拷贝数据(push_back

代码示例:

//copy constructorvector(const vector<T>& v)//copy constructor:_start(nullptr),_finish(nullptr),_endOfStorage(nullptr){reserve(v.capacity());for (size_t i = 0; i < v.size(); ++i){push_back(v[i]);}}

10. 赋值重载

现代写法同 string类 的模拟实现,详情见 string类 模拟实习的文章。

代码示例:

		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;}

11. 其他构造函数重载

注意:自己写了构造函数之后,编译器不会在生成再生成默认构造,所以在构造函数中必须要有一个默认构造函数

1)template <class InputIterator>vector (InputIterator first, InputIterator last);
		template<class InputIterator>vector(InputIterator first, InputIterator last){size_t len = last - first;reserve(len);for (size_t i = 0; i < len; ++i){*(_start + i) = *(first + i);}_finish = _start + len;_endOfStorage = _finish;}
2)vector (size_t n, const T& val = T())

T 为 int 类型时,会出现类型匹配的问题,如下图:因此,对于 int 类型需要专门实现的一个函数重载。

		vector(size_t n, const T& value = T()){reserve(n);while (n--){push_back(value);}}vector(int n, const T& value = T()){reserve(n);while (n--){push_back(value);}}

 

ps.默认构造函数:

		vector(){}

完整代码链接:My_vector/My_vector/My_vector.h · fantansy-13-07/Cpp - 码云 - 开源中国 (gitee.com)


END

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

防御-day3-内容安全(入侵检测-IPS,防病毒网关-AV)

一、内容安全 1、华为---IAE引擎 核心技术 DPI---深度包检测---主要针对完整的数据包&#xff08;数据包分片&#xff0c;分段需要重组&#xff09;&#xff0c;之后对 数据包的内容进行识别。&#xff08;应用层&#xff09; DFI---深度流检测---一种基于流量行为的应用识别技…

SpringCloud(17)之SpringCloud Stream

一、Spring Cloud Stream介绍 Spring Cloud Stream是一个框架&#xff0c;用于构建与共享消息系统连接的高度可扩展的事件驱动微服务。该框架提供了一个灵活的编程模型&#xff0c;该模型建立在已经建立和熟悉的Spring习惯用法和最佳实践之上&#xff0c;包括对持久发布/子语义…

Coze开源软件Windows客户端-coze_desk

字节的coze相信大家都已经有所关注了&#xff0c;最近看到很多公众号在推。笔者也在使用&#xff0c;体验很不错。 这个是官网&#xff1a;https://www.coze.com/。 官网版 应用的样子 三栏式布局&#xff0c;用起来还是可以的。 不过这个是在浏览器端&#xff0c;有时候不小…

springcloud -远程调用方式

如果我们要进行远程微服之间的调用该如何完成呢&#xff0c;本文以案例推动以养老系统中老人支付购买商品为例&#xff0c;一步步实现远程微服务的调用。 目标 微服务之间的调用方式 老人支付 common模块设计 统一返回 package com.wnhz.ssc.common.result;import lombok.G…

书生·浦语大模型全链路开源体系介绍

背景介绍 随着人工智能技术的迅猛发展&#xff0c;大模型技术已成为当今人工智能领域的热门话题。2022 年 11 月 30 日&#xff0c;美国 OpenAI 公司发布了 ChatGPT 通用型对话系统 并引发了全球 的极大关注&#xff0c;上线仅 60 天月活用户数便超过 1 亿&#xff0c;成为历史…

开源人脸检测模型MTCNN简单的例子

阅读本文之前可以先参阅----神经网络中的重要概念 如何快速入门深度学习 当使用MTCNN模型进行人脸检测时&#xff0c;你可以使用Python编程语言和相应的深度学习库来实现。下面是一个简单的例子&#xff0c;演示了如何使用MTCNN模型进行人脸检测&#xff1a; 首先&#xff0c;…

每日学习总结20240227

每日总结 20240227 1.如何将字符串通过串口以十六进制进行传输 将文件名或者文件内容通过串口传输&#xff0c;再解析&#xff0c;拼接成源文件 1.1 文件转换 1.1.1 转十六进制 在Linux中&#xff0c;你可以使用 xxd 命令将文本文件转换为十六进制格式。以下是如何在Linux中…

天翼云登录参数JavaSrcipt逆向

天翼云登录参数 password 、comParam_curTime、comParam_seqCode、comParam_signature JavaSrcipt逆向 目标网站 https://m.ctyun.cn/wap/main/auth/login?redirect/my 目标参数 要逆向的有 password、comParam_curTime、comParam_seqCode、comParam_signature 四个参数 …

【蓝桥杯嵌入式】蓝桥杯嵌入式第十四届省赛程序真题,真题分析与代码讲解

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都已更新完毕&#xff0c;欢迎大家前往订阅本专题&#x1f38f; &#x1f38f;【蓝桥杯嵌入式】蓝桥杯第十届省赛真题 &#x1f38f;【蓝桥杯嵌入式】蓝桥…

软件测试笔记(三):黑盒测试

1 黑盒测试概述 黑盒测试也叫功能测试&#xff0c;通过测试来检测每个功能是否都能正常使用。在测试中&#xff0c;把程序看作是一个不能打开的黑盒子&#xff0c;在完全不考虑程序内部结构和内部特性的情况下&#xff0c;对程序接口进行测试&#xff0c;只检查程序功能是否按…

Web前端3D JS框架和库 整理

在WebGL库和SVG/Canvas元素的支持下&#xff0c;JavaScript变得惊人的强大。几乎可以为网络构建任何东西&#xff0c;包括基于浏览器的游戏和本地应用&#xff0c;许多最新的突破性功能都在3D上运行。 为此&#xff0c;「数维图小编」整理了19个交互式3D Javascript库和框架&am…

开心的金明

好久没发文章了&#xff0c;随着这一题开始2024年吧&#xff01; 题目描述 金明今天很开心&#xff0c;家里购置的新房就要领钥匙了&#xff0c;新房里有一间他自己专用的很宽敞的房间。更让他高兴的是&#xff0c;妈妈昨天对他说&#xff1a;“你的房间需要购买哪些物品&…

每日一练:LeeCode-235、二叉搜索树的最近公共祖先【二叉搜索树+DFS+从上往下】

本文是力扣每日一练&#xff1a;LeeCode-235、二叉搜索树的最近公共祖先【二叉搜索树DFS从上往下】 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百…

idea2023新UI风格不见了怎么办?

用了一段时间idea2023,有一天不知道点了什么&#xff0c;整个UI又变成了2022的风格 如果想换成2023的UI风格怎么办&#xff1f; 点击file->setting->new UI->勾选Enable new UI&#xff0c;restart就可以回到最新版本的UI了 新风格

wu-framework-parent 项目明细

wu-framework-parent 介绍 springboot 版本3.2.1 wu-framework-parent 是一款由Java语言开发的框架&#xff0c;目标不写代码但是却能完成功能。 框架涵盖无赖ORM( wu-database-lazy-starter)、仿生组件 、easy框架系列【Easy-Excel、easy-listener、easy-upsert】 授权框架(…

【C++进阶】STL容器--list底层剖析(迭代器封装)

目录 前言 list的结构与框架 list迭代器 list的插入和删除 insert erase list析构函数和拷贝构造 析构函数 拷贝构造 赋值重载 迭代器拷贝构造、析构函数实现问题 const迭代器 思考 总结 前言 前边我们了解了list的一些使用及其注意事项&#xff0c;今天我们进一步深入…

对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

从铺天盖地的市场消息来看&#xff0c;华为即将面世的鸿蒙NEXT系统已经势不可挡了 想必大家都已经迫不及待地想要进行尝试。 估计大家都有着同样的疑问&#xff1a; 会不会是下一个风口&#xff1f;转鸿蒙应用开发难吗&#xff1f; 会不会是下一个风口&#xff1f; 自从鸿蒙…

C++:类与对象(3)

创作不易&#xff0c;感谢三连 一、深入解析构造函数 如上图&#xff0c;在一般情况下&#xff0c;我们认为A类中的_a1和_a2只不过是声明&#xff0c;并没有开空间&#xff0c;而真正的空间开辟是在【定义】的时候&#xff0c;也就是我们根据这个类实例化出整个对象的时候。 …

高级RAG:从理论到 LlamaIndex 实现,解决原始 RAG 管道的局限性

原文地址&#xff1a;Advanced Retrieval-Augmented Generation: From Theory to LlamaIndex Implementation 如何通过在 Python 中实现有针对性的高级 RAG 技术来解决原始 RAG 管道的局限性 2024 年 2 月 19 日 如何通过在 Python 中实现有针对性的高级 RAG 技术来解决 naiv…

【LeetCode刷题】146. LRU 缓存

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否则返回 -…