C++:vector类(default关键字,迭代器失效)

news2024/11/23 20:29:14

目录

前言

成员变量结构

iterator定义

size

capacity

empty

clear

swap

[]运算符重载

push_back

pop_back

reserve

resize

构造函数

默认构造函数

default

迭代器构造

拷贝构造函数

赋值重载函数

析构函数

insert

erase

迭代器失效问题

insert失效

erase失效

resize失效

clear失效

总结

完整代码


前言

vector其实就是数据结构中的顺序表,建议先学会顺序表

C数据结构:顺序表-CSDN博客

成员变量结构

template<class T>
class vector
{
public:

private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;
};

这里是使用了iterator迭代器来定义了三个成员变量

_start指向顺序表的开始位置

_finish指向顺序表最后元素的下一个位置

_end_of_storage指向顺序表开辟的最后一个空间

当然我们也可以使用顺序表里那种方法用一个*a,size,capacity来完成vector,但库里是用这三个变量,那么就保持一致好了

那么这个iterator是什么?

iterator定义

template<class T>
class vector
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;

private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;
};

这个iterator其实本质就和上一节string类的iterator一致,只不过这里是T*

C++:string类(auto+范围for,typeid)-CSDN博客

有iterator就顺带把const_iterator定义了,方便稍后实现const迭代器的函数

既然有了迭代器,那么begin和end也就很容易实现出来了

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

size

size_t size() const
{
	return _finish - _start;
}

指针相减是指针之间的偏移量个数,首尾指针相减即是顺序表的大小 

capacity

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

与size同理

empty

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

开始和结束指针指向同一块即为空

clear

void clear()
{
	_finish = _start;
}

swap

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

与string类一致 

[]运算符重载

T& operator[](size_t i)
{
	return _start[i];
}

const T& operator[](size_t i) const
{
	return _start[i];
}

这里是实现了两个版本

第一个是vector<T>对象使用,第二个是给const vector<T>对象使用

push_back

void push_back(const T& x)
{
	*_finish = x;
	_finish++;
}

直接在finish指针指向的位置填值即可

但我们哪来的空间给我们放数据?所以这里需要一个扩容逻辑,我们可以封装成与库里一样的函数

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

	*_finish = x;
	_finish++;
}

当空间满时则需要扩容(reserve)

pop_back

void pop_back()
{
	_finish--;
}

--秒了

reserve

void reserve(size_t n)
{
	if (capacity() < n)
	{
		size_t len = size();
		T* tmp = new T[n];
		memcpy(tmp, _start, sizeof(T) * size);
		delete[] _start;

		_start = tmp;
		_finish = tmp + len;
		_end_of_storage = tmp + n;
	}
}

这里扩容的思路是用tmp指针创建一个大小为n的空间,将旧空间的内容拷贝到新空间中,释放旧空间,然后让_start,_finish,_end_of_storage指向相应的位置 

为了记录finish在原空间中的偏移量,我们需要用一个len变量来保存

但是这段代码是有问题的!!!

首先我们要知道memcpy是浅拷贝(值拷贝),不了解的可以先看下面深浅拷贝的章节

C++:类和对象 II(默认成员函数,深浅拷贝)-CSDN博客​​​​​​

这里如果T是int,double这些内置类型就没什么问题

但是如果T是string,vector这些需要再开空间的容器就会出问题

假设T是string类

那么_start每一块空间都是一个string类型,都有一个指针又指向一串字符串

 

这时候如果用memcpy会发生值拷贝,只会将string里的指针拷贝给tmp新空间中,这个浅拷贝会导致大家都指向一块空间

 

所以我们需要一个深拷贝,具体实现方法如下: 

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

		_start = tmp;
		_finish = tmp + len;
		_end_of_storage = tmp + n;
	}
}

用一个for循环将里面的值赋值给新空间

如果是string,我们会调用string的赋值重载,而string的赋值重载也是深拷贝,所以就完成了

总结:T类型如果是内置类型,则正常赋值,若为自定义类型,调用自定义类型的赋值重载函数

resize

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

 reserve是扩容,调整的是_end_of_storage,而resize是调整大小,变的是_finish

若resize的值小于容量大小,那么直接调整_finish到相应位置即可

若resize的值大于容量大小,那么不仅需要变_finish,还需要变_end_of_storage扩容

扩容只需要复用reserve即可

构造函数

默认构造函数

vector() = default;

由于我们的成员变量是三个指针,只需要让它们走初始化列表初始化为nullptr即可,系统自动生成的就够用了 

default

default关键字用于显式地要求编译器为特殊成员函数生成默认实现

可以用于6个默认成员函数中的任意一个,使用方法都如上一样

迭代器构造

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

该构造函数可以用其他容器的迭代器来构造自己

所以为了能够接收其他类的迭代器我们需要新用一个模板来接收

让迭代器里的值依次push_back到当前vector中即可 

vector(size_t n, const T& val = T())
{
	reserve(n);
	while (n--)
	{
		push_back(val);
	}
}

该构造函数也是扩容+复用push_back与上面相同的逻辑

但是这里有个问题!!

若是我们要调用该函数是这样的:

vector<int> v(5, 1);

它的意思是构造顺序表空间为5值为1

看起来这里会调用到该函数,其实它会调用到上面的迭代器构造函数

这是为什么呢? 

分析:

5的类型是int,1的类型也是int,但是我们定义的是size_t和T类型,实例化后就是size_t和int类型

但是这个InputIterator如果实例化也是int就是两个int,显然这个构造会让我们编译器选择,自然就货不对板了

所以我们需要自己写其他类型来构成重载

vector(int n, const T& val = T())
{
	reserve(n);
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

vector(unsigned int n, const T& val = T()) 
{
	reserve(n);
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

这样无论是int,还是size_t,还是unsigned int都可以正常调用了 

拷贝构造函数

vector(const vector<T>& v)
{
	reserve(v.size());
	for (auto& e : v)
	{
		push_back(e);
	}
}

扩容

将v里的数据遍历并复用push_back到当前vector类中 

赋值重载函数

写法1:

vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		clear();

		reserve(v.size());
		for (auto& e : v)
		{
			push_back(e);
		}
	}
	return *this;
}

与前面的拷贝构造思路一致

写法2:现代写法

vector<T>& operator=(vector<T> v)
{
	swap(v);

	return *this;
}

与string类中的写法思路一致

C++:string类(auto+范围for,typeid)-CSDN博客

析构函数

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

清理工作:释放空间

insert

iterator insert(iterator pos, const T& x) 
{
	if (_finish == _end_of_storage)
	{
		size_t n = pos - _start; 
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + n;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}

	*pos = x;
	_finish++;
	return pos;
}

首先是扩容逻辑,但这里并不是简单的扩容就可以了,这里的扩容存在迭代器失效问题

首先我们的pos是在旧空间的,若是贸然扩容那么pos的位置就无法在新空间找到了

所以我们需要先用一个变量n来记录pos相对于_start的偏移量

然后只需要重新让pos指向新空间相应位置即可

剩下的就是从后往前挪动数据,最后在pos位置插入x即可,与string的insert逻辑相同

erase

void erase(iterator pos)
{
	iterator cur = pos + 1;
	while (cur != _finish)
	{
		*(cur - 1) = *cur;
		cur++;
	}
	_finish--;
}

挪动覆盖数据即可,与顺序表逻辑一致 

迭代器失效问题

insert失效

insert的使用是存在迭代器失效的问题的

int main()
{
	vector<int> v = { 1, 2, 3, 4, 5 };
	auto it = v.begin() + 2;
	v.insert(it, 99);

	for (auto x : v)
		cout << x << " ";
	cout << endl;

	cout << *it;
	
	return 0;
}

在这段代码中,如果在vs的环境下运行是会报错的!

因为我们用it这个迭代器插入后,it这个迭代器就失效了,此时再访问则会有问题,vs的选择是直接报错!

为什么会失效?

迭代器失效问题具体是因为内存管理机制所导致的

当在任意位置插入新元素时,如果当前容量不足以容纳更多元素,会重新分配内存。这会导致所有指向旧内存的迭代器、引用和指针失效。即使容量足够,插入点之后的迭代器也会因为元素移动而失效

所以在使用insert后迭代器一定不能使用!!!

erase失效

erase也存在迭代器失效问题

int main()
{
	vector<int> v = { 1, 2, 4, 5 };
	auto it = v.begin();
	
	for (auto x : v)
	{
		if (x % 2 == 0)
		{
			v.erase(it);
		}
		it++;
	}

	return 0;
}

这是一段删除偶数的代码

这里迭代器失效就体现在连续偶数的情况下

若删除2,此时4会在2的位置,那么it还要++就会跳过这个4,所以这也是一种迭代器失效的体现

总结:当从容器中删除元素时,虽然通常不会导致内存重新分配,但删除点之后的迭代器会因为元素的移动而失效

在vs中运行会直接报错!和insert一样

正确写法:

int main()
{
	std::vector<int> v = { 1, 2, 3,4, 5 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			it = v.erase(it);
		}
		else
		{
			it++;
		}
	}

	return 0;
}

若进行了删除操作,我们只需要让it指向删除的位置即可,反之it++

resize失效

当使用resize函数改变vector的大小时,如果新大小大于当前容量,vector会重新分配内存,导致所有迭代器、引用和指针失效。如果新大小小于当前大小,则会发生元素删除,删除点之后的迭代器会失效

clear失效

虽然clear函数不会减少vector的容量,但它会删除所有元素,因此所有指向元素的迭代器、引用和指针都会失效

总结

为了避免迭代器失效问题,建议在执行插入、删除、调整大小或清空操作后,重新获取或更新迭代器。例如,insert和erase函数都会返回指向新位置的有效迭代器,可以使用这些返回的迭代器进行后续操作

完整代码

#pragma once

#include<iostream>
#include<vector>
#include<list>
#include<string>
using namespace std;

namespace lyw
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//C++11 前置生成默认构造
		vector() = default;

		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);	
				first++;
			}
		}	
			
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T()) // test6的v7
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(unsigned int n, const T& val = T()) // test6的v7
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		void clear()
		{
			_finish = _start;
		}

		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this != &v)
		//	{
		//		clear();

		//		reserve(v.size());
		//		for (auto& e : v)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	return *this;
		//}

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

		vector<T>& operator=(vector<T> v)
		{
			swap(v);

			return *this;
		}

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

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		void reserve(size_t n)
		{
			if (capacity() < n)
			{
				size_t len = size();
				T* tmp = new T[n];
				//memcpy(tmp, _start, sizeof(T) * size); 浅拷贝 vector<string>...中会报错
				for (size_t i = 0; i < len; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;

				_start = tmp;
				_finish = tmp + len;
				_end_of_storage = tmp + n;
			}
		}

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

		size_t size() const
		{
			return _finish - _start;
		}

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

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

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

			*_finish = x;
			_finish++;
		}

		void pop_back()
		{
			_finish--;
		}

		iterator insert(iterator pos, const T& x) // 存在迭代器失效问题
		{
			if (_finish == _end_of_storage)
			{
				size_t n = pos - _start; // 记录偏移量,否则一旦扩容pos的位置就无效了
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + n;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}

			*pos = x;
			_finish++;
			return pos;
		}

		void erase(iterator pos)
		{
			iterator cur = pos + 1;
			while (cur != _finish)
			{
				*(cur - 1) = *cur;
				cur++;
			}
			_finish--;
		}

		T& operator[](size_t i)
		{
			return _start[i];
		}

		const T& operator[](size_t i) const // 为了给const vector<T>对象使用
		{
			return _start[i];
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

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

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

相关文章

Linux使用学习笔记3 系统运维监控基础

系统运维监控类命令 查询每个进程的线程数 for pid in $(ps -ef | grep -v grep|grep "systemd" |awk {print $2});do echo ${pid} > /tmp/a.txt;cat /proc/${pid}/status|grep Threads > /tmp/b.txt;paste /tmp/a.txt /tmp/b.txt;done|sort -k3 -rn for pid…

mfc100u.dll丢失问题分析,详细讲解mfc100u.dll丢失解决方法

面对mfc100u.dll文件丢失带来的挑战时&#xff0c;许多用户都可能感到有些无助&#xff0c;尤其是当这一问题影响到他们日常使用的软件时。但实际上&#xff0c;存在几种有效方法可以帮助您快速恢复该关键的系统文件。为了方便不同水平的用户&#xff0c;本文将详细解析各种处理…

自动化测试工具Selenium IDE

简介 Selenium IDE 是实现Web自动化的一种便捷工具&#xff0c;本质上它是一种浏览器插件。该插件支持Chrome和Firefox浏览器&#xff0c;拥有录制、编写及回放操作等功能&#xff0c;能够快速实现Web的自动化测试。 使用场景 1、Selenium IDE本身的定位并不是用于复杂的自动…

Ps:首选项 - 技术预览

Ps菜单&#xff1a;编辑/首选项 Edit/Preferences 快捷键&#xff1a;Ctrl K Photoshop 首选项中的“技术预览” Technology Previews选项卡允许用户启用或禁用一些实验性功能&#xff0c;以测试或使用 Adobe 提供的最新技术。 技术预览 Technology Previews 启用保留细节 2.0…

如何解决浏览器页面过曝,泛白等问题

问题描述&#xff0c;分别对应edge和chrome浏览器这是什么原因&#xff1f;

使用C#禁止Windows系统插入U盘(除鼠标键盘以外的USB设备)

试用网上成品的禁用U盘的相关软件&#xff0c;发现使用固态硬盘改装的U盘以及手机等设备&#xff0c;无法被禁止&#xff0c;无奈下&#xff0c;自己使用C#手搓了一个。 基本逻辑&#xff1a; 开机自启&#xff1b;启动时&#xff0c;修改注册表&#xff0c;禁止系统插入USB存…

字符串函数!!!(续)(C语言)

一. strtok函数的使用 继续上次的学习&#xff0c;今天我们来认识一个新的函数strtok&#xff0c;它的原型是char* strtok(char* str,const char* sep)&#xff0c;sep参数指向了一个字符串&#xff0c;定义了用作分隔符的字符合集&#xff0c;第一个参数指定⼀个字符串&#…

DataStream API的Joining操作

目录 Window Join Tumbling Window Join Sliding Window Join Session Window Join Interval Join Window CoGroup Window Join 窗口连接&#xff08;window join&#xff09;将两个流的元素连接在一起&#xff0c;这两个流共享一个公共键&#xff0c;并且位于同一窗口。…

从老旧到智慧病房,全视通智慧病房方案减轻医护工作负担

传统的老旧病房面临着诸多挑战。例如&#xff0c;患者风险信息的管理仍依赖于手写记录和人工核查&#xff1b;病房呼叫系统仅支持基本的点对点呼叫&#xff0c;缺乏与其它系统的集成能力&#xff1b;信息传递主要依靠医护人员口头传达&#xff1b;护理信息管理分散且不连贯&…

JavaEE-多线程

前情提要&#xff1a;本文内容过多&#xff0c;建议搭配目录食用&#xff0c;想看哪里点哪里~ PC端目录 手机端目录 话不多说&#xff0c;我们正式开始~~ 目录 多线程的概念进程和线程的区别和联系:使用Java代码进行多线程编程Thread类中的方法和属性线程的核心操作1. 启动…

【mamba学习】(一)SSM原理与说明

mamba输入输出实现与transformer几乎完全一样的功能&#xff0c;但速度和内存占用具有很大优势。对比transformer&#xff0c;transformer存在记忆有限的情况&#xff0c;如果输入或者预测的序列过长可能导致爆炸&#xff08;非线性&#xff09;&#xff0c;而mamba不存在这种情…

物理网卡MAC修改器v3.0-直接修改网卡内部硬件MAC地址,重装系统不变!

直接在操作系统里就能修改网卡硬件mac地址&#xff0c;刷新网卡mac序列号硬件码机器码&#xff0c;电脑主板集成网卡&#xff0c;pcie网卡&#xff0c;usb有线网卡&#xff0c;usb无线网卡&#xff0c;英特尔网卡&#xff0c;瑞昱网卡全支持&#xff01; 一键修改mac&#xff0…

百问网全志系列开发板音频ALSA配置步骤详解

8 ALSA 8.1 音频相关概念 ​ 音频信号是一种连续变化的模拟信号&#xff0c;但计算机只能处理和记录二进制的数字信号&#xff0c;由自然音源得到的音频信号必须经过一定的变换&#xff0c;成为数字音频信号之后&#xff0c;才能送到计算机中作进一步的处理。 ​ 数字音频系…

MySQL本地Windows安装

下载MySQL 官网&#xff1a;MySQL 下载完成后文件 安装类型 选择功能 功能过滤筛选&#xff0c;系统为64位操作系统&#xff0c;所以选【64-bit】&#xff0c;32位可选【32.bit】 下方勾选后自动检查安装环境&#xff0c;如果提示缺少运行库&#xff0c;请先安装VC_redist.x64。…

【Dash】plotly.express 气泡图

一、More about Visualization The Dash Core Compnents module dash.dcc includes a componenet called Graph. Graph renders interactive data visualizations using the open source plotly.js javaScript graphing library.Plotly.js supports over 35 chart types and …

数据结构 之 二叉树功能的代码实现

文章目录 二叉搜索树搜索删除节点二叉树的遍历 代码实现 二叉搜索树 无序树的查找&#xff0c;就是整个遍历&#xff0c;所以时间复杂度为O(N)。为了优化无序查找的时间复杂度&#xff0c;我们把树进行排序&#xff0c;这里引入了二叉搜索树。 二叉搜索树是一个有序树&#xf…

vue el-select下拉框在弹框里面错位,

1&#xff1a;原因是因为 底层滚动条滚动的问题。 2&#xff1a;解决方案 加上这个属性 :popper-append-to-body"false" 和样式 <el-select:popper-append-to-body"false"> </el-select><style> .el-select .el-select-dropdown {t…

数据埋点系列 4|数据分析与可视化:从数据到洞察

在前面的文章中,我们讨论了数据埋点的基础知识、技术实现以及数据质量保证。现在,我们拥有了高质量的数据,是时候深入挖掘这些数据的价值了。本文将带你探索如何通过数据分析和可视化,将原始数据转化为有价值的业务洞察。 目录 1. 数据分析基础1.1 描述性统计1.2 推断统计1.3 相…

Haproxy的配置详解与使用

一、haproxy简介 HAProxy是一个使用C语言编写的自由及开放源代码软件&#xff0c;其提供高可用性、负载均衡&#xff0c;以及基于TCP和HTTP的应用程序代理。 HAProxy特别适用于那些负载特大的web站点&#xff0c;这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬…

uniapp实现自定义弹窗组件,支持富文本传入内容

1.首先安装vuex 通过此命令安装 ​​npm install vuex --save​​ 创建initModal.js import Vuex from vuex // 自定义弹窗 export default function initModal (v) {// 挂在store到全局Vue原型上v.prototype.$modalStore new Vuex.Store({state: {show: false,title: 标题,c…