vector模拟实现下篇及迭代器失效和深浅拷贝问题详解

news/2025/2/12 4:12:10/

文章目录

  • 1:构造函数
    • 1.1默认构造函数
    • 1.2迭代器构造
    • 1.3用n个val构造
    • 1.4拷贝构造
  • 2:operator=
  • 3:析构函数和clear
  • 4:迭代器失效问题
    • 4.1:删除偶数
  • 深浅拷贝

1:构造函数

1.1默认构造函数

		vector():_start(nullptr),_end(nullptr),_endofstorage(nullptr){}

1.2迭代器构造

		template<class InputIterator>vector(InputIterator first, InputIterator last): _start(nullptr), _end(nullptr), _endofstorage(nullptr){while (first != last){pushback(*first);++first;}}

在类模板中使用函数模板。

1.3用n个val构造

		vector(size_t n, const T& val = T()): _start(nullptr), _end(nullptr), _endofstorage(nullptr){reserve(n);for (size_t i = 0; i < n; i++){pushback(val);}}

我们测试一下

	void test1(){vector<char> v(10, 1);for (size_t i = 0; i < v.size(); i++){cout << v[i];}}

image-20221215142047255

这个报错产生在 image-20221215142105794

分析:

用10个1构造vector,1是整形,那么T就会推演为int类型,10也是整形,但是这个n个val构造的n是size_t类型,int转为size_t需要发生隐式类型转换,而1.2里面我们写的迭代器,inputiterator只是个名字,int可以被这个名字表达,所以编译器不会去找要转换的而是直接调用1.2的构造函数,但是int如果作为迭代器,*it访问的时候就会出错,也就是非法的间接寻址。因此为了解决这个问题,我们需要再重载一个构造函数

		vector(int n, const T& val = T()): _start(nullptr), _end(nullptr), _endofstorage(nullptr){reserve(n);for (int i = 0; i < n; i++){pushback(val);}}

1.4拷贝构造

		void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_end, v._end);std::swap(_endofstorage, v._endofstorage);} 		vector(const vector<T>& v): _start(nullptr), _end(nullptr), _endofstorage(nullptr){vector<T> temp(v.begin(), v._end());swap(v);}

和string一样,用库里面的swap进行深拷贝交换。

2:operator=

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

和string一样,传值的形参不改变实参,形参出了作用域自动销毁对象。而且这里的swap传形参的引用,形参拿到原本的vector地址,当他出了函数作用域的时候顺便帮vector也析构。

3:析构函数和clear

		~vector(){delete[] _start;_start = _end = _endofstorage = nullptr;}void clear(){_end = _start;}

clear是清空数据

4:迭代器失效问题

上篇我们设计了insert和erase成员函数。

		void insert(iterator pos, const T& val){assert(pos < _end);assert(pos >= _start);if (_end == _endofstorage){size_t len = pos - _start;int newcapacity = capacity() > 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator _finish = _end - 1;while (_finish >= pos){*(_finish + 1) = *_finish;--_finish;}*pos = val;++_end;}void erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator begin = pos+1;while (begin < _finish){*(begin-1) = *(begin);++begin;}--_finish;return pos;}

使用len是解决了内部迭代失效的原因。

	void test2(){std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);std::vector<int>::iterator it = find(v.begin(), v.end(), 3);if (it != v.end()){v.erase(it);}cout << *it << endl;for (auto e : v){cout << e << ' ';}}

当删除了it原本的位置后,

image-20221215145610834

直接崩溃,不打印。

而看看g++

image-20221215145640200

vs报错,g++不报错。

迭代器失效,因此在g++源码里面,是这样实现的。

将erase和insert的返回值设定为iterator。这样可以更新位置后再继续使用。

it = v.erase(it);

4.1:删除偶数

image-20221215151355939

		iterator erase(iterator pos){assert(pos >= _start);assert(pos < _end);iterator begin = pos+1;while (begin < _end){*(begin-1) = *(begin);++begin;}--_end;return pos;}

如果是1234,如果是最后一个位置是偶数要删除,那么begin就会从pos+1的位置删除也就是和end一样的位置,那么我们的erase都不会进去,直接end–了,然后此时it还要++,也就是2个迭代器错过了,这样就会一直判断下去导致不断越界。问题就是最后一个数是偶数。

如果是122345, image-20221215152045383

当it是第一个2的时候,会erase,挪动数据,将下一个2移动到前面一个2的位置,然后还要++it,这样的话就会导致第二个2没有判断到,所以结果不符合。

如果单纯的解决办法是如下

else
{
++it;
}

这样子可以解决末尾有偶数的问题(段错误)。

但是我们参考的是sgi,sgi是linux的,采用的迭代器是原生指针,而vs是windows下的。 迭代器不是原生指针。所以erase和insert等涉及到迭代器失效的需要用迭代器作为返回值。

	//void test_vector6()//{//	// 要求删除所有偶数//	std::vector<int> v;//	v.push_back(1);//	v.push_back(2);//	v.push_back(2);//	v.push_back(3);//	v.push_back(4);//	std::vector<int>::iterator it = v.begin();//	while (it != v.end())//	{//		if (*it % 2 == 0)//		{//			it = v.erase(it);//		}//		else//		{//			++it;//		}//	}//	for (auto e : v)//	{//		cout << e << " ";//	}//	cout << endl;//}

深浅拷贝

	void test_vector9(){/*	vector<vector<int>> vvRet = Solution().generate(5);for (size_t i = 0; i < vvRet.size(); ++i){for (size_t j = 0; j < vvRet[i].size(); ++j){cout << vvRet[i][j] << " ";}cout << endl;}cout << endl;*/vector<vector<int>> vv;vector<int> v(5, 1);vv.push_back(v);vv.push_back(v);vv.push_back(v);vv.push_back(v);vv.push_back(v);for (size_t i = 0; i < vv.size(); ++i){for (size_t j = 0; j < vv[i].size(); ++j){cout << vv[i][j] << " ";}cout << endl;}cout << endl;}
}

看下面的代码,创建包含5个1的vector,再往一个vector里面插入这种vector,然后打印这个大的vector。

image-20221215154542562

运行结果报错,还出现了随机数。这是为什么?

在前面章节我提到过,当按字节序拷贝的时候会发生浅拷贝,按字节序拷贝的函数有memcpy,而memcpy是在reserve过程的,这里面当我们在总的vector里面插入小的vector的时候一定会发生扩容,当发生扩容的时候就会reserve,将旧数据拷贝到新空间。

		void reserve(size_t n){if (n > capacity()){size_t oldsize = size();//需要扩容T* temp = new T[n];//使用new可以调用默认构造,比malloc要舒服的多。//要注意如果start为空的时候,拷贝不了任何东西所以先判断startif (_start){memcpy(temp, _start, sizeof(T) * oldsize);delete[] _start;}_start = temp;_end = _start + oldsize;_endofstorage = _start + n;}}

image-20221215154852024

image-20221215154914444

当开辟好新空间,调用memcpy的时候,是发生的字节序拷贝,将上面4个单元格内容拷贝到下面的几个单元格里面,并且用的是字节序拷贝,所以下面的单元格里面的start都和上面的4个小单元格里面的start指向内容一样,是同一块空间,当调用delete的时候,就会析构掉上面的整体,会调用整体(自定义类型vector int)的析构函数,析构掉每个单元,这时每个小单元格指向的内容都变成了随机的,temp里的单元格和上面的单元格指向内容是一样的,所以temp赋值给下面的总体的start迭代器的时候,相当于把随机值给他,所以导致了这个结果。

本质上就是因为reserve的memcpy导致了浅拷贝,内容先被释放,成为随机空间,又把随机空间赋值给新空间的start导致出错。

解决办法

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

使用赋值,赋值的话是深拷贝。不会引起上面问题。


http://www.ppmy.cn/news/3714.html

相关文章

如何将数学曲线变为机器人轨迹-花式show爱心代码-turtlesim篇

第一步&#xff1a;找到曲线数学描述的网址。 阅读后了解曲线所对应的xy函数。 不要选太复杂的&#xff0c;毕竟先复现出来最重要的。 第二步&#xff0c;这个函数转为C代码。 //Lovegoal_x5.54.0*pow(sin(curve_t/200.0),3);goal_y5.5((13.0*cos(curve_t/200.0)-5.0*cos(curv…

Qt5 中的 Json 模块与 JsonCpp 的对比

注&#xff1a;大家常说的 QJson 其实并不是 Qt 中的模块&#xff0c;而是在 Qt4 没有 Json 模块的年代&#xff0c;一个非官方的第三方模块。对于现在 Qt 中的 Json 模块&#xff0c;官方称之为 Qt Json。 其实 Qt5 中的 Qt Json 模块的代码&#xff0c;写的可以说是严格按照…

springMVC+mysql实现的Java web图书管理系统源码+运行教程+参考论文

今天给大家演示的是一款由srpingMVC实现的Java web图书管理系统&#xff0c;本项目功能非常丰富&#xff0c;且附带配套论文及视频指导配置运行教程&#xff0c;系统实现的功能主要有&#xff1a;图书馆里、图书分类管理、出版社管理、图表图书统计展示、用户管理、角色管理、权…

win7无损合并分区,win7合并磁盘分区

电脑的操作系统是win7的&#xff0c;如果磁盘分区太小或者说磁盘分区不合理&#xff0c;需要对磁盘分区重新分区&#xff0c;其中合并磁盘分区就是解决方法之一&#xff0c;那么&#xff0c;有没有关于win7无损合并分区的操作方法呢&#xff1f; 一、利用Windows自带的功能来合…

SSM整合

SSM整合 1、准备工作 ①创建Maven Module ②导入依赖 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.c…

记录-链表头插尾插区别

链表作为数据结构中比较重要的一种&#xff0c;具有操作效率高、内存利用率高、结构简单、使用方便等特点&#xff0c;今天我们一起交流一下单向线性表的头插法和尾插法的区别及优缺点 线性表因为每个元素都包含一个指向下一元素的指针&#xff0c;所以新增、删除、修改起来非…

Jenkins Pipeline Kubernetes 如何创建 Pod

Jenkins Pipeline & Kubernetes 如何创建 pod 文章目录Jenkins Pipeline & Kubernetes 如何创建 pod1. 前言2. Jenkins 插件2.1 安装 Kubernets Plugin2.2 安装 Docker Plugin2.3 安装 Git Plugin3. Jenkins 配置 kubernetes credentials4. Jenkins 连接 minikube 集群…

android 和风图标字体移植显示墨迹天气图标

android studio版本&#xff1a;21.2.1 例程&#xff1a;newareaautov1 和风天气字体图标使用方法见&#xff1a; android 显示和风天气字体图标_kim5659的博客-CSDN博客_qweather-icons 之前做了个全自动获取天气的app,用的是墨迹的接口&#xff08;实际是科大讯飞再接入墨…