【C++】STL中vector常见功能的模拟实现

前言:在上一篇中我们讲到了Vector的一些常见功能的使用方式,今天为了进一步的去学习Vector和能够更深度的去理解Vector的一些底层的原理。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • Vector的大体框架
  • Vector中的构造函数
    • 无参构造
    • 拷贝构造(传统写法)
    • 析构函数
    • swap函数
    • 赋值重载operator=
  • Vector容器中的遍历方式
    • operator[]遍历容器
    • 迭代器的遍历和范围for
    • 打印函数print_vector(const vector<T>& v)
  • Vector容器中相关的增删查改的函数
    • 查看容器中有效元素的个数- size()
    • 查看容器中国的容量的大小- capacity()
    • 调整和检查容量的大小- reserve(size_t n)
    • 调整向量的大小-resize(size_t n, const T& value = T())
    • 尾插数据- push_back(const T& val)
    • 尾删数据-pop_back()
    • 插入数据-insert(iterator pos,const T& val)
    • 删除数据- erase(iterator pos)
    • 查看容器中的数据是否为空Empty()
  • vector中构造函数的一点延伸
    • 拷贝构造的现代写法
    • 区间构造函数
    • 列表初始化构造(c++11以上的写法)
  • 总体代码

Vector的大体框架

在vector中本质是由三个指针变量组成的,且vector中可以存储各种类型的对象,因此我们使用模板在这里插入图片描述

namespace bit
{template<class T>//因为在使用的时候Vector中可以是任意类型所以我们调用模板class vector{public:typedef T* iterator;//迭代器typedef const T* const_iterator;private:iterator _start = nullptr; //容器的起始位置  // T* _startiterator _finish = nullptr;//容器中所使用的元素的最后一个位置iterator _endofstorage = nullptr;//容器中所有元素的最后一个位置};
}

Vector中的构造函数

无参构造

我们也可以直接在成员变量中对其直接给初始值,不写这个无参构造函数也是可以的。

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

拷贝构造(传统写法)

在拷贝构造中有两种写法,一个传统写法一个现代写法,由于传统写法要调用的函数我们得到后面才会去实现,所以现代写法作者就先放在后面了,这里先写的传统写法.

  1. 如果我们不自己实现,让编译器去实现,就是浅拷贝,会出现问题,故需我们自己实现深拷贝,v2(v1),即只要让v2拷贝一份新的数据即可(使v2和v1不是指向同一份数据)。
  2. 因此我们需要先开辟一块和需要拷贝的对象的同样大的空间,让新的对象指向这一块空间,在把值依次拷贝过去。(代码如下)
vector(const vector<T>& V) 
{//传统写法_start = new T[V.capacity()];_finish = _start;//让_finish指向现在的位置,然后依次进行赋值_endofstorage = _start + V.capacity();for (size_t i = 0; i < V.size(); i++){*_finish = V[i];_finish++;}//此时finish指向的就是最后一个有效的元素的后一个位置
}

析构函数

关于析构函数这里就不过多的介绍了,就是把容器给销毁,然后置空即可。

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

swap函数

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}

赋值重载operator=

代码思路:

  1. 传过来的对象会产生一个临时变量,临时变量会有一块独立的空间,这块空间和原本需要的值进行交换即达到了深拷贝的效果,然后返回*this即可达到连续赋值的效果。
vector<T>& operator=(vector<T> v)//现代写法
{swap(v);return *this;
}

Vector容器中的遍历方式

operator[]遍历容器

1.我们通过运算符重载可以写出两个operator的容器的遍历方式,一种是可以对其进行修改和操作,一种是只可以对其进行操作不可以修改

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

迭代器的遍历和范围for

当然了,迭代器也有可读和可写的两个版本(如下所示),且我们在前面的学习中知道,范围for的本质也就是迭代器进行遍历,只要有迭代器我们就能实现范围for了。

iterator begin()//返回起始位置
{return _start;
}iterator end()//结束位置
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end()const
{return _finish;
}
void test2()
{vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);cout << "迭代器遍历" << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}cout << endl;cout << "范围for遍历" << endl;for (auto e : v1)cout << e << " ";cout << endl;
}

在这里插入图片描述


打印函数print_vector(const vector& v)

void print_vector(const vector<T>& v)
{for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}

Vector容器中相关的增删查改的函数

查看容器中有效元素的个数- size()

代码思路:_finsh指向最后一个元素的后一个元素,_start指向第一个元素,两个指针相减就是元素的个数。

size_t size()const //元素个数
{return _finish - _start;
}

查看容器中国的容量的大小- capacity()

这里和上面的size()函数同理就不过多的解释了。

size_t capacity()const //总的容量
{return _endofstorage - _start;
}

调整和检查容量的大小- reserve(size_t n)

代码思路:

  1. 我们首先需要看传过来的大小是否比原本的容量还大,如果比原本的容量还大我们就需要对其进行扩容,比他小我们就不需要处理,因为通常都不会进行缩容的操作。
  2. 在前面string类的模拟实现的时候我们知道在C++中是没有realloc这样的函数的,因此我们只能开辟一块新的空间,然后将原本的值拷贝过去,在将旧空间的指针指向新空间,在释放掉旧的空间。
  3. 这里我们需要注意的是,我们需要将原本旧的空间的size记录下来,因为在前面的size函数中是元素的个数是_finish - _start,此时_start指向了一块新的空间,如果直接对他们相减就会导致越界等问题的发生(如下图所示)。
    在这里插入图片描述
void reserve(size_t n)//调整和检查容量
{if (capacity() < n){T* tmp = new T[n];size_t old_size = size();//因原本的_start在后面会改变,放在野指针的出现我们需要对old_size进行存储for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;//用之前记录的size来更新_finsh_endofstorage = tmp + n;}
}

调整向量的大小-resize(size_t n, const T& value = T())

代码思路:
1.这里同理,我们需要看传过来的容量是否比原本的大,大的话我们就进行扩容的操作,如果使用者指定了value的值,就将扩容的空间的值全部指定成使用者指定的值,没有指定就使用默认值,小的话对原本的_finish进行更新即可。
2. 这里很多人不懂的是为什么使用匿名对象T(),在前面我们说过自定义类型会调用它的默认构造函数去初始化他的匿名对象,但是T是内置类型的话也会有自己的默认构造函数,如下图所示。
在这里插入图片描述

void resize(size_t n, const T& value = T())//使用半缺省,扩容的话就把剩余的部分全部扩容成value
{if (n > size()){reserve(n);while (_finish < _start + n){*_finish = value;_finish++;}}else{_finish = _start + n;}
}

尾插数据- push_back(const T& val)

代码思路:

  1. 同理们在插入数据的时候需要查看空间的大小是否足够,如果不够扩容即可。
  2. 直接在原本的_finish进行赋值,再让其往后更新一位即可。
void push_back(const T& val)
{//insert(end(), val);if (_endofstorage == _finish)//判断容量是否是满的{reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;_finish++;}

尾删数据-pop_back()

代码思路:

  1. 我们需要判断是否还有数据,如果没有数据就不能进行删除
  2. 我们直接和之前顺序表一样的玩法,让其尾部往前移动一位即可。
void pop_back()
{assert(_finish > _start);--_finish;
}

插入数据-insert(iterator pos,const T& val)

代码思路:

  1. 同理我们需要判断是否需要扩容,如果需要扩容的话我们需要注意的是在调用reserve函数时候会对底层的_start和_finish进行修改,所以我们需要对原本的长度进行记录,否则就无法知道原本的长度了。
  2. 此时我们要插入的位置,就是新的_start+len此时的位置了。
  3. 这里我们找到尾部的位置让pos后的位置依次往后移动一位即可,然后再将需要插入的值赋值给pos的位置即可完成插入。
void insert(iterator pos,const T& val)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage)//判断是否需要扩容{size_t len = pos - _start;//同理需要记录原本的长度,因为在reserver后会对原本的_start进行修改reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容了的话原本的_start//如果扩容了就要更新pospos = len + _start;}iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = val;++_finish;
}

讲到这里很多朋友就会发现,那么我们的push_back函数也可以直接调用insert函数即可,两个写成一个就行,代码如下:

void push_back(const T& val)
{insert(end(), val);//直接尾插即可
}

删除数据- erase(iterator pos)

代码思路:

  1. 我们这里需要判断删除的数据是否在容器有效数据的区间内
  2. 同理我们只需要将删除数据位置的区间全部往前移动一个覆盖掉删除的数据即可,然后更新_finish即可完成删除。
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;//返回被删除数据的下一个位置,那就是还是原位置,原位置的数据已经被删除了
}

查看容器中的数据是否为空Empty()

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

vector中构造函数的一点延伸

拷贝构造的现代写法

代码思路:

  1. 我们直接用reserve开辟一个一样大的空间,相当于开辟了一块新的空间,达到了深拷贝的效果,然后在对其每个数据进行尾插即可。
vector(const vector<T>& V) 
{reserve(V.capacity());//直接开辟一个一样大的空间,然后每个数据进行尾插for (auto& e : V){push_back(e);}
}

区间构造函数

代码思路:
1.这里直接将该区间的数据依次插入到*this中的容器中即可

template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

列表初始化构造(c++11以上的写法)

代码思路:

  1. initializer_list是C++11引入的一种数据结构,用于表示一个初始化列表(initializer list)。它是一个模板类,定义在<initializer_list>头文件中。initializer_list可以用于在函数参数中传递一组值,也可以用于对象的初始化。它的语法类似于数组,使用花括号{}括起一组值,并以逗号分隔(这里就不过多的讲解了,后面会单独写一篇博客进行讲解)。
  2. 剩下的同理检查内存容量,然后对这里直接对其每个数据进行尾插就行。
vector(initializer_list<T> il)
{reserve(il.size());for (auto& e : il){push_back(e);}
}

总体代码

using namespace std;
#include <iostream>
#include <assert.h>
namespace bit
{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;}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}//V2(v1) v2是对v1的值拷贝,叫浅拷贝,v1生命周期结束,析构v1,v2生命周期结束释放V2,,vector(const vector<T>& V) {reserve(V.capacity());for (auto& e : V){push_back(e);}//传统写法//_start = new T[V.capacity()];//_finish = _start;//让_finish指向现在的位置,然后依次进行赋值//_endofstorage = _start + V.capacity();//for (size_t i = 0; i < V.size(); i++)//{//	*_finish = V[i];//	_finish++;//}}vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}// 类模板的成员函数可以是函数模板template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}vector<T>& operator=(vector<T> v){swap(v);return *this;}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];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}void reserve(size_t n)//调整和检查容量{if (capacity() < n){T* tmp = new T[n];size_t old_size = size();//因原本的_start在后面会改变,放在野指针的出现我们需要对old_size进行村粗for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_endofstorage = tmp + n;}}void resize(size_t n, const T& value = T())//使用半缺省,扩容的话就把剩余的部分全部扩容成value{if (n > size()){reserve(n);while (_finish < _start + n){*_finish = value;_finish++;}}else{_finish = _start + n;}}void insert(iterator pos,const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容了的话原本的_start//如果扩容了就要更新pospos = len + _start;}iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = val;++_finish;}void push_back(const T& val){//insert(end(), val);if (_endofstorage == _finish)//判断容量是否是满的{reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;_finish++;}void pop_back(){assert(_finish > _start);--_finish;}iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;//返回被删除数据的下一个位置,那就是还是原位置,原位置的数据已经被删除了}bool Empty(){return _start == _finish;}private:iterator _start = nullptr; //容器的起始位置  // T* _startiterator _finish = nullptr;//容器中所使用的元素的最后一个位置iterator _endofstorage = nullptr;//容器中所有元素的最后一个位置};template<class T>void print_vector(const vector<T>& v){for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;}void test1(){vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);//print_vector(v1);//vector<int> v2 = { 1,2,3,4,5 };//print_vector(v2);vector <int>v3(v1);print_vector(v3);for (auto e : v1){cout << e << " ";}}void test2(){vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);cout << "迭代器遍历" << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}cout << endl;cout << "范围for遍历" << endl;for (auto e : v1)cout << e << " ";cout << endl;}void test3(){vector<int> v1 = { 1,3,4,6 };v1.insert(v1.begin()+2, 10);print_vector(v1);}
}

好啦,今天的内容就到这里啦,下期内容预告stl中list的使用,博主前段时间有点事情,后面这段时间会加班加点的更新!


结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

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

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

相关文章

鸿蒙ArkTS声明式开发:跨平台支持列表【禁用控制】 通用属性

禁用控制 组件是否可交互&#xff0c;可交互状态下响应[点击事件]、[触摸事件]、[拖拽事件]、[按键事件]、[焦点事件]和[鼠标事件]。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到…

【一刷《剑指Offer》】面试题 30:最小的 k 个数

牛客对应题目链接&#xff1a;最小的K个数_牛客题霸_牛客网 (nowcoder.com) 力扣对应题目链接&#xff1a;LCR 159. 库存管理 III - 力扣&#xff08;LeetCode&#xff09; 核心考点 &#xff1a; topK 问题。 一、《剑指Offer》内容 二、分析题目 1、排序&#xff08;O(Nlo…

接口interfance的基本使用

一.为什么有接口&#xff1f; 接口:就是一种规则。 二.接口的定义和使用 1.接口用关键字interface来定义 public interface 接口名{} 2.接口不能实例化 3.接口和类之间是实现关系,通过implements关键字表示 4.接口的子类(实现类) 注意1&#xff1a; 接口和类的实现关系…

43.自定义线程池(一)

ThreadPool是线程池&#xff0c;里面是一定数量的线程&#xff0c;是消费者。 BlockingQueue阻塞队列&#xff0c;线程池中的线程会从阻塞队列中去拿任务执行。任务多了线程池处理不过来了&#xff0c;就会到Blocking Queue中排队&#xff0c;等待执行。链表结构&#xff0c;特…

Netfilter/iptables

1. Netfilter组件图 https://en.wikipedia.org/wiki/Netfilter 其中&#xff1a; etables作用于数据链路层&#xff0c;arptables针对ARP, iptables/ip6tables针对IP层。 nftables 是新的包过滤组件. nft是相对应的新的用户态组件&#xff0c;用于替换etables,arptables,ipt…

从tensorflow导入EarlyStopping能运行但是一直提示未解析

在pycharm中导入早停机的库时&#xff0c;碰上一个问题 from tensorflow.keras.callbacks import EarlyStopping这一条代码中&#xff0c;EarlyStopping一直有个红色波浪线&#xff0c;代表着找不到这个库&#xff0c;提示未解析啥的。 但是运行是可以运行的&#xff0c;虽然可…

GPT-4o如何重塑AI未来!

如何评价GPT-4o? 简介&#xff1a;最近&#xff0c;GPT-4o横空出世。对GPT-4o这一人工智能技术进行评价&#xff0c;包括版本间的对比分析、GPT-4o的技术能力以及个人感受等。 GPT-4o似乎是一个针对GPT-4模型进行优化的版本&#xff0c;它在性能、准确性、资源效率以及安全和…

Anolis OS 8.9安装Linux 服务器运维管理面板“1Panel”

一、简介 1.Linux 服务器运维管理面板“1Panel” 使用go语言编写 2.很多的项目的应用都是采用 docker 技术来实现&#xff0c;这让 Linux 服务器的运维管理更简单、更安全。 3.1Panel 采纳最新的前端技术&#xff0c;并通过精心设计的UX 交互&#xff0c;为用户提供更好的用户…

Linux系统tab键无法补齐命令-已解决

在CentOS中&#xff0c;按下tab键就可以自动补全&#xff0c;但是在最小化安装时&#xff0c;没有安装自动补全的包&#xff0c;需要安装一个包才能解决 bash-completion 1.检查是否安装tab补齐软件包&#xff08;如果是最小化安装&#xff0c;默认没有&#xff09; rpm -q ba…

关于sprintboot3版本以上中的swagger3.0的使用

文章目录 1.配置pom.xml(添加以下内容&#xff0c;记住点一下右上方maven下载)2.application.properties添加以下配置信息3.新建swagger的config配置信息&#xff0c;文件位置如下4.添加接口注释信息访问swagger文档 1.配置pom.xml(添加以下内容&#xff0c;记住点一下右上方ma…

抽象一个通用的配置冲突解决方案

最近的开发项目中遇到了一个关于配置冲突的解决和产品设计&#xff0c;一直以来都没有处理好。最近抽空整理了一下思路和设计&#xff0c;并做了抽象&#xff0c;后续的类似使用&#xff0c;可以做到直接复用。 思路和代码见&#xff1a;github地址&#xff1a;https://github…

基于java18多端展示+ idea hbuilder+ mysql家政预约上门服务系统,源码交付,支持二次开发

基于java18多端展示 idea hbuilder mysql家政预约上门服务系统&#xff0c;源码交付&#xff0c;支持二次开发 家政预约上门系统是一种通过互联网或移动应用平台&#xff0c;为用户提供在线预约、下单、支付和评价家政服务的系统。该系统整合了家政服务资源&#xff0c;使用户能…

RabbitMQ三、springboot整合rabbitmq(消息可靠性、高级特性)

一、springboot整合RabbitMQ&#xff08;jdk17&#xff09;&#xff08;创建两个项目&#xff0c;一个生产者项目&#xff0c;一个消费者项目&#xff09; 上面使用原生JAVA操作RabbitMQ较为繁琐&#xff0c;很多的代码都是重复书写的&#xff0c;使用springboot可以简化代码的…

Vue3集成Phaser-飞机大战游戏(设计与源码)

文章目录 引言项目初始化游戏设计和结构游戏程序实现Vue页面嵌入PhaserPreloader 场景加载游戏场景功能实现功能类定义Boom爆炸类Bullet子弹类Enemy敌军类Player玩家类End游戏结束类 总结 更多相关内容可查看 引言 飞机大战&#xff08;也被称为射击游戏或空战游戏&#xff09…

轻松上手MYSQL:优化MySQL慢查询,让数据库起飞

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索MYSQL慢查询之旅✨ &#x1f44b; 大家好&#xff01;我是你们的…

如何优雅简洁的使用YOLOv8

如何优雅简洁的使用YOLOv8 目录训练调用代码如何一键训练多个yamlexport模型测试多个yaml是否运行正常predict本文提供了 如何优雅简洁的使用YOLOv8 🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局…

Crosslink-NX器件应用连载(11): 图像(数据)远程传输

作者&#xff1a;Hello&#xff0c;Panda 大家下午好&#xff0c;晚上好。这里分享一个Lattice Crosslink-NX器件实现图像或数据&#xff08;卫星数据、雷达数据、ToF传感器数据等&#xff09;远程传输的案例&#xff08;因为所描述的内容颇杂&#xff0c;晒图不好晒&#xff…

文件批量改后缀名,轻松实现TXT到DOCX格式转换,高效管理您的文件库!

文件处理与管理已成为我们日常生活和工作中不可或缺的一环。然而&#xff0c;面对海量的文件&#xff0c;如何高效地进行格式转换和管理&#xff0c;却成为了一道难题。今天&#xff0c;我们将为您揭晓一个神奇的解决方案——文件批量改后缀名功能&#xff0c;让您轻松实现TXT到…

【docker】docker的安装

如果之前安装了旧版本的docker我们需要进行卸载&#xff1a; 卸载之前的旧版本 卸载 # 卸载旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc # 卸载历史版本 apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker…

linux文件共享之samba

1.介绍 Samba是一个开源文件共享服务&#xff0c;可以使linux与windows之间进行文件共享&#xff0c;可以根据不同人员调整共享设置以及权限管理。 2.安装 一个命令就OK了&#xff1a;yum install -y samba [rootansible01 ~]# yum install -y samba 已加载插件&#xff1a;l…