C++——STL容器——vector

        vector是STL容器的一种,和我们在数据结构中所学的顺序表结构相似,其使用和属性可以仿照顺序表的形式。vector的本质是封装了一个动态大小的数组,支持动态管理容量、数据的顺序存储以及随机访问。

1.前言说明

        vector作为容器,应该支持任意数据类型的使用,所以vector使用模板类来实现,在使用的时候对模板类进行实例化即可。

        我们需要首先明确vector的迭代器如何实现。对于如vector和上一篇文章中的string而言,他们的存储空间是连续的,所以使用原生指针即可完成遍历和访问操作。在这里对迭代器的实现进行说明,一般而言,存储物理空间连续的容器,迭代器使用原生指针就可以了,因为此时迭代器的自增、begin和end等行为指针可以完美驾驭;但是对于物理空间不连续的容器而言,如链表,就不可以使用原生指针作为迭代器了,因为此时物理空间不再连续,导致自增等行为会出现异常,所以需要额外封装一个迭代器类,具体详情会在下一篇文章中详细说明。

        对于vector而言,我们贴合库中的形式进行实现,所以使用迭代器变量的形式来作为vector类的成员变量。也就是使用指针作为成员变量来表示vector的始末位置以及容量。

namespace m_vector	//为自己实现的vector定义一个命名空间
{template<class T>	//使用类模板class vector{public:typedef T* iterator;typedef const T* const_iterator;	//定义迭代器为T*类型private:	//成员变量都是迭代器变量的形式(指针),尽量贴合库中的形式iterator _start = nullptr;	//vector起始地址iterator _finish = nullptr;	//vector最后一个数据的下一个地址iterator _endofstorage = nullptr;	//vector开辟的空间的下一个地址//全部给定缺省值为空指针};
}

2. 构造函数

2.1 无参构造(默认构造)

        对于无参构造,将所以成员全部初始化为空指针,初始化缺省值在成员变量声明处给出。

		//构造函数://1.无参构造,成员参数全部初始化为空指针vector(){}	

2.2 拷贝构造

        拷贝构造复用已有的成员函数即可,无需自己再手动进行拷贝,先使用reserve扩容后,依次尾插元素即可。

//2.拷贝构造
//拷贝构造需要一些成员函数的辅助
vector(const vector<T>& v)
{//拷贝构造:先扩容,然后将元素依次尾插reserve(v.capacity());for (auto& e : v){push_back(e);}
}

2.3 范围构造

        使用迭代区间对数组内容进行初始化,构造出来的数组具有[first,last)迭代区间的元素。

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

		std::string s("abcdef");vector<char> v4(s.begin() + 1, s.end() - 1);for (auto e : v4){std::cout << e << ' ';}std::cout << std::endl;

        对于范围构造,传递的迭代器参数理应支持任何容器的迭代器作为参数,所以考虑使用函数模板。迭代器的出现,就是为了可以忽略内部细节,而使用统一的方式格式进行访问。因此,在使用迭代器的时候无需关注具体实现细节,只需按照套路使用就好。

		//3.迭代区间构造,构造的数组使用迭代区间的内容初始化// template <class InputIterator> // vector (InputIterator first, InputIterator last);// 类模板的成员函数可以是函数模板,使用函数模板以满足各种数据类型迭代区间的构造template <class InputIterator>vector(InputIterator first, InputIterator last){//由于任何数据类型都实现并支持迭代器,所以可以使用迭代器进行访问while (first != last){push_back(*first);++first;}}

2.4 填充构造

        这个构造函数具有两个参数,将vector的前n个元素使用val进行初始化。模拟实现使用复用resize函数即可,但是值得一提的是它的参数缺省值。

vector (size_type n, const value_type& val = value_type());

        发现val的缺省值是一个匿名对象,这是因为vector类模板实例化对象可能是内置类型,也可能是自定义类型。C++语法为了方便这种缺省值的情况,规定内置类型也有一个伪构造函数,这样就可以兼顾到内置类型和自定义类型两种情况,当value_type是内置类型时,就会被当做对象调用构造函数,如果是自定义类型就会使用匿名对象缺省值。

		//3.n个元素构造,构造的vector初始化为具有n个val元素// vector (size_type n, const value_type& val = value_type());vector(size_t n, const T& val = T())	//以匿名对象T()作为缺省值//使用T()作为缺省值,T()是T类的匿名对象,被用来赋值的时候会调用其类的构造函数生成对象//使用了匿名对象可以保证对自定义类型的缺省//对于内置类型,C++为其定义一个伪构造函数,即当T是内置类型时,内置类型会被当做对象调用构造函数,如int类型会被默认初始化为0{resize(n, val);}

        需要注意的是,由于存在范围构造,并且填充构造中的参数n类型为size_t,int变为size_t发生类型转换,因为调用范围构造并且将类型推导为int更贴合,所以如vector v(2,3)的(int,int)类的构造就会调用范围构造。所以为了避免这种情况需要为(int,int)再重载一个构造。

		vector(int n, const T& val = T()){resize(n, val);}

2.5 初始化列表构造

        这是C++11中支持的构造函数。在C++11中新增了初始化列表类,在初始化列表部分所介绍的多参数隐式类型转换实际上就是使用了这种初始化列表类对象进行初始化。

初始化列表:template<class T> class initializer_list;

        初始化列表类实际是一个类模板,所以可以实例化定义类对象,其形式类似于数组。

		//实例化初始化列表类auto a = { 1,2,3,4,5 };std::initializer_list<int> b = { 2,3,4 };vector<int> v5(a);vector<int> v6{ 1,2,9 };vector<int> v7({ 2,3,5 });vector<int> v8 = { 1,1,4,5,1,4 };//单参数构造支持隐式类型转换

        这个类只实现了迭代器和size()功能,仅仅是为了作为一种特殊的类方便构造等操作。

		//5.使用初始化列表进行构造//初始化列表:template<class T> class initializer_list;//这是C++11中新增的类型,也是一种类模板。在初始化列表时使用过,使用大括号进行表示。//这个类模板的函数很少。只满足了迭代器和size()vector(std::initializer_list<T> il){reserve(il.size());for (auto& e : il)	//e使用引用,减小拷贝开销{push_back(e);}}

3.析构函数

        释放空间,并且将指针置空。

		//析构函数~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}

4.vector遍历

4.1 size与capacity

        通过size()函数可以得到数组的大小;通过capacity()函数可以得到数组的容量。

		//size,返回vector的数组大小size_t size() const{return _finish - _start;	//指针-指针得到之间元素个数}//capacity,返回vector的容量大小size_t capacity() const{return _endofstorage - _start;}

4.2 [ ]重载元素访问

        重载[ ]操作符以满足任意下标访问操作,因为需要进行访问与修改,所以返回值是引用。对于[ ]同样有const与非const版本。

		//[]运算符重载,重载const和非const版本T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}

4.3 迭代器

        vector的迭代器我们在文章开篇已经进行了介绍,vector由于其连续的物理存储空间的特性,所以可以直接使用原生指针作为迭代器。我们模拟实现时只需要重载const和非const版本即可。

		//非const迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const迭代器const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}

5.赋值操作符

        对于赋值操作符重载,首先要明确是深拷贝。但是在上一篇文章中,我们提出了一种使用swap()函数参与的简便写法,使用传值调用,拷贝构造生成局部对象,然后swap交换后将交换后的局部对象释放即可,相当于复用了拷贝构造实现赋值操作符的功能。

		//swap,交换void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}//赋值运算符重载vector<T>& operator=(vector<T> v){//传参时存在拷贝,将拷贝结果与this交换,原先的this被释放swap(v);return *this;}

6.数组大小与容量调整

6.1 reserve函数

        reserve实现的还是对capacity的控制,对于reserve的参数n,当n>capacity的时候,会进行扩容操作,将容量扩展至n;当n<=capacity时,函数不做任何动作,直接返回。

        在reserve中需要强调的是扩容后数据拷贝的问题。因为C++扩容是另外开辟一个空间,将原来的数据进行拷贝。但是与string不同,string的内容只会是字符,所以memcpy完全可以应付。但是vector的元素可能是任意类型的,不只是内置类型,也有可能是自定义类型。

        当类模板实例化为string、vector等类型时,这时候单纯的对vector进行memcpy就是一个浅拷贝,这就会导致拷贝的内容只是对应自定义类型的成员变量的值,而这些值实际上是指向了开辟的空间。当如此浅拷贝之后,释放原空间这些成员变量就变成了野指针,并且在对象生命周期结束后还会进行一次析构,这样就会引起错误。所以为了避免这种问题,可以在新旧空间之间使用赋值运算符进行手动拷贝,因为是使用了赋值运算符,只要类型T正确重载了合适的赋值运算符,那么就不会出错。

        扩容后由于空间发生了变化,原本的成员变量指针就会失效,所以需要先保存好size,之后释放了原空间仍然可以对成员变量进行正确赋值。

		//reserve,调整容量//	n>capacity:扩容到n//	n<=capacity:不做处理void reserve(size_t n){if (n > capacity()){size_t old_size = size();	//需要先存储原数组size//开辟新的容量的空间tmp,将原数组内容拷贝到tmp中,并释放原空间T* tmp = new T[n];//memcpy(tmp, _start, size() * sizeof(T));	//memcpy属于浅拷贝,在遇到类似于string、vector对象作为元素时,拷贝后的结果仅仅是将要被释放的无效的地址//所以要在此处完成深拷贝,即将原数组中的内容一一赋值给tmpfor (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];	//此处实际调用的是T类型对应的赋值运算符,如数组元素为string,这时只需要保证string的赋值运算符重载是深拷贝,此处调用后完成的就是深拷贝}delete[] _start;//成员变量是指针类型,所以在重新分配空间后地址发生变化,所有成员变量都需要进行更新_start = tmp;_finish = tmp + old_size;	//提前计算size大小,否则当_start变化后,就无法再获得size了_endofstorage = tmp + n;}}

6.2 resize函数

        resize函数用于修改对象的size。当参数n大于对象的size时,则会将size扩大至n,扩大的部分则由参数val来填充,此处val的缺省值是一个匿名对象,这和我们上文在填充构造处的作用一致。当参数n小于等于size时,则会将数组的size缩小为n。

		//resize,调整大小//	n>size:将size扩大至n,扩展出的空间使用参数进行填充//	n<=size:将size缩小至n,截断void resize(size_t n, const T& val = T())//使用T()作为缺省值,T()是T类的匿名对象,被用来赋值的时候会调用其类的构造函数生成对象//使用了匿名对象可以保证对自定义类型的缺省//对于内置类型,C++为其定义一个伪构造函数,即当T是内置类型时,内置类型会被当做对象调用构造函数,如int类型会被默认初始化为0{if (n > size()){reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}else{_finish = _start + n;}}

7.增删查改

7.1 插入

7.1.1 push_back尾插

        顺序表尾插元素使用push_back函数。模拟实现注意reserve函数调用扩容逻辑,然后赋值调整成员变量。

		//push_back:尾插void push_back(const T& val){//检查扩容if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = val;++_finish;}

7.1.2 insert插入

        insert函数支持pos位置前进行插入。注意观察insert的参数,其pos参数是迭代器类型,所以传参的时候需要使用迭代器进行传参。

        插入必然涉及到检查容量,但是需要注意的是,如果发生了扩容,那么就说明空间发生了变化,所以就代表这原指针会失效,所以pos就不可以再使用了。所以需要扩容前记录pos的相对位置,在扩容后对pos进行恢复。

		//insert:在pos位置前插入void insert(iterator pos, const T& val){//检查扩容if (_finish == _endofstorage){size_t old_len = pos - _start;	//pos是iterator类型参数,所以当扩容后整个数组的地址会发生改变,所以pos的值就失去了意义,因此记录相对位置,并使pos随数组做出修改reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + old_len;}iterator it = _finish;while (it != pos){*it = *(it - 1);--it;}*pos = val;++_finish;}

7.1.3 迭代器失效

        插入逻辑也是因为存在扩容的机制,所以可能会产生迭代器失效的问题。原本的迭代器值,可能在对象调用了插入函数之后,扩容导致空间更改,从而再调用迭代器发生迭代器失效。为了避免这种情况,一般应避免在调用了插入函数后再使用之前的迭代器,如果要使用,则应该进行更新。

		//迭代器失效://①在insert、push_back操作后可能会导致迭代器实现,这是由于扩容更改空间导致的vector<int> v2;v2.push_back(1);v2.push_back(2);v2.push_back(3);v2.push_back(4);vector<int>::iterator it1 = v2.begin() + 2;std::cout << *it1 << std::endl;v2.push_back(5);std::cout << *it1 << std::endl;	//打印了随机值,所以it1失效了,insert同理//针对这种迭代器失效,无法避免,所以一般在insert或push_back操作后,就不再使用之前的it//如有需求,可以在插入操作后对it进行更新it1 = v2.begin() + 2;std::cout << *it1 << std::endl;

7.2 删除

7.2.1 pop_back尾删

        pop_back的作用就是尾删。

		//empty:判空bool empty(){return _start == _finish;}//pop_back:尾删void pop_back(){assert(!empty());--_finish;}

7.2.2 erase删除

        erase的作用是删除pos位置的值。

		//erase:删除pos位置的值//void erase(iterator pos)iterator erase(iterator pos)	//erase可能导致迭代器失效,所以返回新的迭代器值,来更新原先的迭代器{assert(pos < _finish && pos >= _start);iterator it = pos;while (it != _finish){*it = *(it + 1);++it;}--_finish;return pos;}

7.2.3 迭代器失效

        删除元素同样会导致迭代器失效。erase的迭代器失效原因是因为可能会触发缩容,同时也可能在遍历中删除配合不协调跳过元素的情况。

        我们针对这个问题,做出了小细节优化。细心观察发现erase有一个iterator的返回值,这是为了方便我们更新迭代器,避免迭代器失效。

        除此之外,对于遍历时存在erase的情况,需要判断如果erase执行了就不可以在使迭代器自增了,否则会出现跳过元素的可能。

		//②在erase后可能会导致迭代器失效//一方面是由于缩容导致的//另一方面则是erase函数和遍历时的协调问题,我们可以进行改进优化//由于我们所实现的erase会直接使用后面的值覆盖,但同时在迭代器遍历时又会让it++,这就使得it跳过了一部分元素,甚至是顺序表的end()导致越界崩溃vector<int> v3({ 1,2,3,4,4,5,6 });vector<int>::iterator it2 = v3.begin();while (it2 != v3.end()){if (*it2 % 2 == 0){it2 = v3.erase(it2);	//为了避免第一种可能,所以改进erase,返回新的迭代器,以供更新}else 	//为了避免第二种可能,所以改进遍历时it自增的机制{it2++;}}//针对erase迭代器失效可以改进erase函数,对迭代器进行更新for (auto e : v3){std::cout << e << ' ';}std::cout << std::endl;

        总结:迭代器失效发生在插入和删除数据(erase)时,要求在使用过insert、push_back、erase之后,原来的迭代器就不可以再使用,如果需要使用则需要先更新。

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

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

相关文章

安全再升级,亚信安慧AntDB数据库与亚信安全二次牵手完成兼容性互认证

日前&#xff0c;湖南亚信安慧科技有限公司&#xff08;简称&#xff1a;亚信安慧&#xff09;的产品与亚信科技&#xff08;成都&#xff09;有限公司&#xff08;简称&#xff1a;亚信安全&#xff09;再次携手&#xff0c;完成亚信安慧AntDB数据库与亚信安全IPoE接入认证系统…

「 网络安全常用术语解读 」SBOM主流格式SPDX详解

SPDX&#xff08;System Package Data Exchange&#xff09;格式是一种用于描述软件组件&#xff08;如源代码&#xff09;的规范&#xff0c;它提供了一种标准化的方法来描述软件组件的元数据&#xff0c;包括其许可证、依赖项和其他属性。SPDX最初由Linux基金会于2010年发起&…

复旦微JFM7VX690计算后IO接口模块,用于雷达信号处理、数据处理等需要高速密集计算的应用场景

计算后IO接口模块 1 介绍 1.1 产品概述 计算后IO接口模块主要由复旦微JFM7VX690型FPGA、国产以太网收发器YT8521、国产BMC芯片GD32F450、国产CPLD芯片EF2L45BG256B、国产内存颗粒等主要芯片组成&#xff0c;采用标准6U VPX尺寸设计。 本计算后IO接口模块主要用于雷达信号处…

Java面试八股之Java中数组有没有length()方法?String呢?为什么?

Java中数组有没有length()方法&#xff1f;String呢&#xff1f;为什么&#xff1f; 数组&#xff1a; 数组没有名为length()的方法&#xff0c;但有一个属性叫做length。可以通过数组名直接访问这个属性来获取数组的长度&#xff08;即元素个数&#xff09;。这是一个整数值&…

【redis】Redis数据类型(三)List类型

目录 List类型介绍特点 List数据结构附&#xff1a;3.2以前的版本(介绍一下压缩列表和双向链表)压缩列表ZipList双向链表LinkedList 常用命令lpush示例 lpushx示例 rpush示例 rpushx示例 LPOP示例 RPOP示例 BLPOP非阻塞行为阻塞行为相同的 key 被多个客户端同时阻塞在 MULTI/EX…

[笔试强训day06]

文章目录 NC10 大数乘法NC1 大数加法NC40 链表相加(二) NC10 大数乘法 NC10 大数乘法 #include <string> #include <vector> class Solution {public:string solve(string s, string t) {int m s.size(), n t.size();reverse(s.begin(), s.end());reverse(t.beg…

Matlab图像处理——基于BP神经网络的车牌标识识别系统

1. 数据集介绍 中国交通标志数据集&#xff1a; https://nlpr.ia.ac.cn/pal/trafficdata/detection.html 该数据集包含58类交通标志。 2. 数据处理 按照文件标签&#xff0c;将数据集划分了58类&#xff0c;如下&#xff1a; 对应的类别信息记录如下&#xff1a; 限速5km/…

企业计算机服务器中了rmallox勒索病毒怎么处理,rmallox勒索病毒处理建议

在网络技术不断发展的时代&#xff0c;网络在企业中的应用广泛&#xff0c;可以为企业带来更多的便利&#xff0c;大大提升了企业的生产效率&#xff0c;但网络作为虚拟世界&#xff0c;在为企业提供便利的同时&#xff0c;也为企业数据安全带来严重威胁。近期&#xff0c;云天…

【YOLO改进】换遍IoU损失函数之EIoU Loss(基于MMYOLO)

EIoU损失函数 设计原理 一、IoU的局限性 IoU&#xff08;Intersection over Union&#xff09;是一种常用于评估目标检测模型性能的指标&#xff0c;特别是在计算预测边界框与真实边界框之间的重叠程度时。然而&#xff0c;IoU存在一些局限性&#xff0c;尤其是当两个边界框…

[python趣味实战]----基于python代码实现浪漫爱心 დ

正文 01-效果演示 下图是代码运行之后的爱心显示结果&#xff1a; 下面的视频该爱心是动态效果&#xff0c;较为简洁&#xff0c;如果需要使用&#xff0c;可以进行完善&#xff0c;这里只是一个趣味实战&#xff0c;下面将对代码实现进行非常详细地描述&#xff1a; 浪漫爱心…

Java数据结构-模拟实现ArrayList

MyArrayList顺序结构&#xff1a; 接口和MyArrayList重写接口 接口 接口中的方法是很多类通用的&#xff0c;所以可以写到接口中 public interface IList {public void add(int data) ;// 在 pos 位置新增元素public void add(int pos, int data);// 判定是否包含某个元素p…

踏上R语言之旅:解锁数据世界的神秘密码(三)

多元相关与回归分析及R使用 文章目录 多元相关与回归分析及R使用一.变量间的关系分析1.两变量线性相关系数的计算2.相关系数的假设检验 二.一元线性回归分析的R计算三、回归系数的假设检验总结 一.变量间的关系分析 变量间的关系及分析方法如下&#xff1a; 1.两变量线性相关…

【C++程序员的自我修炼】string 库中常见的用法 (一)

唤起一天明月照我满怀冰雪浩荡百川流鲸饮未吞海 剑气已横秋 目录 string 库的简介 string 的一些小操作 构造函数的使用 拷贝构造的常规使用 指定拷贝内容的拷贝构造 拷贝字符串开始的前 n 个字符 用 n 个字符初始化 计算字符串的长度 string 的三种遍历方式 常规的for循环 op…

利用大型语言模型提升数字产品创新:提示,微调,检索增强生成和代理的应用

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

「笔试刷题」:字母收集

一、题目 描述 有一个 &#x1d45b;∗&#x1d45a; 的矩形方阵&#xff0c;每个格子上面写了一个小写字母。 小红站在矩形的左上角&#xff0c;她每次可以向右或者向下走&#xff0c;走到某个格子上就可以收集这个格子的字母。 小红非常喜欢 "love" 这四个字母。…

FFmpeg开发笔记(二十三)使用OBS Studio开启RTMP直播推流

OBS是一个开源的直播录制软件&#xff0c;英文全称叫做Open Broadcaster Software&#xff0c;广泛用于视频录制、实时直播等领域。OBS不但开源&#xff0c;而且跨平台&#xff0c;兼容Windows、Mac OS、Linux等操作系统。 OBS的官网是https://obsproject.com/&#xff0c;录制…

【报错处理】ib_write_bw执行遇到Couldn‘t listen to port 18515原因与解决办法?

要点 要点&#xff1a; ib默认使用18515端口 相关命令&#xff1a; netstat -tuln | grep 18515 ib_write_bw --help |grep port# server ib_write_bw --ib-devmlx5_1 --port88990 # client ib_write_bw --ib-devmlx5_0 1.1.1.1 --port88990现象&#xff1a; 根因&#xff…

为什么公共事业机构会偏爱 TiDB :TiDB 数据库在某省妇幼健康管理系统的应用

本文介绍了某省妇幼健康管理系统的建设和数据库架构优化的过程。原有的数据库架构使用了 StarRocks 作为分析层&#xff0c;但随着业务的发展&#xff0c;这套架构暴露出诸多痛点&#xff0c;不再适应妇幼业务的需求。为解决这些问题&#xff0c;该系统选择了将原有架构中的 St…

OBSERVER(观察者)-- 对象行为模式

意图&#xff1a; 定义对象间地一种一对多地依赖关系&#xff0c;当一个对象地状态发生改变时&#xff0c;所有对于依赖于它的对象都得到通知并被自动更新。 别名&#xff1a; 依赖(Dependents), 发布-订阅(Publish-Subsribe) 动机&#xff1a; 将一个系统分割成一系列相互协…

使用Python及R语言绘制简易数据分析报告

Pytohn实现 在python中有很多包可以实现绘制数据分析报告的功能&#xff0c;推荐两个较为方便的包&#xff1a;pandas-profiling 和 sweetviz 。 使用 pandas-profiling 包&#xff08;功能全面&#xff09; 这个包的个别依赖包与机器学习的 sklearn 包的依赖包存在版本冲突&a…