【C++】vector的底层原理讲解及其实现

目录

一、认识vector底层结构
二、初始化vector的函数

  1. 构造函数
  2. 拷贝构造
  3. 赋值构造
  4. initializer_list构造
  5. 迭代器区间构造

三、迭代器
四、数据的访问
五、容量相关的函数
六、关于数据的增删查改操作


一、认识vector底层结构
STL库中实现vector其实是用三个指针来完成的,使用这三个指针(或称为迭代器)变量来管理其内存

在这里插入图片描述
其实vector和string的实现非常相似,都是利用了顺序表结构,在vector的实现上我们遵循底层用三个指针来完成,_statr,_finish,_end_fo_storage分别指向顺序表的头,顺序表存储数据的有效个数的位置,顺序表的结束

template<class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;iterator _finish;iterator _endofstorage;
};

二、初始化vector的函数

1、构造函数
①最常用的无参默认构造
vector();

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

这个很简单,我就不多讲了,后面遇到难点我会详细说明,便于大家理解

②用n个val构造一个vector
explicit vector (size_type n, const value_type& val = value_type();
库里面用explicit修饰构造函数,是为了防止构造函数发生隐式类型转换

vector(size_t n, const T& val = T())
{_start = new T[n];_finish = _start;while (_finish!=_start+n){*_finish = val;_finish++;}_endofstorage = _start + n;
}

2、拷贝构造
vector(const vector& v);
拷贝构造:用一个已经存在的对象来初始化另一个正在创建的对象

vector(const vector& v)
{_start = new T[v.size()];memcpy(_start, v._start, sizeof(T) * v.size());_finish = _start + v.size();_endofstorage = _finish;
}

3、赋值构造
赋值构造:两个已经存在的对象,一个赋值给另一个
vector& operator= (const vector& v);

void swap(vector& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}
//v1=v2;
vector& operator= (vector v)
{swap(v);return *this;
}

这里我利用库里的函数来实现swap,只交换vector的三个指针更高效,赋值构造的参数是传值传参,会调用拷贝构造,将v2拷贝一份给v,然后之间调用swap函数,将v和this交换,最后返回this即可

4、initializer_list构造
vector (initializer_list<T> il);
tips:这里的initializer_list实际是个类,C++底层将其封装了,里面也有begin,end,size

//vector<int> v={1,2,3,4,5};
vector(initializer_list<T> il)
{for (auto e : il){push_back(e);}
}

5、迭代器区间构造
template <class InputIterator> vector(InputIterator first, InputIterator last);

// 类模板的成员函数可以是函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

注意:如果加了迭代器区间构造会造成一个问题,就是在调用时和vector(size_t n, const T& val = T())会出现冲突,底层给出的解决方案就是加一个重载vector(int n, const T& val = T())

三、迭代器
这里博主就只介绍最重要的迭代器部分
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//加了const修饰,const对象可以调用,非const对象也可以调用{return _start;}const_iterator end() const{return _finish;}private:iterator _start; //指向空间(顺序表)的开始iterator _finish;//指向有效数据个数的位置iterator _endofstorage;//指向空间的结束
};

四、数据的访问

由于vector底层是连续的物理空间,所以支持下标访问
T& operator[] (size_t n);
const T& operator[] (size_t n) const;

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

五、容量相关的函数

size(有效数据个数)和capacity(空间容量大小)

size_t size() const
{return _finish - _start;//指针减指针得到两个指针之间的数据个数
}
size_t capacity() const
{return _endofstorage - _start;
}

reserve
void reserve (size_t n);
易错点1
在这里插入图片描述

void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* temp = new T[n];memcpy(temp, _start, sizeof(T) * size());delete[]_start;_start = temp;_finish = _start + old_size;_endofstorage = _start+n;}
}

改成现在这个样子确实是解决了上图中的问题,但是这个就是对的吗?让我们一起来看一下这个代码究竟对不对

易错点2
其实上面的代码大体逻辑是没有什么问题的,问题就出在这个memcpy上,要知道memcpy底层实现是按字节一个一个拷贝过去的,虽然我们的vector是深拷贝,但是memcpy是浅拷贝,这样写针对内置类型是🆗的,但是针对自定义类型会存在指向同一块空间的问题,两次析构等问题,话不多说,我们看图解

出错案例:
在这里插入图片描述
出错图解:
在这里插入图片描述
其实要改正这个问题有一个很简单的方式,采用赋值的方式,无论是内置类型还是自定义类型,在赋值时都会调用他的拷贝构造,这样就自动调用该类型的深拷贝了

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

resize
void resize (size_t n, const T& val=T());

void resize(size_t n, const T& val=T())
{if (n > capacity()){reserve(n);for (size_t i = size(); i < n; i++){_start[i] = val;}_finish = _start + n;}else{_finish = _start + n;}
}

注意:reverseresize都不会缩容

empty
bool empty() const

bool empty()const
{return _finsh == _start;
}

六、关于数据的增删查改操作

push_back
void push_back (const T& val);

void push_back(const T& val)
{if (size() == capacity()){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = val;_finish++;
}

inserrt
void insert (iterator pos, const T& val);

void insert(iterator pos, const T& val)
{assert(pos>=_start);assert(pos <= _finish);size_t d = pos - _start;//先记下pos和_start的相对位置if (size() == capacity()){reserve(capacity() == 0 ? 4 : 2 * capacity());//如果扩容了,要更新pospos = _start + d;}iterator end = _finish;while (pos < end){*end = *(end - 1);end--;}*pos = val;_finish++;
}

erase
iterator erase (iterator pos);

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

clear
void clear();

void clear()
{_finish = _start;
}

vector章节我们就到这里啦,欢迎大家来学习指教下一篇list章节😘

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

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

相关文章

C# OpenCvSharp Demo - Mat格式化输出、Mat序列化和反序列化

C# OpenCvSharp Demo - Mat格式化输出、Mat序列化和反序列化 目录 效果 项目 代码 下载 效果 直接输出&#xff1a;Mat [ 3*2*CV_8UC3, IsContinuousTrue, IsSubmatrixFalse, Ptr0x1eb73ef9140, Data0x1eb73ef91c0 ]格式化输出&#xff1a;默认风格[ 91, 2, 79, 179, …

JAVA基础--IO

IO 什么是IO 任何事物提到分类都必须有一个分类的标准&#xff0c;例如人&#xff0c;按照肤色可分为&#xff1a;黄的&#xff0c;白的&#xff0c;黑的&#xff1b;按照性别可分为&#xff1a;男&#xff0c;女&#xff0c;人妖。IO流的常见分类标准是按照*流动方向*和*操作…

AI办公自动化-用kimi批量重命名Word文档

文件夹里面有很多个word文档&#xff0c;标题里面都含有零代码编程&#xff0c;现在想将其替换为AI办公自动化。 在kimichat中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个编写Python脚本的任务&#xff0c;具体步骤如下&#xff1a; 打开文件夹&am…

windows使用Docker-Desktop部署lobe-chat

文章目录 window安装docker-desktop下载和启动lobe-chatAI大语言模型的选择lobe-chat设置大模型连接 window安装docker-desktop docker-desktop下载地址 正常安装应用&#xff0c;然后启动应用&#xff0c;注意启动docker引擎 打开右上角的设置&#xff0c;进入Docker Engine设…

十、Redis内存回收策略和机制

1、Redis的内存回收 在Redis中可以设置key的过期时间&#xff0c;以期可以让Redis回收内存&#xff0c;循环使用。在Redis中有4个命令可以设置Key的过期时间。分别为 expire、pexpire、expireat、pexpireat。 1.1、expire expire key ttl&#xff1a;将key的过期时间设置为tt…

打开远程连接的命令是什么?

远程连接是一种能够在不同设备之间建立连接并共享信息的技术。在许多情况下&#xff0c;我们需要通过远程连接来访问其他设备或处理一些远程任务。本文将介绍一些常用的打开远程连接的命令。 使用SSH连接远程设备 SSH&#xff08;Secure Shell&#xff09;是一种安全的网络协议…

【IC前端虚拟项目】验证环境env与base_teat思路与编写

【IC前端虚拟项目】数据搬运指令处理模块前端实现虚拟项目说明-CSDN博客 上一篇里解决了最难搞的axi_ram_model,接下来呢就会简单又常规一些了,比如这一篇要说的env和base_test的搭建。在这里我用了gen_uvm_tb脚本: 【前端验证】验证自动化脚本的最后一块拼图补全——gen_t…

Spring编程使用DDD的小把戏

场景 现在流行充血领域层&#xff0c;在原本只存储对象的java类中&#xff0c;增加一些方法去替代原本写在service层的crud&#xff0c; 但是例如service这种一般都是托管给spring的&#xff0c;我们使用的ORM也都托管给spring&#xff0c;这样方便在service层调用mybatis的m…

【免费Java系列】大家好 ,今天是学习面向对象高级的第十二天点赞收藏关注,持续更新作品 !

这是java进阶课面向对象第一天的课程可以坐传送去学习http://t.csdnimg.cn/Lq3io day10-多线程 一、多线程常用方法 下面我们演示一下getName()、setName(String name)、currentThread()、sleep(long time)这些方法的使用效果。 public class MyThread extends Thread{publi…

力扣例题(用栈实现队列)

目录 链接. - 力扣&#xff08;LeetCode&#xff09; 描述 思路 push pop peek empty 代码 链接. - 力扣&#xff08;LeetCode&#xff09; 描述 思路 push 例如我们将10个元素放入栈中&#xff0c;假设最左边为栈顶&#xff0c;最右侧为栈底 则为10,9,8,7,6,5,4,3,…

stm32——OLED篇

技术笔记&#xff01; 一、OLED显示屏介绍&#xff08;了解&#xff09; 1. OLED显示屏简介 二、OLED驱动原理&#xff08;熟悉&#xff09; 1. 驱动OLED驱动芯片的步骤 2. SSD1306工作时序 三、OLED驱动芯片简介&#xff08;掌握&#xff09; 1. 常用SSD1306指令 2. …

清理缓存简单功能实现

在程序开发中&#xff0c;经常会用到缓存&#xff0c;最常用的后端缓存技术有Redis、MongoDB、Memcache等。 而有时候我们希望能够手动清理缓存&#xff0c;点一下按钮就把当前Redis的缓存和前端缓存都清空。 功能非常简单&#xff0c;创建一个控制器类CacheController&#xf…