vector类——常用函数模拟(C++)

        在上一篇中我们介绍了 string 类的常用函数模拟,接下来我们将开始讲解 vector 类的常用函数的讲解以及模拟实现,相较于 string 来说,vector 的函数不那么冗余,用法也没有那么多,但是在 vector 中的函数使用和模拟中,也存在很多的细节,我们将在以下一一讲解。

        我们将按照迭代器、容量、获取元素、修改、默认成员函数的顺序讲解和实现函数,在实现函数的时候,详细了给出实现该函数需要注意的事项。最后谈了谈迭代器失效的情况,给出了使用迭代器的使用注意事项。

        讲解的顺序按照网站:vector - C++ Reference (cplusplus.com):

        目录如下:

目录

1. Iterator —— 迭代器与成员变量

2. Capacity —— 容量

3. Element access —— 获取元素

4. Modify —— 修改内容函数

5. 默认成员函数

6. 迭代器注意事项 —— 迭代器失效

1. Iterator —— 迭代器与成员变量

        首先我们要介绍的便是 vector 的迭代器,迭代器的主要作用可以帮助我们遍历成员,还有有些函数的需要传入的参数也是需要传入迭代器,迭代器的种类有以下几种,我们只介绍其中的 begin 和 end:

        但是在介绍迭代之前,我们给出 vector 的成员变量。我们的成员变量根据 Linux 下的 g++ 编译器下的 vector ,如下:

        在 g++ 下的 vector 存在内存配置器,本篇就不实现该内存配置器,因为在大多数情况下都用不到,所以我们的成员变量为 start finish end_of_storage。同时需要注意的是 vector 和 string 不同,vector 需要使用适用于多种变量,所以我们需要适用模板实现。

        iterator 的实现同时还需要注意要适用于被 const 修饰的变量,iterator 的实现如下:

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

	private:
		// 成员初始化为 nullptr,就不用走初始化列表了
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
	};
}

2. Capacity —— 容量

        接着我们实现和容量相关的函数,我们需要实现和掌握的函数如下:

        其中的重点为 resize 和 reserve 函数,对于其他函数的实现都较为轻松,我们先实现 size、capacity、empty 函数,如下:

	size_t size() const {
        // 指针减指针,返回指针间的元素个数
		return _finish - _start;
	}

	size_t capacity() const {
		return _endofstorage - _start;
	}

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

        接下来我们实现 reserve 函数,reserve 函数的作用为对 vector 进行扩容,传入的参数为需要扩容的容量。

        对于reserve 函数的实现,我们需要注意的是只有在传入的长度大于当前容量的时候,我们才对其接下扩容。进行扩容的时候,我们需要重新开辟一段空间,然后将原始空间的内容拷贝回去,然后释放原始空间,将 成员变量指向新的空间。实现 reserve 需要注意的细节

        1. 在将 _finish 指向新的空间的时候,我们不能使用 _start + size() 实现,因为对于 size() 的返回值为 _finish - _start ,将其展开之后,_finish = _start + _finish - _start ,这就导致,_finish 的值根本没有改变。所以我们需要使用一个值记录原始 _start _finish 之间的间距。

        2. 在将原始空间拷贝到新空间的时候,不能使用 memcpy 将其拷贝到新空间。因为:假若我们 vector 为 vector<int> 那么使用 memcpy 拷贝过去的时候,确实可以每个值都拷贝过去,但是当我们将 vector 实现为 vector<string> 的时候,在 vector 开辟的空间中存在的 string 成员也会指向一块属于他的空间,存在于 vector 中的只是 string 的成员变量,假若我们将原空间拷贝到新空间之后,将原空间释放的时候,就会调用到 string 的析构函数,然后新空间中的 string 指向的就是一系列被释放掉的空间,当我们访问这段空间的时候,就是非法访问,析构的时候也是释放已经被释放的空间。解决这个问题的方法:将原空间的值一个一个拷贝到新空间,然后在释放原空间,因为一个一个拷贝到新空间,也会调用对应的拷贝构造函数。实现如下:

	void reserve(size_t num) {
		if (num > capacity()) {
			// 记录 _finish 与 _start 的相对位置
			size_t len = size();
			T* tmp = new T[num];
			//不能使用memcpy(tmp, _start, size() * sizeof(T));
			for (int i = 0; i < len; i++) {
				tmp[i] = _start[i];
			}
			delete[] _start;

			_start = tmp;
			_finish = _start + len;
			_endofstorage = _start + num;
		}
	}

        接着是对 resize 函数的使用,resize 函数的作用为对 vector 的 size 进行控制,当传入的参数大于当前的 size 的时候,我们将使用默认缺省值将其多出来的 size 进行初始化,传入的参数小于当前的 size 时,就i将 _finish 向前进行移动即可,具体实现如下:

	void resize(size_t num, const T& val = T()) {
		if (num > size()) {
			reserve(num);
			while (_finish < _start + num) {
				*_finish = val;
				_finish++;
			}
		}
		else {
			_finish = _start + num;
		}
	}

        由上的代码我们可以发现,我们可以发现对于默认缺省给出的是一个匿名对象,对于自定义类型来说,匿名对象可以给出,但是对于 int、char 等内置类型的对象,也存在匿名对象吗?答案是存在的,在 Cpp 中,为了使缺省值匿名对象更加使用,对内置类型也给出了匿名对象,对于浮点型给出的为 0.0,对整型给出的为 0,对指针给出的为 nullptr

3. Element access —— 获取元素

        这段便介绍获取元素的函数,对于获取元素的函数如下:

        其中很常用的就是对 [ ] 的运算符重载,其次为 front 和 back 函数,front 和 back 分别为获取第一个元素和最后一个元素。对于 [ ] 的运算符重载实现,我们只需要先检查需要获取的位置是否合法,然后将对应位置的元素传引用传回去即可。该函数还存在一个由 const 修饰的重载函数,用于被 const 修饰的变量。实现如下:  

	T& operator[](iterator pos) {
		assert(pos < _finish && pos >= _start);
		return *pos;
	}

	const T& operator[](iterator pos) const {
		assert(pos < _finish && pos >= _start);
		return *pos;
	}

4. Modify —— 修改内容函数

        关于 modify 的函数便是 vector 中常用的函数,也是需要实现的重头戏,需要掌握以及会在本篇实现的如下:

        我们首先实现较为简单的函数,clear ,该函数的作用为将 vector 的 size 清零,所以实现该函数只需要将 _finish 指针指向 _start 指向的地址即可。实现如下:

	void clear() {
		_finish = _start;
	}

        接下来我们实现,push_backpop_back 函数,前者的作用为:尾插一个元素,后者的作用为:尾删一个元素。实现 push_back 函数只需要判断是否需要扩容,然后加入一个元素,pop_back 函数只需判断当前 size 是否为 0,然后在删除一个元素,实现如下:

	void pop_back() {
		assert(!empty());
		_finish--;
	}

	void push_back(const T& val) {
		if (_finish == _endofstorage)
			reserve(capacity() == 0 ? 4 : 2 * capacity());
		*_finish = val;
		_finish++;
		
		// 使用尾插也可以
		//insert(end(), val);
	}

        接着实现 swap 函数,该函数作用为与另一个 vector 变量交换。实现的原理也较为简单,我们只需要调用 std 中的 swap 函数,将两个变量的 _start _finish _endofstorage,给交换即可。实现如下:

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

        最后我们实现 insert 函数和 erase 函数,前者的作用为在指定位置插入元素,后置的作用为在指定位置删除元素,对于这两个函数传入的参数都为迭代器,不能传入数字

        先实现 erase 函数,在实现该函数的时候,我们只需要先判断传入的位置是否合法,然后再通过循环将 pos 位置后的元素往前移一位,最后的返回值为新删除元素的这个位置。实现如下:

	iterator earse(iterator pos) {
		assert(pos >= _start && pos < _finish);
		iterator begin = pos;
		while (pos != _finish - 1) {
			*pos = *(pos + 1);
			pos++;
		}
		_finish--;
		return pos;
	}

        然后 insert 函数,对于该函数的实现,首先还是判断需要插入的位置是否合法,然后判断是否需要扩容,若要扩容,直接调用 reserve 函数即可,然后将 pos 位置及以后的元素都向后移动一位,最后的返回值为新插入元素的这个位置,实现如下:

	iterator insert(iterator pos, const T& val) {
		// 对数据进行插入,需要判断插入的位置
		assert(pos <= _finish && pos >= _start);

		// 进行扩容的同时,需要将 pos 也给更改
		if (_finish == _endofstorage) {
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : 2 * capacity());
		}
		// 对数据进行挪动
		iterator end = _finish;
		while (end != pos) {
			*end = *(end - 1);
			end--;
		}
		*pos = val;
		_finish++;
		return pos;
	}

5. 默认成员函数

        最后需要讲解的便是 vector 的默认成员函数,我们通常只实现四种默认成员函数:构造函数、拷贝构造函数、赋值运算符重载、析构函数。但是对于这些成员的函数的实现,也存在很多的细节。

        我们首先先介绍易于实现的成员函数:析构函数,析构函数只需要将空间释放,然后将指针置 nullptr 即可,如下:

	~vector() {
		if (_start != nullptr) {
			// 当存入的是一个 string 的时候,需要先分别释放空间,然后
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}

	}

        接着实现赋值运算符重载函数,对于赋值运算符重载函数的实现,按照普通的写法,我们只需要开辟一段一个与被拷贝成员一样大的空间,然后将值拷贝过去即可。但是我们可以直接将赋值运算符重载函数的形参设置为一个临时变量,非引用非 const 修饰,然后在函数体内调用 swap 函数,就可以成功达到赋值运算符重载的功能,原因为:当我们使用一个形参作为参数,当实参传入进来的时候,将形参拷贝构造给了形参,然后此时与形参交换值,函数退出时候,析构的也是原地址的空间,实现如下:

	vector& operator=(vector v) {
		swap(v);
		return *this;
	}

    // 普通写法
	vector& operator=(const vector& v) {
		reserve(v.capacity());
		//memcpy(_start, v._start, sizeof(T) * v.size());
		//_finish = _start + v.size();
		for (auto& e : v)
			push_back(e);
		return *this;
	}

        接下来我们实现拷贝构造函数,关于拷贝构造的实现的不能像赋值运算符重载在形参阶段调用拷贝构造函数,因为这样会导致无限循环调用的情况。我们只能老实的开辟空间然后一个一个的赋值即可,如下:

	// 拷贝构造
	vector(const vector& v) {
		reserve(v.capacity());
        // 对于范围for的使用,最好使用引用,因为v中的变量空间可能也很大
		for (auto& e : v)
			push_back(e);
	}

        最后实现我们的构造函数,对于构造函数的实现存在多种构造函数,我们先实现默认拷贝构造函数,对于默认拷贝构造函数,可以传入两个值,一个为需要开辟元素个数的空间,一个为开辟空间的后元素初始化为什么。这个默认构造函数的功能其实和 resize 非常相似,所以我们直接在函数中调用 resize 函数即可,如下:

	vector(size_t num = 0, const T& val = T()) {
		// 进行扩容,然后将所有的值变成 val
		resize(num, val);
	}

        接下来需要实现的迭代器区间构造函数,迭代器区间构造函数需要传入的参数为两个迭代器,然后从第一个参数迭代拷贝到第二个参数,实现如下:

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

        当实现以上迭代器区间构造函数之后,我们会发现当我们再一次调用构造函数的时候,会报错,这是因为迭代器区间构造函数与默认构造函数发生了冲突,当我们想调用默认构造函数的时候,比如:MyVector::vector<int> v1(10, 6); 反而会调用到迭代器区间构造函数,这是因为传入 v1 中的 10 和 6 为 int 类型,而默认构造函数中的第一个参数类型为 size_t,与 int 类型不对应,但是这个时候我们写出了迭代器区间构造函数,这个函数为一个模板函数,所以想要调用默认构造函数反而调用到了迭代器区间构造函数,在函数内存在解引用,所以会报错,所以想要解决这个问题,我们只需要写出一个非默认构造函数,如下:

	// 创建一个重载构造函数,防止vector<int>类与模板迭代器区间构造冲突
	vector(int num, const T& val = T()) {
		// 进行扩容,然后将所有的值变成 val
		resize(num, val);
	}

        初始化列表构造函数,在 C++11 标准中,引入了初始化列表构造函数,如下:

        以上两种调用初始化列表函数的方式分别为:第一种直接调用构造函数,先对构造函数中的参数初始化,第二种,隐式类型转换,先调用构造函数生成一个 vector 然后调用拷贝构造函数,但编译器一般会优化,直接调用构造函数。        

        实现初始化列表函数如下:

	vector(initializer_list<T> il) {
		// 初始化列表初始化
		auto it = il.begin();
		while (it != il.end()) {
			push_back(*it);
			it++;
		}
	}

6. 迭代器注意事项 —— 迭代器失效

        在使用迭代器的时候,存在一个严重的问题,那就是迭代器失效的问题。如下这种情况

        对于以上的这种情况,我们发现第一次打印的时候,可以正常的打印,但是增加一个元素之后在使用迭代器打印却出错了。这是因为增加一个元素之后,原 vector 变量进行了扩容,然后就到了新开辟的空间处,但是此时的 it 还是指向原来的地方,所以会出错。

        以上这种情况为增容时会出错,那么是不是只要不增容就不会出错,只能说不一定,因为对于 vector 中的函数值规定了功能,并没有规定该怎么实现,每个平台都有每个平台实现的风格,万一某些版本的 vector,会对 size 小于 capacity 一般的变量进行缩容呢?

        所以对于迭代器的使用:

        1. 使用之后最好就不再使用,可以重新定义一个;

        2. 当再次使用的时候,我们可以将其更新。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单链表的实现(单链表的增删查改)

在顺序表中实现数据的增删的操作时&#xff0c;都要把操作位置之后的数据全部移动一遍&#xff0c;操作效率低下。其次是容量固定&#xff08;静态顺序表&#xff09;&#xff0c;虽然在动态顺序表中容量可变&#xff0c;但也会造成空间上的浪费。 单链表就完美解决了上述缺点…

微服务架构与Dubbo

一、微服务架构 微服务架构是一种架构概念&#xff0c;旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。 分布式系统式若干独立系统的集合&#xff0c;但是用户使用起来好像是在使用一套系统。 和微服务对应的是单体式开发&#xff0c;即所有的功能打包在一个WAR…

No spring.config.import property has been defined

运行Springcloud项目出现下面错误&#xff1a; Description: No spring.config.import property has been defined Action: Add a spring.config.importnacos: property to your configuration. If configuration is not required add spring.config.importoptional:nac…

C 排序算法

冒泡排序 冒泡排序&#xff08;英语&#xff1a;Bubble Sort&#xff09;是一种简单的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序&#xff08;如从大到小、首字母从A到Z&#xff09;错误就把他们交换过来。 过程演示&…

校园综合服务平台V3.9.2 源码修复大部分已知BUG

校园综合服务平台&#xff0c;版本更新至V3.9.1 &#xff0c;源码功能强大&#xff0c;ui 精美&#xff0c; 功能包含但不限于校园跑腿&#xff0c;外卖&#xff0c;组局&#xff0c;圈子&#xff0c;商城&#xff0c;抽奖&#xff0c;投票&#xff0c;团购&#xff0c;二手市场…

ROS学习笔记(12)AEB和TTC的实现

0.前提 在自动驾驶领域有许多关于驾驶安全的措施AEB和TTC就是为了驾驶安全而设计出来的。在这篇文章中我会讲解我对AEB和TTC算法的一些理解。本期ROS学习笔记同时也是ros竞速小车的学习笔记&#xff0c;我会将我的部分代码拿出来进行讲解&#xff0c;让大家更好的理解ttc和aeb…

Zabbix监控系统

一.监控软件的作用: 作为一个运维&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果和网站的健康状态 利用一个优秀的监控软件&#xff0c;我们可以&#xff1a; 对系统不间断实时监控实时反馈系统当前状态…

挣钱新玩法,一文带你掌握流量卡推广秘诀

手机流量卡推广项目是什么&#xff1f;听名字我相信大家就已经猜出来了&#xff0c;就是三大运营商为了开发新用户&#xff0c;发起的有奖推广活动&#xff0c;也是为了长期黏贴用户。在这个活动中&#xff0c;用户通过我们的渠道&#xff0c;就能免费办理低套餐流量卡&#xf…

链表OJ - 7(链表的回文结构)

题目描述&#xff08;来源&#xff09; 对于一个链表&#xff0c;请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法&#xff0c;判断其是否为回文结构。 给定一个链表的头指针A&#xff0c;请返回一个bool值&#xff0c;代表其是否为回文结构。保证链表长度小于等于900。…

【SVG】从零开始绘制条形图

效果图 定义背景色和坐标轴颜色 :root {--cord-color: #2be7ca; }body {background-color: #000;}画坐标轴 画X轴 <!-- 坐标轴 --> <g id"cordinate"><!-- x轴 --><line x1"50" y1"600" x2"900" y2"600&q…

同城货运系统的开发与货运搬家软件的技术性探讨和市场分析

一、市场前景展望 随着城市化进程的加快和电商物流的蓬勃发展&#xff0c;同城货运市场展现出了巨大的潜力。尤其是在快节奏的生活环境中&#xff0c;个人和企业对于快速、便捷、可靠的货运搬家服务需求日益增长。同城货运系统与货运搬家软件作为连接货主与货运司机的桥梁&…

Opengl 坐标系统概述

1.谈到opengl 坐标系统 首先要知道三个坐标转换矩阵&#xff0c;模型矩阵&#xff0c;观察矩阵&#xff0c;投影矩阵。 模型矩阵作用在将以物体中心为原点的坐标系统&#xff0c;转换到世界坐标。 观察矩阵作用在将世界坐标系统转换到观察坐标系统 投影矩阵作用在将观察坐标…

2024年苹果审核4.3相关问题综述

苹果审核中的4.3问题是开发者关注的焦点之一&#xff0c;本文对此进行了综述&#xff0c;总结了不同情况下的处理方式和优化策略。 第一种4.3 该类问题常见于代码或UI的重复率过高&#xff0c;苹果会直接拒绝应用。开发者需注意避免此类情况的发生&#xff0c;特别是在更新应…

亚信安全数据安全运营平台DSOP新版本发布 注入AI研判升维

在当今快速发展的数字经济时代&#xff0c;企业对于数据的依赖日益加深&#xff0c;数据安全已成为企业的生命线。亚信安全推出数据安全运营平台DSOP全新版本&#xff0c;正是为满足企业对数据安全的高度需求而设计。这款平台以其卓越的能力和技术优势&#xff0c;为企业的数据…

逆向案例二十七——某笔网登录接口非对称加密算法RSA,涉及全扣代码,浏览器断点调试,和补环境

网址&#xff1a;aHR0cHM6Ly93d3cuZmVuYmkuY29tL3BhZ2UvaG9tZQ 点击账号密码登录&#xff0c;找到登陆的包&#xff0c;发现password进行了加密。 顿时&#xff0c;老生常谈&#xff0c;开始搜索&#xff0c;找到最有嫌疑的加密代码。进行搜索&#xff0c;进入js文件后&#x…

云计算:Linux 部署 OVS 集群(服务端)实现VXLAN

目录 一、实验 1.环境 2.Linux 部署 OVS 集群&#xff08;服务端&#xff09; 3.Linux 部署VXLAN 一、实验 1.环境 (1) 主机 表1 宿主机 主机架构软件IP备注ovs_controller控制端192.168.204.63 1个NAT网卡 &#xff08;204网段&#xff09; ovs_server01服务端 Openv…

康谋技术 | 深入探讨:自动驾驶中的相机标定技术

随着自动驾驶技术的快速发展&#xff0c;多传感器的数据采集和融合可以显著提高系统的冗余度和容错性&#xff0c;进而保证决策的快速性和正确性。在项目开发迭代过程中&#xff0c;传感器标定扮演着至关重要的角色&#xff0c;它位于数据采集平台与感知融合算法之间&#xff0…

Python学习之-typing详解

前言&#xff1a; Python的typing模块自Python 3.5开始引入&#xff0c;提供了类型系统的扩展&#xff0c;能够帮助程序员定义变量、函数的参数和返回值类型等。这使得代码更易于理解和检查&#xff0c;也方便了IDE和一些工具进行类型检查&#xff0c;提升了代码的质量。 typ…

Unity之OpenXR+XR Interaction Toolkit快速监听手柄任意按键事件

前言 当我们开发一个VR时,有时希望监听一个手柄按键的点击事件,或者一个按钮的Value值等。但是每次有可能监听的按钮有不一样,有可能监听的值不一样,那么每次这么折腾,有点累了,难道就没有一个万能的方法,让我可以直接监听我想要的某个按钮的事件么? 答案是肯定的,今…

【结构型模式】代理模式

一、代理模式概述 代理模式的定义-意图&#xff1a;给某一个对象提供一个代理或占位符&#xff0c;并由代理对象来控制来原对象的访问(对象结构型模式)。某个客户端不能直接操作到某个对象&#xff0c;但又必须和那个对象有所互动。 代理模式分析&#xff1a; 1.引入一个新的代…