【C++】STL — vector的接口讲解 +详细模拟实现

前言:
本章我们将学习STL中另一个重要的类模板vector…

  • vector是表示可变大小数组的序列容器
  • 就像数组一样,vector也采用的连续存储空间来存储元素。但是又不像数组,它的大小是可以动态改变的
  • 本质讲,vector使用动态分配数组来存储它的元素
  • vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。

能不能用vector来替代string呢?

  • 可以是可以,但是有区别的,string的部分功能vector实现不了,比如:流插入>>和流提取 <<
  • string比较大小是按照ASCII码比较,但是vector的规则就不一样了。
  • Vector 是没有重载<< 和>>操作符的 find( ),以及以下使用情况,

vector < char> T;
string str ; // 数据结尾\0 、+=、find、比较大小、to_string、<< 、>>等等
// vector < char> 无法替代string

目录

    • 1. vector的使用
      • 1.1 vector的构造:
      • 1.2 vector的迭代器:
      • 1.3 vector的内存管理:
      • 1.4 迭代器可以访问容器:
    • 2. vector的模拟实现
    • 2.1 迭代器失效问题
    • 2.2 深浅拷贝问题

1. vector的使用

1.1 vector的构造:

在我们使用vector之前我们需要先包一下头文件#include< vector >

在这里插入图片描述

int main ()
{// constructors used in the same order as described above:std::vector<int> first;                                // empty vector of intsstd::vector<int> second (4,100);                       // four ints with value 100std::vector<int> third (second.begin(),second.end());  // iterating through secondstd::vector<int> fourth (third);                       // a copy of third
// the iterator constructor can also be used to construct from arrays:int myints[] = {16,2,77,29};std::vector<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );}

1.2 vector的迭代器:

用法和string中的迭代器一样.

void test_vector2()
{//遍历vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//1、下标 + []for (size_t i = 0; i < v.size(); i++){v[i] += 1;cout << v[i] << " ";}cout << endl;//2、迭代器vector<int>::iterator it = v.begin();while (it != v.end()){*it -= 1;cout << *it << " ";it++;}cout << endl;//3、范围forfor (auto e : v){cout << e << " ";}cout << endl;
}

1.3 vector的内存管理:

STL中vector每次扩容的规律:

void test_vector3()
{//vector<int> v;//cout << v.max_size() << endl;//Capicity容量测试 -- VS是PJ版本 大概是1.5倍增容,Linux是SGI版本 是2倍增容size_t sz;vector<int> v;sz = v.capacity();cout << "making v grow:\n";for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}

内存管理数据分析:

  • 单次增容越多,插入N个值,增容次数越少,效率就越高
  • 单次增容越多,造成空间浪费。

reserve / resize 的使用:

  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size
容量空间接口说明
resize(重点)改变vector的size
reserve (重点改变vector的capacity
void tese_vector4()
{vector<int> countV;//resize 开空间 + 初始化countV.resize(100, 1);countV.resize(10);countV.reserve(1000);//sting 和 vector等都有一个特点,删除数据,一般不会主动缩容的countV.shrink_to_fit();cout << countV.size() << endl;cout << countV.capacity() << endl;cout << endl << endl;
//操作系统的空间不允许一部分一部分还
//vector没有头插头删,效率比较低
}

insert / erase 的使用:

void test_vector5()
{//遍历vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.insert(v.begin(), -1);v.insert(v.begin(), -2);v.insert(v.begin(), -3);for (auto e : v){cout << e << " ";}cout << endl;//可以在尾最后一个位置插入,越界了是不行的v.insert(v.begin() + 7, 3000);for (auto e : v){cout << e << " ";}cout << endl;v.erase(v.begin());v.erase(v.begin());for (auto e : v){cout << e << " ";}cout << endl;
}

1.4 迭代器可以访问容器:

void test_vector6()
{vector<int> v;//auto pos = find(v.begin(), v.end(), 3);vector<int>::iterator pos = find(v.begin(), v.end(), 3);if (pos != v.end()){cout << "找到了" << endl;v.erase(pos);}else{cout << "没有找到" << endl;}for (auto ch : v){cout << ch << " ";}cout << endl;v.push_back(0);v.push_back(9);v.push_back(3);v.push_back(1);//默认是升序//sort(v.begin(), v.end()); // < //排降序,使用仿函数greater//关于仿函数,先记住这个用法,具体后面学习队列细学sort(v.begin(), v.end(), greater<int>()); // > ,greater<int>()匿名对象
}
  • 仿函数我们后期会学,先包一下头文件:#include< functional> – 仿函数
  • vector和list没有find,没有查找函数,我们需要包一个算法的头文件 #include< functional >

2. vector的模拟实现

template<class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;//构造函数1vector():_start(nullptr), _finish(nullptr), _endofstoage(nullptr){}//给一个迭代器的区间去构造2template<class InputIterator>vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstoage(nullptr){while (first != last){push_back(*first);first++;}}//用n个val初始化3vector(size_t n, const T& val = T()): _start(nullptr), _finish(nullptr), _endofstoage(nullptr){reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}
//重载一个int n (逻辑和上面一样)vector(int n, const T& val = T()): _start(nullptr), _finish(nullptr), _endofstoage(nullptr){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstoage, v._endofstoage);}//拷贝构造--现代写法1vector(const vector<T>& v)//vector(const vector& v):_start(nullptr), _finish(nullptr), _endofstoage(nullptr){//拷贝构造中调用一个构造函数,构造一个tmp出来vector<T> tmp(v.begin(), v.end());//通过this指针调用该对象的成员函数this->swap(tmp);}//-----现代写法2vector<T>& operator=(vector<T> v){this->swap(v);return *this;}
}

C++中,内置类型也可以认为,有构造函数和析构函数

原因在于:1.这样可以更好支持模板 2.void resize(size_t n, T val = T()) 与类的对象使用方法一样:
int i=0;
int j= int(1)
int m(1);
在这里插入图片描述

//资源清理
~vector()
{if (_start != nullptr){delete[] _start;_start = _finish = _endofstoage = nullptr;}
}iterator begin()
{return _start;
}iterator end()
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}size_t size() const
{return _finish - _start;
}size_t capacity() const
{return _endofstoage - _start;
}void reserve(size_t n)
{//这里存在start更新之后就算不准的现象size_t sz = size();if (n > capacity()){T* tmp = new T[n];if (_start != nullptr){//memcpy(tmp, _start, size() * sizeof(T));//这里是浅拷贝,所以会有问题for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;}_finish = _start + sz;_endofstoage = _start + n;
}

注意:

  • 一开始的时候 _finish 和 _start 都是0空指针nullptr
  • _start被更新了,要是这样:_finish = _start + size();
  • 不能用size( ),要是这样:_finish = _start + _finsih - _start;
//生成T类型的匿名对象,C++内置类型也有默认构造函数
//分3种情况:1.n>capacity 需要扩容+初始化2.n>size && n<=capacity 初始化3.n<size 删除数据void resize(size_t n, const T& val = T())
{if (n > capacity()){reserve(n);}if (n > size()){while (_finish < _start + n){*_finish = val;_finish++;}}else{_finish = _start + n;}
}void push_back(const T& x)
{/*if (_finish == _endofstoage){size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);}*_finish = x;_finish++;*/finish代表最后一个,所以要加一个insert(end(), x);
}void pop_back()
{/*if (_finish > _start){_finish--;}*///返回的临时对象不能改变,不能++(自增)和--(自减)erase(end() - 1);
}T& operator[](size_t pos)
{assert(pos <= size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}

2.1 迭代器失效问题

两种迭代器失效原因:

  • 1.野指针
  • 2.意义变了

与vector类似, string在插入+扩容操作+ erase之后,迭代器也会失效
1.在迭代器位置P插入数据以后,不要访问P了,因为P可能失效了

auto p=find(v.begin( ),v.end( ),3);
if(p!=v.end( ))
{v.insert(p,30);cout<<*p<<endl;//这里最好就不要使用了,迭代器P已经失效了,如果必须要有,要接收返回值,对P重新赋值v.insert(p,30);
}
  1. 总结一下,如果必须要用,为了防止迭代器失效,在使用迭代器前,对迭代器重新赋值即可

结论:insert/erase pos位置后,不要直接访问pos.一定要更新,因为直接访问,可能会出现各种出乎意料的结果,这就是所谓的迭代器失效

演示代码1:

void test_vector2()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.insert(v.begin(), 0);for (auto e : v){cout << e << " ";}cout << endl;
}

迭代器失效问题,是因为pos是不更新的。

  • 当reserve扩容之后,如果是新空间,_start和_finish更新了,pos(就是v.begin())还指向旧空间
  • 但是旧空间被释放了,这个问题就是迭代器失效,pos为野指针
  • 所以就插入失败了
  • 迭代器发生了野指针的问题
    insert函数中的内部pos指针更新了,但是形参的改变不会影响实参,pos指针是一个传值传参
	//pos前一个位置插入(之前是void,所以错误)iterator insert(iterator pos, const T& x){//检查Pos位置是否合理assert(pos >= _start && pos <= _finish);//扩容//扩容以后pos就失效了,需要更新一下if (_finish == _endofstoage){size_t n = pos - _start;size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);//更新迭代器的位置,做返回pos = _start + n;}//挪动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;return pos;}**//有返回值是防止STL库里面有缩容的情况,不知道怎么实现的**//如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是,没有元素的,那么pos就失效了iterator erase(iterator pos) %-**-返回删除位置的下一个位置**{assert(pos >= _start && pos < _finish);iterator begin= pos + 1;while (begin != _finish){*(begin - 1) = *begin;begin++;}_finish--;return pos;}void clear(){_finish = _start;}
private:iterator _start;iterator _finish;iterator _endofstoage;
};//类外定义拷贝构造-模板写法:
template<typename T>
vector<T>::vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstoage(nullptr)
{vector<T> tmp(v.begin(), v.end());this->swap(tmp);
}

2.1.1 Insert 和 Erase 迭代器失效的讨论

vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){//错误写法:v.insert(it, 20)//正确写法:it = v.insert(it, 20);it++;}it++;}

(1)Insert 扩容的情况:(野指针)

  • 如果扩容的话,迭代器将it指向原来的空间,原来的空间已经被释放了
  • 当再次进入Insert时,it变成野指针。

(2) Insert 不扩容的情况:(意义变了)

  • insert以后虽然没有扩容,it不是野指针,但是it指向位置的意义变了

  • it++之后一直指向20,导致了我们这个程序重复插入20

  • 不是it是野指针失效的,而是it指向的位置意义变了

  • 也叫作迭代器失效

(3)Erase 缩容的情况:(野指针)

  • STL里对erase的实现,有可能存在缩容实现,就会出问题
  • 当再次进入erase时,it变成野指针。

(4) Erase不扩容的情况:(意义变了)

  • erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效。
  • 但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了

解决方案:

  • 采取返回指针的话可以完美避开上述所有问题
  • 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等

2.2 深浅拷贝问题

以杨辉三角为例:
vector<vector< int>> vv;
在这里插入图片描述

  • vector<vectot< int >>表示里面的每个数据存的都是vector的对象
  • 外面vector的是深拷贝,里面的Vector 使用的是memcpy( )做的是浅拷贝, 导致delete析构的时候,指向同一个空间,出现问题
  • vector里面的T类型有可能是自定义类型,使用Memcpy( )就是浅拷贝,所以里面也要做深拷贝。

**结论:**如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃

尾声
看到这里,相信大家对这个C++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

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

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

相关文章

配电室智能巡检机器人

近年来&#xff0c;生产过程高度自动化&#xff0c;各工矿企业关键场所需定期巡检维护。但目前巡检主要靠人工&#xff0c;既耗时费力效率又低&#xff0c;且受环境等因素影响&#xff0c;巡检难以全面规范&#xff0c;隐患或问题易被忽视。在此情况下&#xff0c;如何利用现有…

ElasticSearch02(DSL查询文档,DSL处理结果,RestClient查询,旅游案例,数据聚合)【全详解】

目录 一、DSL查询文档 1. 说明 2. 文本检索 3. 精确查询 4. 地理坐标查询 5. 复合查询 6. 课堂演示 7. 小结 二、DSL处理结果 1. 排序 2. 分页 3. 高亮 4. 课堂演示 5. 小结 三、RestClient查询 1.快速入门 2.match查询 3.精确查询 4.布尔查询 5. 算分函数…

锁相环原理解析

在计算机和嵌入式系统中&#xff0c;常常要用锁相环来倍频&#xff0c;那么&#xff0c;锁相环是如何倍频的&#xff0c;其原理又是什么呢&#xff1f; 目录 1. 锁相环基本概念与构成1.1 鉴相器1.2 低通滤波器1.3 压控振荡器 2. 锁相环如何实现倍频3. 锁相环也会失效&#xff…

【前端学习——正则】

https://www.bilibili.com/video/BV1da4y1p7iZ/?spm_id_from333.337.search-card.all.click&vd_source5cef5968d539682b683e7d01b00ad01b 学习网站 https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md

Bookends for Mac:文献管理工具

Bookends for Mac&#xff0c;一款专为学术、研究和写作领域设计的文献管理工具&#xff0c;以其强大而高效的功能深受用户喜爱。这款软件支持多种文件格式&#xff0c;如PDF、DOC、RTF等&#xff0c;能够自动提取文献的关键信息&#xff0c;如作者、标题、出版社等&#xff0c…

在M1芯片安装鸿蒙闪退解决方法

在M1芯片安装鸿蒙闪退解决方法 前言下载鸿蒙系统安装完成后&#xff0c;在M1 Macos14上打开闪退解决办法接下来就是按照提示一步一步安装。 前言 重新安装macos系统后&#xff0c;再次下载鸿蒙开发软件&#xff0c;竟然发现打不开。 下载鸿蒙系统 下载地址&#xff1a;http…

MATLAB实现遗传算法优化第三类生产线平衡问题

第三类生产线平衡问题的数学模型 假设&#xff1a; 工作站数量&#xff08;m&#xff09;和生产线节拍&#xff08;CT&#xff09;是预设并固定的。每个任务&#xff08;或作业元素&#xff09;只能分配到一个工作站中。任务的执行顺序是预先确定的&#xff0c;且不可更改。每…

PHP医疗不良事件上报系统源码 AEMS开发工具vscode+ laravel8 医院安全(不良)事件报告系统源码 可提供演示

PHP医疗不良事件上报系统源码 AEMS开发工具vscode laravel8 医院安全&#xff08;不良&#xff09;事件报告系统源码 可提供演示 医院安全不良事件报告系统&#xff08;AEMS&#xff09;&#xff1b;分为外部报告系统和内部报告系统两类。内部报告系统主要以个人为报告单位&…

Docker私有镜像仓库搭建 带图形化界面的

搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。 官网地址&#xff1a;https://hub.docker.com/_/registry 先配置私服的信任地址: # 打开要修改的文件 vi /etc/docker/daemon.json # 添加内容&#xff1a; "insecure-registries":["http://192.…

暴力匹配字符串的升级版算法 —— Kmp算法

文章目录 一、Kmp算法是什么&#xff1f;二、算法分析1.构建next数组2.匹配主串 三、完整代码 一、Kmp算法是什么&#xff1f; 简单来说&#xff0c;KMP&#xff08;Knuth-Morris-Pratt&#xff09;算法主要用于解决字符串匹配问题。也就是当你有一个主串&#xff08;text&…

OceanBase 轻量级数仓关键技术解读

码到三十五 &#xff1a; 个人主页 为了更好地聚合和治理跨域数据&#xff0c;帮助企业用较低的成本快速聚合分析&#xff0c;快速决策&#xff0c;不断的让企业积累的数据产生价值&#xff0c;从全域海量数据抓取&#xff0c;高性能流批处理&#xff0c;元数据血缘治理等等方面…

AI-数学-高中52-离散型随机变量概念及其分布列、两点分布

原作者视频&#xff1a;【随机变量】【一数辞典】2离散型随机变量及其分布列_哔哩哔哩_bilibili 离散型随机变量分布列&#xff1a;X表示离散型随机变量可能在取值&#xff0c;P:对应分布在概率&#xff0c;P括号里X1表示事件的名称。 示例&#xff1a;

QT:QT窗口(一)

文章目录 菜单栏创建菜单栏在菜单栏中添加菜单创建菜单项添加分割线 工具栏创建工具栏设置停靠位置创建工具栏的同时指定停靠位置使用QToolBar类提供的setAllowedAreas函数来设置停靠位置 设置浮动属性设置移动属性 状态栏状态栏的创建在状态栏中显示实时消息在状态栏中显示永久…

AI人才争夺战,华尔街入局:豪掷百万美元年薪抢人

继硅谷之后&#xff0c;华尔街也入局**“AI人才争夺大战”**。 他们的目标非常明确——抢的就是高精尖的AI专家。 △图源&#xff1a;Business Insider 现在这条“街”上&#xff0c;不论是银行、对冲基金还是私募股权公司都已纷纷下场&#xff0c;可谓是豪掷千金&#xff0c…

选择深度学习框架:TensorFlow 2 vs PyTorch

TensorFlow 2 vs PyTorch 选择深度学习框架&#xff1a;TensorFlow 2 vs PyTorchTensorFlow 2概述TensorFlow 2的优点TensorFlow 2的缺点 PyTorch概述PyTorch的优点PyTorch的缺点 选择建议对于选择困难症的人&#xff0c;我给你们的答案——PyTorch选择理由&#xff1a;结论&am…

【LinuxC语言】setitimer与getitimer函数

文章目录 前言一、setitimer() 函数二、getitimer() 函数三、示例代码总结 前言 在Linux系统下&#xff0c;编写程序时经常需要使用定时器来实现一些定时任务、超时处理等功能。setitimer() 和 getitimer() 函数是两个用于操作定时器的重要函数。它们可以帮助我们设置定时器的…

[华为OD]C卷 给定一个数组,数组中的每个元素代表该位置的海拔高度 山脉的个数 200

题目&#xff1a; 给定一个数组&#xff0c;数组中的每个元素代表该位置的海拔高度。0表示平地&#xff0c;&#xff1e;1时表示属于某个 山峰&#xff0c;山峰的定义为当某个位置的左右海拔均小于自己的海拔时&#xff0c;该位置为山峰。数组起始位 置计算时可只满足一边…

SpringBoot自定义定时任务

通常&#xff0c;在我们的项目中需要定时给前台发送一些提示性消息或者我们想要的定时信息&#xff0c;这个时候就需要使用定时任务来实现这一功能&#xff0c;实现也很简单&#xff0c;接下来具体来看看吧~ 简单定时任务 首先&#xff0c;你需要在你的启动类上加上开启定时任…

YOLOv5改进之bifpn

目录 一、原理 二、代码 三、在YOLOv5中的应用 一、原理 论文链接: