Cpp::STL—vector类的模拟实现(11)

文章目录

  • 前言
  • 一、各函数接口总览
  • 二、默认成员函数
    • vector();
    • vector(size_t n, const T& val = T( ));
    • template< class InputIterator> vector(InputIterator first, InputIterator last);
    • vector(const vector<T>& v);
    • vector<T>& operator=(const vector<T>& v);
    • vector<T>& operator=(const vector<T> v);
    • ~vector();
  • 三、迭代器相关函数
  • 四、容量和大小有关函数
    • size & capacity
    • reserve
      • 野指针问题
      • 浅拷贝问题
    • resize
    • empty
  • 五、增删查改有关函数
    • push_back
    • pop_back
    • insert
    • erase
    • swap
  • 六、访问容器相关函数
    • operator[ ]
    • front & back
  • 总结


前言

  我们来实现一下vector吧!
  这会很有意思的!


一、各函数接口总览

  不如先来看看我们要实现的接口,请注意!实际实现并未声明和定义分离,因为这涉及到模板的一些特性,我们先按下不表,后面再做解释,且为了避免与库里的vector相冲突,我们要用命名空间包起来!

namespace HQ
{//模拟实现vectortemplate<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector();                                           //构造函数vector(size_t n, const T& val);                     //构造函数template<class InputIterator>                      vector(InputIterator first, InputIterator last);    //构造函数vector(const vector<T>& v);                         //拷贝构造函数vector<T>& operator=(const vector<T>& v);           //赋值运算符重载函数~vector();                                          //析构函数//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//容量和大小相关函数size_t size() const;size_t capacity() const;void clear()void reserve(size_t n);void resize(size_t n, const T& val = T());bool empty() const;//修改容器内容相关函数void push_back(const T& x);void pop_back();iterator insert(iterator pos, const T& x);iterator erase(iterator pos);void swap(vector<T>& v);//访问容器相关函数T& front();const T& front(); constT& back();const T& back(); constT& operator[](size_t i);const T& operator[](size_t i)const;private:iterator _start;        // 指向容器的头iterator _finish;       // 指向有效数据的尾iterator _endofstorage; // 指向容器的尾};template<class T>void print_vector(const vector<T>& v); 
}

二、默认成员函数

vector();

很显然的无参构造,这时候只需要将三个成员变量初始化为空指针即可:

vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}

vector(size_t n, const T& val = T( ));

我们可以先使用reserve函数将容器容量先设置为n,然后使用push_back函数尾插n个值为val的数据到容器当中即可,并且正如我们上篇说的一样,最好再重载一个vector(int n, const T& val = T( )); 版本:

vector(size_t n, const T& val):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{reserve(n); // 调用reserve函数将容器容量设置为nfor (size_t i = 0; i < n; i++) // 尾插n个值为val的数据到容器当中{push_back(val);}
}

template< class InputIterator> vector(InputIterator first, InputIterator last);

因为该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的,所以我们这里需要将该构造函数设计为一个函数模板:

// 请注意,接下来的接口将不再给出初始化列表
// 事实上,运用缺省值这里会更方便一些
template<class InputIterator> // 模板函数
vector(InputIterator first, InputIterator last)
{// 将迭代器区间在[first,last)的数据一个个尾插到容器当中while (first != last){push_back(*first);++first;}
}

vector(const vector& v);

vector(const vector<T>& v)
{reserve(v.capacity());for (const auto& e : v){push_back(e);}
}

vector& operator=(const vector& v);

//传统写法
vector<T>& operator=(const vector<T>& v)
{if (this != &v) // 防止自己给自己赋值{delete[] _start; // 释放原来的空间_start = new T[v.capacity()]; // 开辟一块和容器v大小相同的空间// 注意这里不能使用memcpy函数进行拷贝!// 原因是memcpy是值拷贝,浅拷贝!for (size_t i = 0; i < v.size(); i++) // 将容器v当中的数据一个个拷贝过来{_start[i] = v[i];}_finish = _start + v.size(); // 容器有效数据的尾_end_of_storage = _start + v.capacity(); // 整个容器的尾}return *this; //支持连续赋值
}

vector& operator=(const vector v);

事实上,赋值运算符重载还有一个非常狂野的写法,我们看这种方式舍弃了引用传值,先来个拷贝构造,接着再跟this交换数值空间,又因为v是临时变量,出作用域后立马销毁,非常巧妙,有种借力打击的感觉!:

vector<T>& operator=(vector<T> v)
{if (this != &v) // 防止自己给自己赋值{swap(v);}return *this;
}

~vector();

对容器进行析构时,首先判断该容器是否为空容器,若为空容器,则无需进行析构操作,若不为空,则先释放容器存储数据的空间,然后将容器的各个成员变量设置为空指针即可:

~vector()
{if (_start) //避免对空指针进行释放{delete[] _start; _start = _finish = _end_of_storage = nullptr;}
}

三、迭代器相关函数

vector当中的迭代器实际上就是容器当中所存储数据类型的指针:

typedef T* iterator;
typedef const T* const_iterator;

begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址,而我们还需要重载一对适用于const对象的begin和end函数,使得const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作,而不能进行修改:

iterator begin()
{return _start; // 返回容器的首地址
}iterator end()
{return _finish; // 返回容器当中有效数据的下一个数据的地址
}const_iterator begin() const
{return _start; // 返回容器的首地址
}const_iterator end() const
{return _finish; // 返回容器当中有效数据的下一个数据的地址
}

四、容量和大小有关函数

size & capacity

由于两个指针相减的结果,就是这两个指针之间对应类型的数据个数,所以利用三个原生指针,我们可以很显然的得出 size 和 capacity

size_t size() const
{return _finish - _start; // 返回容器当中有效数据的个数
}size_t capacity() const
{return _endofstorage - _start; // 返回当前容器的最大容量
}

reserve

void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//memcpy(tmp, _start, old_size * sizeof(T)); // errfor (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}

我们要注意两个问题:

野指针问题

迭代器失效的问题,出现野指针_finish指向错误。在delete[ ] _start的时候,_finish还在指向旧空间,导致调用size()得到数据错误,我们的解决办法就是在开辟新空间之前,保存一下旧size()的值,存在临时变量old_size里面
在这里插入图片描述

浅拷贝问题

memcpy是逐字节拷贝,属于浅拷贝。当T为自定义类型,使用memcpy进行浅拷贝操作,会指向同一块空间,我们的解决办法是利用赋值运算符,就算是自定义类型,也有自己的赋值构造函数
在这里插入图片描述

resize

根据resize函数的规则,进入函数我们可以先判断所给n是否小于容器当前的size,若小于,则通过改变_finish的指向,直接将容器的size缩小到n即可,否则先判断该容器是否需要增容,然后再将扩大的数据赋值为val即可。

那等于呢?等于怎么办?
那就不动呗~直接跳出

// 在C++当中内置类型也可以看作是一个类,它们也有自己的默认构造函数
// 所以在给resize函数的参数val设置缺省值时,设置为T()即可
void resize(size_t n, const T& val = T())
{if (n < size()) // 当n小于当前的size时{_finish = _start + n; // 将size缩小到n}else if (n > capacity()){reserve(n);while (_finish < _start + n) // 将size扩大到n{*_finish = val;_finish++;}}
}

empty

empty函数可以直接通过比较容器当中的_start和_finish指针的指向来判断容器是否为空,若所指位置相同,则该容器为空

bool empty()
{return _start == _finish;
}

五、增删查改有关函数

push_back

要尾插数据首先得判断容器是否已满,若已满则需要先进行增容,然后将数据尾插到_finish指向的位置,再将_finish++即可

void push_back(const T& x)
{// 扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}

pop_back

尾删数据之前也得先判断容器是否为空,若为空则做断言处理,若不为空则将_finish- -即可

//尾删数据
void pop_back()
{assert(!empty()); // 容器为空则断言--_finish; // _finish指针前移
}

insert

insert函数可以在所给迭代器pos位置插入数据,在插入数据前先判断是否需要增容,然后将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入,最后将数据插入到pos位置即可

iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);// 扩容if (_finish == _end_of_storage){// 若需要增容,则需要在增容前记录pos与_start之间的间size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}

erase

erase函数可以删除所给迭代器pos位置的数据,在删除数据前需要判断容器释放为空,若为空则需做断言处理,删除数据时直接将pos位置之后的数据统一向前挪动一位,将pos位置的数据覆盖即可

iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()){*(it - 1) = *it;++it;}--_finish;return pos;
}

swap

直接调用库当中的swap函数将两个容器当中的各个成员变量进行交换即可

void swap(vector<T>& v)
{// 根据就近原则,所以你需要加上个std::std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}

六、访问容器相关函数

operator[ ]

vector也支持我们使用“下标+[ ]”的方式对容器当中的数据进行访问,实现时直接返回对应位置的数据即可

T& operator[](size_t i)
{assert(i < size()); // 检测下标的合法性return _start[i]; // 返回对应数据
}const T& operator[](size_t i) const
{assert(i < size()); // 检测下标的合法性return _start[i]; // 返回对应数据
}

front & back

很简单,也是直接利用原生指针来返回即可

T& front()
{return *_start;
}const T& front() const
{return *_start;
}T& back()
{return *(_finish - 1);
}const T& back() const
{return *(_finish - 1);
}

总结

  怎么样,接下来我们就要迎来不太一样的list了哦!

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

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

相关文章

Oracle exadata存储节点更换内存操作及报错处理

1.报错信息 在进行Oracle exadata巡检时&#xff0c;发现cell节点有一根内存报错&#xff0c;报错信息如下&#xff1a; 报错内存位置为&#xff1a;CPU1 P1/D2槽位 报错内存信息&#xff1a; 根据报错信息确认内存PN号、大小等息&#xff0c;并将信息反馈公司&#xff0c;及…

【java数据结构】顺序表

【java数据结构】顺序表 一、了解List接口二、顺序表2.1 线性表2.2 顺序表2.2.1 顺序表接口的实现给数组增加新元素判断数组数据是否为满在 pos 位置新增元素判定是否包含某个元素查找某个元素对应的位置获取 pos 位置的元素给 pos 位置的元素设为 value删除第一次出现的关键字…

数据结构:将复杂的现实问题简化为计算机可以理解和处理的形式

整句话的总体意义是&#xff0c;**数据结构是用于将现实世界中的实体和关系抽象为数学模型&#xff0c;并在计算机中表示和实现的关键工具**。它不仅包括如何存储数据&#xff0c;还包括对这些数据的操作&#xff0c;能够有效支持计算机程序的运行。通过这一过程&#xff0c;数…

语言模型发展史

四个阶段 第一阶段&#xff1a;基于规则和统计的语言模型 由人工设计特征并使用统计方法对固定长度的文本窗口序列进行建模分析&#xff0c;这种建模方式也被称为N-gram语言模型。 优点&#xff1a; 1&#xff09;采用极大似然估计, 参数易训练 2&#xff09;完全包含了前n-…

Spring(学习笔记)

<context:annotation-config/>是 Spring 配置文件中的一个标签&#xff0c;用于开启注解配置功能。这个标签可以让 Spring 容器识别并处理使用注解定义的 bean。例如&#xff0c;可以使用 Autowired 注解自动装配 bean&#xff0c;或者使用 Component 注解将类标记为 bea…

虚拟机三种网络模式详解

在电脑里开一台虚拟机&#xff0c;是再常见不过的操作了。无论是用虚拟机玩只有旧版本系统能运行的游戏&#xff0c;还是用来学习Linux、跑跑应用程序都是很好的。而这其中&#xff0c;虚拟机网络是绝对绕不过去的。本篇文章通俗易懂的介绍了常见的虚拟网络提供的三种网络链接模…

鸿蒙OpenHarmony

开源鸿蒙系统编译指南 Ubuntu编译环境配置第一步&#xff1a;Shell 改 Bash第二步&#xff1a;安装Git和安装pip3工具第三步&#xff1a;远程仓配置第四步&#xff1a;拉取代码第五步&#xff1a;安装编译环境第六步&#xff1a;本地编译源码 Windows开发环境配置第一步&#x…

dubbo微服务

一.启动nacos和redis 1.虚拟机查看是否开启nacos和redis docker ps2.查看是否安装nacos和redis docker ps -a3.启动nacos和redis docker start nacos docker start redis-6379 docker ps二.创建三个idea的maven项目 1.第一个项目dubboapidemo 2.1.1向pom.xml里添加依赖 …

x-cmd pkg | qrencode - 命令行生成二维码,小白也能轻松上手!

目录 简介首次用户功能特点竞品和相关项目进一步阅读 简介 qrencode 是一个用于生成二维码的命令行工具。它可以将文本、URL、电话号码等信息转换为二维码图像。生成的二维码图像可以保存为图片文件&#xff0c;方便在电子文档、网页、移动应用等各种场景中使用。 它支持的二维…

深入理解 Solidity 中的支付与转账:安全高效的资金管理攻略

在 Solidity 中&#xff0c;支付和转账是非常常见的操作&#xff0c;尤其是在涉及资金的合约中&#xff0c;比如拍卖、众筹、托管等。Solidity 提供了几种不同的方式来处理 Ether 转账&#xff0c;包括 transfer、send 和 call&#xff0c;每种方式的安全性、灵活性和复杂度各有…

SKD4(note上)

微软提供了图形的界面API&#xff0c;叫GDI 如果你想画某个窗口&#xff0c;你必须拿到此窗口的HDC #include <windows.h> #include<tchar.h> #include <stdio.h> #include <strsafe.h> #include <string>/*鼠标消息 * 键盘消息 * Onkeydown * …

STM32 软件触发ADC采集

0.91寸OLED屏幕大小的音频频谱&#xff0c;炫酷&#xff01; STM32另一个很少人知道的的功能——时钟监测 晶振与软件的关系&#xff08;深度理解&#xff09; STM32单片机一种另类的IO初始化方法 ADC是一个十分重要的功能&#xff0c;几乎任何一款单片机都会包含这个功能&a…

阿里云 SAE Web:百毫秒高弹性的实时事件中心的架构和挑战

作者&#xff1a;胡志广(独鳌) 背景 Serverless 应用引擎 SAE 事件中心主要面向早期的 SAE 控制台只有针对于应用维度的事件&#xff0c;这个事件是 K8s 原生的事件&#xff0c;其实绝大多数的用户并不会关心&#xff0c;同时也可能看不懂。而事件中心&#xff0c;是希望能够…

JS进阶 3——深入面向对象、原型

JS 进阶3——深入面向对象、原型 1.编程思想 面向过程&#xff1a;分析出解决问题的过程&#xff0c;然后用函数将这些步骤一步步封装起来面向对象&#xff1a;将事物分为一个个对象&#xff0c;然后对象之间分工合作 2.构造函数&#xff1a;封装性、面向对象 构造函数方法存…

linux学习--第七天(多路复用IO)

多路复用IO -阻塞IO与非阻塞IO -IO模型 IO的本质时基于操作系统接口来控制底层的硬件之间数据传输&#xff0c;并且在操作系统中实现了多种不同的IO方式&#xff08;模型&#xff09;比较常见的有下列三种&#xff1a; 1.阻塞型IO模型 2.非阻塞型IO模型 3.多路复用IO模型 -阻…

开源项目 - 交通工具检测 yolo v3 物体检测 单车检测 车辆检测 飞机检测 火车检测 船只检测

开源项目 - 交通工具检测 yolo v3 物体检测 单车检测 车辆检测 飞机检测 火车检测 船只检测 开源项目地址&#xff1a;https://gitcode.net/EricLee/yolo_v3 示例&#xff1a;

【C++】多态(下)

个人主页~ 多态&#xff08;上&#xff09;~ 多态 四、多态的原理1、虚表的存储位置2、多态的原理3、动态绑定和静态绑定 五、单继承和多继承关系的虚函数表1、单继承中的虚函数表2、多继承中的虚函数表 六、多态中的一些小tips 四、多态的原理 1、虚表的存储位置 class A {…

开放式耳机哪个品牌好?分享几款不错的开放式蓝牙耳机

相信很多人戴入耳式耳机时间一久&#xff0c;就不是很舒服。经常会有闷热、不透气的感觉&#xff0c;甚至有的朋友会因为佩戴入耳式耳机滋生细菌&#xff0c;导致最后炎症的发生。总之&#xff0c;入耳式耳机真的不适合长时间佩戴&#xff0c;而且佩戴的场景也有很多限制。 那…

一文了解构建工具——Maven与Gradle的区别

目录 一、Maven和Gradle是什么&#xff1f; 构建工具介绍 Maven介绍 Gradle介绍 二、使用时的区别&#xff1a; 1、新建项目 Maven&#xff1a; Gradle&#xff1a; 2、配置项目 Maven&#xff1a; Gradle&#xff1a; 3、构建项目——生成项目的jar包 Gradle&…

Linux 信号详解

目录 一.前置知识 1.前台进程和后台进程 a.概念理解 b.相关指令 2.信号的前置知识 a.Linux 系统下信号的概念 b.进程对信号的处理方式 3.信号的底层机制 二.详解信号 1.信号的产生 a.键盘组合键 b.kill 指令和系统调用接口 ① kill 指令 ② kill() 系统调用接口 ③ raise() 系统…