【C++】STL——vecot的模拟实现

embedded/2024/9/19 22:47:29/ 标签: c++, 开发语言

目录

  • 前言
  • 总体结构
  • 默认成员函数
    • 构造函数
    • 拷贝构造
    • 赋值重载
    • 析构函数
  • vector的相关容量空间以及访问的实现
    • capacity()和size()
    • 迭代器实现
    • operator[]
    • reserve
  • vector类对象的修改操作
    • 尾插尾删
    • 任意位置插入
    • 任意位置删除
    • 交换和清理

请添加图片描述

前言

  前面我们已经学习了解了vector重要接口的使用:【C++】vector的使用。
  下面我们继续来模拟实现vector,这对学习STL有重大帮助。

总体结构

  同样,vetor在标准库中已经实现好了,因此在模拟实现时我们可以使用命名空间进行隔离,防止命名冲突
  在vector的使用中我们就已经介绍过源码中的vector是如何写的:

namespace bit
{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;};
}

  和在c语言中的动态顺序表不太一样,vector是由三个指针变量来实现的
在这里插入图片描述

默认成员函数

构造函数

在这里插入图片描述
对照着库里面的构造函数可以模拟实现如下:

//强制生成默认构造
vector() = default;//构造
//函数模板——支持任意迭代器的迭代区间进行初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);first++;}
}
vector(size_t n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}
vector(int n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}

注意:
1.当所写函数中没有默认构造时,我们就可以写上一句:

vector() = default;

即可强制编译器生成默认构造,当然直接自己写vector(){}也可以。


2.vector(size_t n, const T& val = T())它的缺省值给的是T(),即匿名对象
我们知道,对匿名对象如果是自定义类型,就调用它的默认构造。如果是内置类型C++对内置类型进行了升级,让其也有默认构造
如:

int k = int();//也是正确的

3.我们还要再写一个vector(int n, const T& val = T())是因为如果不写时,我们写vector< int > v(3,5)时会优先匹配上迭代器构造,此时InputIterator会被替代成为int,而解引用就会报错:

error C2100: 非法的间接寻址

拷贝构造

  在进行拷贝构造前先扩容,可以避免后期频繁扩容会存在过多的内存碎片。

//拷贝构造v3(v2)
vector(const vector<T>& v)
{reserve(v.capacity());for (auto e : v){push_back(e);}//两种写法都可以/*for (size_t i = 0; i < v.size(); i++){push_back(v[i]);}*/
}

  逐个数据赋值拷贝,属于深拷贝,可以避免浅拷贝的问题。

赋值重载

//传统写法
/*vector<T>& operator=(const vector<T>& v)
{if (this != &v){reserve(v.capacity());for (size_t i = 0; i < v.size(); i++){_start[i] = v[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;
}*///现代写法,都交给编译器去完成
//v1=v3
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}

  对于现代写法,我们使用传值传参对于自定义类型,编译器会调用它的拷贝构造,即v是v3的一个拷贝构造,直接交换,可使得v1的值换给v,v是一个形参,出了作用域会调用析构函数销毁

析构函数

~vector()
{if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}
}

  我们前面在构造开空间时用的是new[],因此在析构时也要使用delete[]

vector的相关容量空间以及访问的实现

capacity()和size()

  对于vector的一系列增删查改都离不开对空间的判断,看容量是否充足,若不充足则需要扩容,即reserve。
  对于vector而言,获取数据个数size()和容量大小capacity()都还是比较简单的:

size_t capacity() const
{return _end_of_storage - _start;
}
size_t size()const
{return _finish - _start;
}

  vector的三个成员变量都是指针,因此我们直接采用指针-指针的方式即可得到相应的数据。

迭代器实现

  同样,迭代器也是比较简单的,我们可以将其认定为T*的一个原生指针即可,同样还有const迭代器,即const对象对应的只能访问不能修改。

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

operator[]

迭代器主要实现访问的功能,那operator[]也不能少,也比较简单:

T& operator[](size_t n)
{return _start[n];
}

reserve

  当我们想要增加数据时,我们就要判断我们的容量空间是否足够,如果不足就需要先扩容再插入数据,当然这涉及到深浅拷贝的问题:

void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];//开辟新空间size_t oldsize = size();//不能省略if (_start){//memcpy(tmp, _start, sizeof(T) * oldsize);//浅拷贝不能使用for(size_t i = 0; i < oldsize; i++){tmp[i] = _start[i];//深拷贝}delete[] _start;//释放旧空间}_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}
}

注意:为什么说对oldsize的定义不能省略
是因为在size()的函数定义中,它是由_start和_finish两个指针相减而来。而后面_start已经到了新空间,而_finish还是属于旧空间上的指针,二者相减就会报错。

在这里插入图片描述
  通过调试可以发现,对于memcpy是浅拷贝会使得tmp和_start的指针同时指向同一块空间,析构则会析构两次而崩溃,因此还是要逐个数据赋值拷贝。

vector类对象的修改操作

尾插尾删

void push_back(const T& x)
{if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;_finish++;
}
void pop_back()
{assert(size() > 0);--_finish;
}

  在尾插时要注意判断空间是否足够,不够还要记得扩容。
  尾删时也要判断size是否大于0,避免删除过多导致崩溃。

任意位置插入

iterator insert(iterator pos, const T& x)
{if (_finish == _end_of_storage){size_t len = pos - _start;//不能省略iterator newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;//更新pos,避免变成野指针}iterator end = _finish - 1;while (pos <= end){*(end + 1) = *end;--end;}*pos = x;_finish++;return pos;
}

对于insert可以认为是以下几个步骤:

  1. 判断数据是否需要扩容
  2. 往后挪动数据
  3. 插入数据

  而对于size_t len = pos - _start;pos = _start + len;这两行代码是不能省略的,因为牵扯到扩容,不更新pos就会导致迭代器失效的问题。

任意位置删除

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

  任意位置的删除只需要将该位置后面的数据挪动覆盖即可,同样存在着迭代器pos失效的问题,这在上一节就已经说了,因此我们要有返回值返回被删除元素的下一个元素即可

交换和清理

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}
void clear()
{erase(begin(), end());
}

  库里面其实已经提供了std::swap函数,但vector还是有自己的swap函数,是因为库里面的交换函数涉及到拷贝较多,效率较低,而vector自己的交换函数只需要交换指针即可,没有拷贝,效率高


感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。
请添加图片描述


http://www.ppmy.cn/embedded/113942.html

相关文章

【原创】java+swing+mysql长途客车售票管理系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片&#xff0c;希望和大家…

如何使用ssm实现基于vue.js的购物商场的设计与实现+vue

TOC ssm616基于vue.js的购物商场的设计与实现vue 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本…

「Netmarble 小镇」活动来了:踏上穿越标志性世界的旅程!

欢迎来到 Netmarble 小镇&#xff01;本次活动从 9 月 13 日持续到 10 月 11 日&#xff0c;是你们体验 Netmarble 著名游戏世界最精彩内容的入口。在为期一个月的庆祝活动中&#xff0c;你们将体验到独家内容、惊险刺激的挑战和全新人物化身的发布&#xff01; 探索 Netmarble…

(黑马点评)八、实现签到统计和uv统计

8.1 签到统计系列功能 8.1.1 认识BitMap结构 BitMap是Redis基于String实现的一种高效的二进制数位的数据结构。因此一个BItMap的最大上线为512M&#xff0c;转为bit位可表示 2^32位 常见命令 SETBIT&#xff1a;向指定位置&#xff08;offset&#xff09;存入一个0或1 GETBIT …

c语言动态内存分配

前言 我们已经掌握的内存开辟⽅式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的⽅式有两个特点&#xff1a; • 空间开辟⼤⼩是固定的。 • 数组在申明的时候&#xff0c;必须指定数组的…

什么?blender可以云渲染了!

在数字艺术和动画制作的世界里&#xff0c;Blender一直是自由和开源软件的佼佼者。它以其强大的功能和灵活性&#xff0c;赢得了全球艺术家和设计师的青睐。但你知道吗&#xff1f;现在&#xff0c;Blender也可以通过渲染 101云渲染来提高渲染效率了&#xff01; 渲染101云渲染…

Python 数学建模——Fitter 拟合数据样本的分布

文章目录 介绍代码实例 介绍 数学建模中很多时候&#xff0c;我们有某个随机变量 X X X 的若干样本 X 1 , X 2 , ⋯ , X n X_1,X_2,\cdots,X_n X1​,X2​,⋯,Xn​&#xff0c;想要还原随机变量 X X X 的概率密度函数 f ( x ) f(x) f(x)。诚然&#xff0c;高斯核密度估计可以…

solidity-19-fallback

接收ETH receive和fallback receive和callback是solidity中两个特殊的回调函数&#xff0c;一个处理接收ETH,一个处理不存在的函数调用。本质上就是吧fallback拆成了两个回调函数。我暂时不知道什么是fallback fallback调用不存在的函数时会被调用也就是这个函数是不是等价于…

Python基础(八)——MySql数据库

一.数据库 【库——>表——>数据】 借助数据库对数据进行组织存储&#xff0c;借助SQL语言对数据库、数据进行操作管理 Mysql数据库 下载&#xff1a;https://www.mysql.com/ 查看是否安装配置成功&#xff1a; 安装DBeaver用于Mysql数据库图形化 安装&#xff1a;…

MySQL——数据库的高级操作(一)数据备份与还原(1)数据的备份

在操作数据库时&#xff0c;难免会发生一些意外造成数据丢失。例如&#xff0c;突然停电、管理员的操作失误都可能导致数据的丢失。为了确保数据的安全&#xff0c;需要定期对数据库进行备份&#xff0c;这样&#xff0c;当遇到数据库中数据丢失或者出错的情况&#xff0c;就可…

Keil MDK5学习记录

2024.9.19 1. no browse information available in ‘xxx’的问题 成功解决Keil MDK5中no browse information available in ‘xxx’的问题-CSDN博客https://blog.csdn.net/bean_business/article/details/1091894452. .c文件中显示函数列表 如何在Keil5里.c文件中显示函数列表…

LeeCode打卡第二十九天

LeeCode打卡第二十九天 第一题&#xff1a;岛屿数量&#xff08;LeeCode第200题&#xff09;: 给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。岛屿总是被水包围&#xff0c;并且每座岛屿只…

Tcp三次握手四次挥手和SSL/TLS

1.Tcp三次握手四次挥手&#xff1a; 1.1基本概念&#xff1a; TCP&#xff08;三次握手和四次挥手&#xff09;是用于建立和终止可靠传输连接的过程。TCP协议是一种面向连接的传输层协议&#xff0c;确保数据在网络上可靠、有序地传输。下面详细解释三次握手和四次挥手的工作机…

浸没边界法精度相关的论文的阅读笔记

Convergence proof of the velocity field for a stokes flow immersed boundary method https://doi.org/10.1002/cpa.20233 研究对象的选取 他这里为什么能够选取一个周期性边界的流场啊&#xff1f;为什么不是狄利克雷边界或者诺伊曼边界&#xff1f; 方形流场的边界值 …

高级算法设计与分析 学习笔记6 B树

B树定义 一个块里面存了1000个数和1001个指针&#xff0c;指针指向的那个块里面的数据大小介于指针旁边的两个数之间 标准定义&#xff1a; B树上的操作 查找B树 创建B树 分割节点 都是选择正中间的那个&#xff0c;以免一直分裂。 插入数字 在插入的路上就会检查节点需不需要…

Testbench编写与Vivado Simulator的基本操作

Testbench编写与Vivado Simulator的基本操作 Testbench编写 Testbench 是一种用Verilog或者systemVerilog语言编写的程序或模块&#xff0c;编写testbench的主要目的是为了对使用硬件描述语言&#xff08;HDL&#xff09;设计的电路UUT(unit under test)进行仿真验证&#xf…

一招教你解决excel表格打印预览时候表格线条显示不全的问题

1、如图&#xff0c;我们在制作好excel表格后再需要打印时候&#xff0c;点击打印预览会出现以下情况&#xff1a; 最下边的表格线条显示不全&#xff0c;这样即使打印出来或者导出为pdf&#xff0c;文件中依然显示不全&#xff0c;这时候我们只需要在excel表格中轻轻设置一下就…

CleanMyMac X 4.15.6正式版 mac直装破解版

你知道 CleanMyMac是什么吗&#xff1f;它的字面意思为“清理我的Mac”&#xff0c;作为软件&#xff0c;那就是一款 Mac清理工具 &#xff0c;Mac OS X 系统下知名系统清理软件&#xff0c;是数以万计的Mac用户的选择。它可以流畅地与系统性能相结合&#xff0c;只需…

JVM 运行时数据区域

目录 前言 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 前言 首先, java程序在被加载在内存中运行的时候, 会把他自己管理的内存划分为若干个不同的数据区域, 就比如你是一个你是一个快递员, 一堆快递过来需要你分拣, 这个时候, 你就需要根据投放的目…

三、(JS)JS中常见的表单事件

一、onfocus、onblur事件 这个很容易理解&#xff0c;就不解释啦。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&…