vector底层实现及深层次拷贝问题

news2024/12/24 10:50:04

目录

大框架

接口实现

深层次拷贝问题(两次深拷贝)


大框架

为了与库里实现的更接近一些,先来看一下STL中是如何实现vector的(这里不是PJ版,PJ版稍微晦涩一点不容易理解,这里采用Linux下g++的版本)这里仅就大体框架做个简单介绍,其中复杂的概念不做过多解释。

 首先是vector定义的模板参数T和Alloc,这是空间配置器是一种底层实现机制,使用角度不用管。

可以看到源码做了很多typedef,主要是对格式的控制和方便理解编写。其中将T* typedef成了iterator,这个要注意,后面protected 成员变量会用到。

 这里成员变量就用到了刚刚typedef的iterator,实际上就是T* start ; T* finish;  T* end_of_storage;

T就是实例化模板参数。好像与我们设想的变量不太一样,我们设想的应该是有size和capacity的,但是这里全部统一使用指针,其实只是改变了使用方式,本质还是一样的。

 假设存在size(有效数据个数),capacity(容量),那么finish也可以表示成start+size,end_of_storage也可以表示成strat+capacity,本质是一样的。

后面是各个接口的实现,后面我们自己实现的时候具体说明,先把大框架搭建起来。

namespace bc
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;

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

为了避免和库里的冲突,将我们的实现代码封在命名空间内,库里的繁杂的typedef我们省略,只保留基本的T* 的重命名和成员变量表示。

接口实现

vector.h

#pragma once

#include<iostream>
#include<vector>
#include<assert.h>

using namespace std;

namespace bc
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{ }

		//拷贝构造写法一
		//vector(const vector<T>& v)
		//	: _start(nullptr)
		//	, _finish(nullptr)
		//	, _end_of_storage(nullptr)
		//{
		//	reserve(size());
		//	for (auto& e : v) {
		//		push_back(e);
		//	}
		//}


		//拷贝构造现代写法
		template<class InputIterator>
		vector(InputIterator first, InputIterator last) 
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last) {
				push_back(*first);
				first++;
			}
		}

		vector(vector<T>& v) 
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

		vector(size_t n, const T& val= T()) 
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i) {
				push_back(val);
			}
		}
		//函数重载
		vector(int n, const T& val= T())
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (int i = 0; i < n; ++i) {
				push_back(val);
			}
		}

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

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

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

		iterator begin() {
			return _start;
		}
		iterator end() {
			return _finish;
		}
		const iterator begin() const {
			return _start;
		}
		const iterator end() const {
			return _finish;
		}

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

		void clear() {
			_finish = _start;
		}

		void swap(vector<T>& v) {
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}
		T& operator[](size_t pos) {
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const {
			assert(pos < size());
			return _start[pos];
		}

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

		void reserve(size_t n) {
			if (n > capacity()) {
				size_t oldsize = size();
				T* tmp = new T[n];
				if (_start != nullptr) {
					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;
				_end_of_storage = _start + n;
			}
		}



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


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

		//迭代器失效
		iterator insert(iterator pos, const T& x) {
			assert(pos >= _start);
			assert(pos < _finish);
			if (_finish == _end_of_storage) {
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(newcapacity);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos) {
				*(end + 1) = *(end);
				--end;
			}
			*pos = x;
			_finish++;
			return pos;	
		}

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

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

下面是我给出的测试用例:

vector.h

	void Test1() {
		vector<int>a;
		a.push_back(1);
		a.push_back(1);
		a.push_back(1);
		a.push_back(1);
		a.push_back(1);
		cout << a.capacity() << endl;
		for (size_t i = 0; i < a.size(); ++i)
		{
			cout << a[i] << " ";
		}
		cout << endl;

		vector<int>::iterator it = a.begin();
		while (it != a.end()) {
			cout << (*it) << " ";
			it++;
		}
		cout << endl;

		//a.resize(2);
		for (auto e : a) {
			cout << e << " ";
		}
		cout << endl;

		cout << a.empty() << endl;

		a.pop_back();

		for (auto e : a) {
			cout << e << " ";
		}
		cout << endl;
	}

	void Test2() {
		vector<int> a;
		a.push_back(1);
		a.push_back(1);
		a.push_back(1);
		a.push_back(1);
		for (auto e : a) {
			cout << e << " ";
		}
		cout << endl;

		a.insert(a.begin(), 0);

		for (auto e : a) {
			cout << e << " ";
		}
		cout << endl;

		vector<int>::iterator it = find(a.begin(), a.end(), 1);
		if (it != a.end()) {
			a.insert(it, 2);
		}
		for (auto e : a) {
			cout << e << " ";
		}
		cout << endl;

		(*it)++;
		for (auto e : a) {
			cout << e << " ";
		}
		cout << endl;
	}

	void Test3() {
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(4);
		v.push_back(5);
		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 Test4() {
		vector<int> v;
		v.push_back(1);
		v.push_back(1);
		v.push_back(1);
		v.push_back(1);
		vector<int> a(v);
		for (auto e : a) {
			cout << e << " ";
		}
		cout << endl;

		vector<int> b;
		b = a;
		for (auto e : b) {
			cout << e << " ";
		}
		cout << endl;

		vector<int>c(10, 1);
		for (auto e : c) {
			cout << e << " ";
		}
		cout << endl;
	}

void Test5()
	{
		vector<vector<int>> vv;
		vector<int> v(4, 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;
	}	
}

test.cpp

#include"vector.h"

int main()
{
	bc::Test1();
    bc::Test2();
	bc::Test3();
	bc::Test4();
	bc::Test5();

	/*vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	std::vector<int>::iterator it = v.begin();
	while (it != v.end()) {
		if ((*it) % 2 == 0) {
			v.erase(it);
		}
		++it;
	}
	for (auto e : v) {
		cout << e << " ";
	}
	cout << endl;*/

	return 0;
}

以上是常用接口的大致实现,基本功能包含在内,还有些不太常用的接口没有实现。

深层次拷贝问题(两次深拷贝)

在测试vector<vector<int>>二维数组的时候,发现一个奇怪的现象:

当创建好vector<vector<int>> vv模型,并创建出内置数组vector<int> v (4,1)后,此时想要设定行数,也就是通过push_back插入行实现时,如果插入4个以内,没有问题,超过4个就会报错。

 

 

由此我们可以判定问题出在扩容上,具体我画图来解释。

 对于vector<vector<int>>模型要有一定理解,外部的大类型是vector<>,< >里面的类型是vector<int>,所以先从内部开始创建,vector<int> v(4,1) 构造一个{1,1,1,1}的一维数组,里面构建好了,在外面通过push_back插入,相当于插入一个一个这样的一维数组,这样就构建了二维数组,push_back多少次就相当于有多少行,里面4相当于列。

vector<int> v 数组里面的元素是int类型的,外面通过_start 指针指向v数组,指针是int* 类型的,一个指针指向一个v数组,外面还有一个_start指针指向整个vector<vector<int>>,指针类型是vector<int>*

建好二维数组后来看扩容问题,当调用reserve时,需要开辟新空间tmp,用memcpy将数据传给tmp 。                   

 而memcpy是按内存将数据拷贝给tmp的,这里的拷贝是浅拷贝,这就会导致一个问题,tmp最后也会指向_start 空间,

然后memcpy结束就是delete[]_start,delete会调用析构函数将_start空间释放,这下tmp指向的空间get不到了,tmp就是野指针了。

所以要想解决这个问题,就要解决内层浅拷贝的问题,将其改为深拷贝,这样外层tmp对象深拷贝,里层tmp数据也进行深拷贝,就不会出问题了。

解决方案:1、不用memcpy,利用operator = 重载函数将数据一个一个拷贝给tmp

		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;
			}
		}

tmp[i] = _start[i] 将_start[i] 一个一个拷贝给tmp[i],这里调用operator= 是深拷贝。

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

但是这种方法缺点是效率太低。在C++11 中会有更好的方式解决上述问题。

以上就是深层拷贝的内容,关于底层实现还有其他不同版本,但因为复杂度等原因我们是按Linux g++使用的库实现的,感谢浏览!

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

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

相关文章

VectorNet: Encoding HD Maps and Agent Dynamics from Vectorized Representation

Paper name VectorNet: Encoding HD Maps and Agent Dynamics from Vectorized Representation Paper Reading Note URL: https://arxiv.org/pdf/2005.04259.pdf TL;DR waymo 出品的 CVPR2020 论文 &#xff0c;关注在自动驾驶场景&#xff08;复杂多智能体系统&#xff0…

【算法自由之路】快慢指针在链表中的妙用(下篇)

【算法自由之路】快慢指针在链表中的妙用&#xff08;下篇&#xff09; 继上篇之后&#xff0c;链表这块还有两个相对较难的问题我们继续举例。 问题 1 给定具有 random 指针的 next 方向无环单链表&#xff0c;复制该链表 单听这个问题可能有点懵&#xff0c;这个链表结构我…

PCB封装创建(CHIP类)

PCB封装要有以下内容 PCB焊盘管脚序号丝印阻焊1脚标识目录 CHIP类&#xff08;电阻 电容 电感 三极管&#xff09; 0805C 0805R 0805L SOT-23 1.CHIP类&#xff08;电阻 电容 电感 三极管&#xff09; 1.新建一个PCB元件库 打开PCB Library 以下以0805为例。 创建080…

“CAcModuleResourceOverride”: 未声明的标识符

本文迁移自本人网易博客&#xff0c;写于2011年10月8日首先是运行时提示&#xff1a;试图执行系统不支持的操作。添加CAcModuleResourceOverride resourceOverride; 后&#xff0c;编译出现如下错误&#xff1a;error C2065: “CAcModuleResourceOverride”: 未声明的标识符 添…

scikit-learn 普通最小二乘法

scikit-learn 普通最小二乘法什么是普通最小二乘法&#xff1f;参考文献什么是普通最小二乘法&#xff1f; 线性回归模型的数学表达式如下&#xff1a; y^(w,x)w0w1x1…wpx1\hat{y}(w, x)w_{0}w_{1} x_{1}\ldotsw_{p} x_{1}y^​(w,x)w0​w1​x1​…wp​x1​ 其中 w0,w1,...,w…

Java--集合

1、集合框架 集合框架被设计成要满足以下几个目标。 该框架必须是高性能的。基本集合&#xff08;动态数组&#xff0c;链表&#xff0c;树&#xff0c;哈希表&#xff09;的实现也必须是高效的。 该框架允许不同类型的集合&#xff0c;以类似的方式工作&#xff0c;具有高度的…

【自用】高频电子线路复习(更新中)

疫情原因 没有考试就放假回家了 返校后将先进行死亡考试周 七天考完九门 回校再进行极限复习只能说可以通过 而不利于绩点的提升 所以要从现在开始抽取一些时间进行学习 第七章 频率变换方法与电路分析 7.1 非线性电路包括 发送端的高频振荡器、倍频器、谐振功率放大器和调…

【ROS自定义文件】自定义头文件及源文件的调用

本文记录ROS中的自定义文件的调用&#xff0c;主要包括自定义头文件和源文件的使用。 1 自定义C头文件的调用 注意这个文件目录的结构&#xff0c;尤其是 hello.h 这个自定义的头文件在 include/plumbing_head 文件夹之下&#xff0c;这个会直接影响后续头文件的引用。 hello.…

尚医通-整合网关-Nuxt搭建前端环境(二十六)

目录&#xff1a; &#xff08;1&#xff09;整合服务网关 &#xff08;2&#xff09;前台用户系统-nuxt搭建前端环境 &#xff08;3&#xff09;前台用户系统-目录结构和封装axios &#xff08;1&#xff09;整合服务网关 前面的过程使用nginx请求转发 下面使用SpringClo…

ScheduledThreadPoolExecutor定时任务执行线程池分析

概述 ScheduledThreadPoolExecutor自然是继承了ThreadPoolExecutor&#xff0c;那么它也就是一个被定义了特定功能的线程池而已&#xff0c;本质上就是一个ThreadPoolExecutor。 代码分析 可以看到其继承了ThreadPoolExecutor&#xff0c;在new ScheduledThreadPoolExecutor…

【FPGA】Verilog 编码实现:与非门 | 或非门 | 异或门 | NAND/NOR/XOR 行为验证

写在前面&#xff1a;本章主要内容为了解和确认 NAND/NOR/XOR 门的行为&#xff0c;并使用Verilog实现&#xff0c;生成输入信号后通过模拟&#xff0c;验证每个门的操作&#xff0c;并使用 FPGA 来验证 Verilog 实现的电路的行为。 本章目录&#xff1a; Ⅰ. 前置知识 0x00…

C++ 排序大合集

目录 一、了解排序 1、内部 2、外部 二、排序的稳定性 三、插入排序 1、算法和操作 2、代码 四、选择排序 1、算法和操作 2、代码 五、冒泡排序 1、算法和操作 2、代码 六、堆排序 1、优先队列 2、排序代码 七、归并排序 1、定义 2、基本算法 &#xff08;1&#xff09;、分离 …

宝塔Linux面板安装MySQL数据库,并且开启远程链接

1.宝塔面板【软件商店】->【应用搜索】&#xff0c;搜索MySQL,然后点击安装想要的版本&#xff0c;我这边是安装的5.6版 2. 安装完后重置数据库管理员密码 3.Navicat Premium 15连接数据库 4.外网navicat工具无法连接数据库的处理办法 4.1输入 mysql -u root -p 后回车&a…

零基础入门反序列化漏洞

目录 前提知识 漏洞产生原理 常见的函数 序列化 反序列化 __sleep函数 私有和保护 __wakeup函数 反序列化漏洞举例 构造XSS漏洞 反序列化免杀后门 POP CHAIN(POP链) 前提知识 漏洞产生原理 serialize() 和 unserialize() 在 PHP内部实现上是没有漏洞的&#xf…

Cadence PCB仿真使用Allegro PCB SI配置电路板层叠结构的方法图文教程

⏪《上一篇》   🏡《总目录》   ⏩《下一篇》 目录 1,概述2,配置方法3,总结1,概述 本文详细介绍使用Allegro PCB SI软件配置电路板层叠结构的方法。 2,配置方法 第1步:打开待仿真的PCB文件,并确认软件为Allegro PCB SI 如果,打开软件不是Allegro PCB SI则可这样…

解决No module named tkinter

原因 今天准备使用tutle画个图&#xff0c;导入turtle后运行发现提示没有tkinter这个包&#xff0c;于是尝试pip install tkinter安装&#xff0c;结果当然是失败&#xff1a; 后面一番搜索之后发现tinter是python3自带的包&#xff0c;不能用pip安装&#xff0c;我这里安装的…

JS的六种继承方式

继承 什么是继承&#xff1f; JS里的继承就是子类继承父类的属性和方法 目的可以让子类的实例能够使用父类的属性和方法 抽象的表达就是&#xff1a;一个人有车&#xff0c;有房&#xff0c;那么他的儿子也可以去使用他的车子&#xff0c;住他的房子。 方法一&#xff1a;…

Seata流程源码梳理上篇-TM、RM处理

这一篇我们主要来分析下Seata的AT模式的流程处理。一、流程案例 1、案例源码 ​ 我们本地流程梳理用的是基于spring-cloud框架&#xff0c;注册中心是eurak&#xff0c;服务间调用的是feign&#xff0c;源码下载的是官网的&#xff08;当然你如果对dubbo更熟悉&#xff0c;也…

CSDN博客之星年度评选活动 - 2022

文章目录一、2022年CSDN博客之星评选活动报名二、2022年CSDN博客之星评选活动流程线上评分流程争议&#xff08;官方最后证实公布后会更新&#xff09;三、2022年CSDN博客之星评选规则四、2022年CSDN博客之星评分规则五、2022年CSDN博客之星活动奖品「博客之星」奖品「博客新星…

CInternetSession OpenURL没反应,不能捕获异常

本文迁移自本人网易博客&#xff0c;写于2013年10月22日CString sFileName;CInternetSession iSession;BOOL bRet FALSE;CStdioFile* pFileDown NULL;try{pFileDown iSession.OpenURL(szURL, 1, INTERNET_FLAG_TRANSFER_BINARY|INTERNET_FLAG_DONT_CACHE);}catch(...){CStri…