vector模拟实现之迭代器失效及深浅拷贝的问题

news2025/5/22 13:13:36

vector模拟实现

Tips:new申请空间不用判断,因为失败的话会抛异常。

STL源代码中vector的私有成员变量如下:

private:
		iterator _start;//首元素
		iterator _finish;//最后一个有效数据的下一个,-_start为size
		iterator _endofstorage;//-_start为容量

迭代器失效

情况1 会引起其底层空间改变的操作

会引起其底层空间改变的操作都有可能使迭代器失效,比如:resize、reserve、insert、assign、push_back等。分析:其实就是野指针问题。

例1 insert模拟实现

insert不一定会导致迭代器失效,insert里扩容就一定会导致迭代器失效,看如下代码

void insert(iterator pos, const T& val)
{
	if (size() == capacity())
	{
		size_t newcapacity = (capacity() == 0) ? 4 : 2 * capacity();
		reserve(newcapacity);
	}

	//挪动数据
	_end = _finish - 1;
	while (_end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	++_finish;
}
//调用
v.insert(v.begin(), 10);

由于一开始传参的时候,传的是v.begin(),扩容后可能是异地扩容,那么v的实际begin()位置就变了,在挪动数据的时候就会出错。_end >= pos此时pos的指向是错误的,这个循环挪动数据就会出错。

所以在扩容时要注意同步更新pos

//更正代码如下
if (size() == capacity())
	{
		size_t len = pos - _start;//记录pos实际长度
		size_t newcapacity = (capacity() == 0) ? 4 : 2 * capacity();
		reserve(newcapacity);

		pos = _start + len;//更新pos
	}

同理引起底层空间改变的操作,也不再适合使用pos

vector<int>::iterator it = find(v.begin(), v.end(), 10);
if(it != v.end())
{
	v.insert(it, 20);
}
(*it)++;//it失效,不能再使用了,v有可能进行扩容了

出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。
解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。

情况2 删除指定位置元素 erase

例1 删除末尾数据

it = std::find(v.begin(), v.end(), 3);
if (it != v.end())
{
	v.erase(it);
}
//检测it是否失效--VS2019不失效,VS2013失效
//读
std::cout << *it << std::endl;
//写
std::cout << (*it)++ << std::endl;

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

例2 删除偶数

it = v.begin();
while (it != v.end())
{
	if (*it % 2 == 0)
		it = v.erase(it);//更新it,认为it失效
    else
		++it;
}
//测试用例---->结果
//1 2 3 4     --> segmentation fault
//1 2 3 4 5   --> 1 3 5
//1 2 2 3 4 5 --> 1 2 3 5

接下来分析出现这三种情况的原因:
erase导致迭代器失效 图解

深拷贝

浅拷贝reserve中的memcpy问题

以动态二维数组举例–杨辉三角

void reserve(size_t n)
{
	//reserve只扩不缩
	if (n > capacity())//扩容
	{
		size_t old_size = size();//保存size
		T* tmp = new T[n];

		if (_start != 0)//如果对象本来为空就不用拷贝了
		{
			memcpy(tmp, _start, old_size * sizeof(T));
			delete[] _start;//如果_start==0,这里就会出错
		}

		_start = tmp;
		_finish = _start + old_size;
		_endofstorage = _start + n;
	}
}

当使用杨辉三角的代码运行我们模拟实现的vector时,调用reserve还是会出问题。

因为对象是vector<vector<int>>,T可以是int,也可以是vector<int>。当T为int时没什么问题,问题出在当T是vector<int>的时候,会因为memcpy的浅拷贝而报错,因为此时_start相当于int**,里面保存的是地址!那使用memcpy就相当于浅拷贝了,下一句代码delete[] _start;释放原空间,相当于此时tmp拿到的内容(指针指向的空间)失效了。【当T为int时,_start相当于int*,里面保存的是数值】

解决方案:

if (_start != 0)
{
	//memcpy(tmp, _start, old_size * sizeof(T));
	//要用下面这个写法
	for(size_t i = 0; i < n; i++)
	{
		tmp[i] = _start[i];
	}
	delete[] _start;//如果_start==0,这里就会出错
}

拷贝构造函数

该函数只要求已有的数据一模一样即可,没有要求capacity也要一样。

//传统写法1
vector(vector<T>& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{
	reserve(v.capacity());
	for(const auto& e : v)
	{
		push_back(e);
	}
}
//传统写法2
vector(vector<T>& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{
	size_t len = v.size();
	_start = new T[v.size()];
	_endofstorage = _finish = _start + len;	
}
//现代写法
vector(vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	vector<int> tmp(v.begin(), v.end());
	swap(tmp);
}

现代写法需要下方这个成员函数来当工具人。

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

成员函数再套模板问题

//1
vector (size_t n, const T& val = T());
-------------------
//2
template <class InputIterator>
vector(InputIterator first, InputIterator last)
-------------------
vector<int> v(10, 1);//这个写法本意是调用1,但是编译器会调用2,就会报错--编译错误
vector<int> v(10, 'A');

原因:因为10和1都是字面量,10和1都会被识别为int类型,写法1需要隐式类型转换,故优先匹配写法2模板,在vector下first会被解引用,实际上int类型的值无法再解引用,因此报错;

而A被识别为char,虽然10得隐式转换,但是int和char无法匹配写法2,只能匹配写法1。

解决方案:

为了匹配写多个版本(size_t、int、long)vector (int n, const T& val = T()); vector (size_t n, const T& val = T());

题目

电话号码的字母组合

其实是回溯问题。要全排列。得用vector套string

//这个写法更简洁
vector<string> _numStr[10] = { "", "", 
                                   "abc", "def", "ghi", "jkl", 
                                   "mno", "pqrs", "tuv", "wxyz" };
--------------------最土的方法-------------------
string s0("");string s1("");string s2("abc");
string s3("def");string s4("ghi");string s5("jkl");
string s6("mno");string s7("pqrs");string s8("tuv");
string s9("wxyz");

vector<string> vecStr;
vecStr.reserve(10);
vecStr.push_back(s0);vecStr.push_back(s1);vecStr.push_back(s2);
vecStr.push_back(s3);vecStr.push_back(s4);vecStr.push_back(s5);
vecStr.push_back(s6);vecStr.push_back(s7);vecStr.push_back(s8);
vecStr.push_back(s9);

初始化完成后,建议用递归来完成全排列。代码如下

class Solution {
    //把0 1 都放进来,以便下标直接访问
    string _numStr[10] = {  "", "", "abc", "def", "ghi", "jkl","mno", "pqrs", "tuv", "wxyz" };

public:
    void Combine(const string& digits, size_t i, vector<string>& vecStr, string comStr)
    {
        //递归的层数就是digits的字符个数
        //如果i等于字符个数了,即遇到'\0',结束递归
        if(i == digits.size())
        {
            vecStr.push_back(comStr);
            return;
        }

        int num = digits[i] - '0';//取出对应的数字
        //[i] -> "数字" -> -'0' -> (int)数字
        string str = _numStr[num];//根据数字对应的字符串

        //str有几个字符就要走几次递归
        for(auto ch : str)
        {
            Combine(digits, i+1, vecStr, comStr+ch);
        }
    }
    vector<string> letterCombinations(string digits) {
        vector<string> vecStr;
        //如果为空,直接返回
        if(digits.empty())
            return vecStr;

        string comStr;//保存每次组合出来的字符串
        size_t i = 0;//表示递归层数
        Combine(digits, i, vecStr, comStr);

        return vecStr;
    }
};

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

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

相关文章

6-3分布散度的9个梯度

( A, B )---1*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有1个节点&#xff0c;AB各由9张二值化的图片组成&#xff0c;排列组合A和B的所有可能性&#xff0c;固定收敛误差为7e-4&#xff0c;统计收敛迭代次数&#xff0c;并比较迭代次数的变化规律。 差值结构 A-B 迭代次数 …

Huawei Matebook X Pro 2018 Space Gray电脑 Hackintosh 黑苹果efi引导文件

硬件型号驱动情况主板Huawei Matebook X Pro 2018 Space Gray处理器Intel Core i7-8550U已驱动内存16 GB LPDDR4 2133 MHz已驱动硬盘LiteON SSD PCIe NVMe 512 GB [CA3-8D512]已驱动显卡NVIDIA GeForce MX150 (Disabled) / Intel(R) UHD Graphics 620已驱动声卡瑞昱ALC256 英特…

微积分——导数和切线问题

目录 1. 切线(Tangent Line)问题 2. 函数的导数(derivative) 3. 函数的可微性(differentiability)与连续性(Continuity) 1. 切线(Tangent Line)问题 微积分的出现源于17世纪欧洲数学家们正在研究解决的四个主要的问题&#xff1a; (1) 切线(tangent line)问题&#xf…

使用Alexnet实现CIFAR10数据集的训练

如果对你有用的话&#xff0c;希望能够点赞支持一下&#xff0c;这样我就能有更多的动力更新更多的学习笔记了。&#x1f604;&#x1f604; 使用Alexnet进行CIFAR-10数据集进行测试&#xff0c;这里使用的是将CIFAR-10数据集的分辨率扩大到224X224&#xff0c;因为在测试…

第03讲:Docker 容器的数据卷

一、什么是数据卷 数据卷是宿主机中的一个目录或文件&#xff0c;当容器目录或者文件和数据卷目录或者文件绑定后&#xff0c;对方的修改会立即同步&#xff0c;一个数据卷可以被多个容器同时挂载&#xff0c;一个容器也可以被挂载多个数据卷&#xff0c;数据卷的作用:容器数据…

基于遥感卫星影像水体提取方法综述

水体提取分类依据及基础 水体提取分类依据 水体提取的方法很多,很多学者也进行了分类,大体上有一个分类框架,主要是基于光学影像的分类,比如王航等[7]将水体提取分成3类,分别是基于阈值法、分类器法和自动化法; 李丹等[8]更深一步进行总结,引入近些年发展火热的基于雷达影像数…

Redisson自定义序列化

配置RedissonClientBean public RedissonClient redissonClient() {Config config new Config();// 单节点模式SingleServerConfig singleServerConfig config.useSingleServer();singleServerConfig.setAddress("redis://127.0.0.1:6379");singleServerConfig.set…

LeetCode二叉树经典题目(六):二叉搜索树

目录 28. LeetCode617. 合并二叉树 29. LeetCode700. 二叉搜索树中的搜索 30. LeetCode98. 验证二叉搜索树 31. LeetCode530. 二叉搜索树的最小绝对差 32. LeetCode501. 二叉搜索树中的众数 33. LeetCode236. 二叉树的最近公共祖先​ 28. LeetCode617. 合并二叉树 递归&…

Hi3861鸿蒙物联网项目实战:智能安防报警

华清远见FS-Hi3861开发套件&#xff0c;支持HarmonyOS 3.0系统。开发板主控Hi3861芯片内置WiFi功能&#xff0c;开发板板载资源丰富&#xff0c;包括传感器、执行器、NFC、显示屏等&#xff0c;同时还配套丰富的拓展模块。开发板配套丰富的学习资料&#xff0c;包括全套开发教程…

Windows11 系统打开IE浏览器的方式(完整版)

前言 大家好&#xff0c;好久不见&#xff01; 1、最近疯狂加班&#xff0c;旧电脑不太给力&#xff0c;换了新电脑&#xff0c;嘎嘎开心&#xff1b;开心之余发现新电脑是Win11系统的&#xff0c;但是IE浏览器找不到了&#xff0c;由于我的某些工作需要用到IE浏览器&#xf…

Vue2前端路由(vue-router的使用)、动态路由、路由和视图的命名以及声明式和编程式导航

目录 一、vue2的前端路由&#xff08;vue-router&#xff09; 1、路由&#xff1a;页面地址与组件之间的对应关系 2、路由方式&#xff1a;服务器端路由、前端路由 3、前端路由&#xff1a;在前端维护一组路由规则&#xff08;地址和组件之间的对应关系&#xff09;&#xf…

【UE4 第一人称射击游戏】34-制作一个简易计时器

上一篇&#xff1a;【UE4 第一人称射击游戏】33-创建一个迷你地图本篇效果&#xff1a;可以看到左上角有个简易的关卡计时器在倒计时步骤&#xff1a;打开“FPSHUD”&#xff0c;拖入一个图像控件图像选择“Timer_Backing”&#xff0c;尺寸改为4719拖入3个文本控件大小为1210字…

学习ffmpeg-录屏实现记录

项目需要一个录屏的功能&#xff0c;之前看到了一个使用Qt计时器截图avilib生成AVIffmpeg合并视频音频的方式&#xff1a;Qt C 录屏录音功能实现&#xff08;avilibffmpeg&#xff09;以及动态库生成https://blog.csdn.net/qq_35769071/article/details/125323624使用后&#x…

【.dll 没有被指定在windows上运行】

修复&#xff08;重新注册DLL&#xff09;的具体步骤如下&#xff1a; 方法一&#xff1a; 1、快捷键winr打开“运行”输入cmd,点击确定打开命令提示符窗口。 2、复制&#xff1a;for %1 in (%windir%\system32*.dll) do regsvr32.exe /s %1 命令&#xff0c;在打开的管理员…

ubuntu安装vue

首先建议使用ubuntu18.04以上的系统&#xff0c;不然会有类似fcntlGLIBC_2.28‘未定义的引用的报错 VUE官网&#xff1a;http://caibaojian.com/vue/guide/installation.html 其中安装说明只写到&#xff1a;npm install vue 我们还需要安装node.js、npm 1、安装 NVM&#xf…

Windows安装TensorRT

文章目录前言TensorRT下载TensorRT安装参考资料前言 本文将介绍Windows如何安装TensorRT。本文的基础是&#xff1a;Windows安装PytorchCUDA环境 TensorRT下载 进入官方网站&#xff1a;https://developer.nvidia.com/nvidia-tensorrt-8x-download 寻找自己对应的版本&#…

RabbitMQ之Work Queue(工作队列)

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;曾经在某央企公司实习&#xff0c;目前在某税务公司。本篇文章将记录和分享RabbitMQ工作队列相关的知识点。 本篇文章记录的基础知识&#xff0c;适合在学Java的小白&#xff0c;也适合复习中&am…

【自学Python】Python string转bytes

Python string转bytes Python string转bytes教程 在 Python 中&#xff0c;bytes 类型和 字符串 的所有操作、使用和内置方法也都基本一致。因此&#xff0c;我们也可以实现将字符串类型转换成 bytes 类型。 Python string转bytes方法 如果字符串内容都是 ASCII 字符&#…

从0到1完成一个Vue后台管理项目(十六、后端分页方法以及分页组件的封装以及复用)

往期 从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;二、使用element-ui&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;三、使用SCSS/LESS&#xff0c;安装图标库&#xff09; 从0到1完成一个Vu…

C++STL——list类与模拟实现

Listlistlist的常用接口模拟实现完整代码list与vector的区别list list是一个带头双向循环链表。 list文档介绍&#xff1a;https://legacy.cplusplus.com/reference/list/list/ list因为是链表结构&#xff0c;所以没有 [] 去访问数据的方式&#xff0c;只有用迭代器&#xff…