STL_vector简化模拟—详解深层次深拷贝问题

news2024/11/19 8:39:54

在这里插入图片描述

文章目录

    • 迭代器框架和成员变量
    • 基础成员函数
    • 容量相关的成员函数
    • 关于深拷贝中的深拷贝问题
    • operator[ ]重载和内容修改函数
    • 类模板内的嵌套类型
    • 全部代码

根据原码看出vector的成员并不像string类的一个指针加一个size和一个capacity。
而是三个指针,_start , _finish , _endofstorage.
image.png
size和capacity也是计算得到,_finish - _start就是size,_endofstorage - _start就是capacity。

迭代器框架和成员变量

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

		iterator begin()
		{
			return _start;
		}
		const_iterator begin() const
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator end()const
		{
			return _finish;
		}
    private:
		iterator _start;//指向内存块的开始位置
		iterator _finish;//指向最后一个元素的下一个位置
		iterator _end_of_storage;//指向最后一个有效空间的下一个位置
	};
}

为了实现存储任意类型的元素,这里使用了模板参数。

基础成员函数

//构造and析构
		vector()
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
		{}

		//拷贝构造现代写法
		vector(const vector<T>& vt)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			vector<T> tmp(vt.begin(), vt.end());
			swap(tmp);
		}
			
		void file_initialize(size_t n, const T& val = T())
		{
			_start = new T[n];
			for (size_t i = 0; i < n; i++)
			{
				_start[i] = val;
			}
			_end_of_storage = _finish = _start + n;
		}
		vector(size_t n, const T& val = T())
		{
			file_initialize(n, val);
		}
		vector(int n, const T& val = T())
		{
			file_initialize(n, val);
		}

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

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

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

构造函数使用迭代器区间来构造一个对象,这个迭代器可以是任意类型的迭代器,所以定义成为一个模板参数,在类模板里面可以嵌套定义模板供成员函数使用

赋值运算符重载使用的是现代写法,现代写法更加的简单,但是存在一个问题就是自己给自己赋值,因为参数并不是引用,所以传参的时候就已经发生了深拷贝,后面交换与否都不会有效率的提升。
现代写法的思路是:传参过来的这个值是要赋值给this的,参数不用引用,传参的时候就发生了拷贝构造,构造出现的这个对象的内容是this想要的,所以使用swap进行内容的交换,将this内容给vtmp这个局部变量,并且让vtmp帮忙释放这块旧空间(生命周期结束调用析构函数完成资源清理)

容量相关的成员函数

//capacity(内容相关的函数)
		size_t size()const
		{
			return _finish - _start;
		}

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//更深层次关于vector内元素的深拷贝比如vector内的元素是string
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

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

		}

resize的实现分为几种情况,一种就是n小于size,这直接将多余的元素删除就好了。对n大于size小于capacity的时候不需要增容,只需补上val使得size== n,对n大于capacity的时候需要增容,这时候增容可以复用reserve,然后填入val。

对于reserve和拷贝构造的时候为什么不适用memcpy这就是深层次的深拷贝问题。

关于深拷贝中的深拷贝问题

什么是深拷贝中的深拷贝问题呢?在reserve和拷贝构造函数中为什么使用了循环赋值没用使用memcpy。
下面这个场景就是vector里面存放的是string类对象,这时候如果发生了增容或者是拷贝构造就会导致程序崩溃。因为memcpy使用的是值拷贝。
image.png
如果使用的是memcpy增容后和增容前,这些旧数据指向的是同一块内存空间,但是增容后旧的空间被释放了,所以打印的时候会出现随机值,扩容后的对象声明周期结束的时候,对同一块内存空间释放第二次导致了程序崩溃。

当我去vs里面测试的时候会发现,程序崩溃,但是内容打印了出来并不是随机值,这里就跟vs中string类的存储方式有关了。
image.png
这里的s1是存储在buf里面的,vs中的buf是一个16个字节大小的字符数组。
image.png
这里的s2是存储在ptr中,ptr是一个char*的指针。
所以如果字符串比较短那么就会存在于_Buf,字符串长就会存在于_Ptr中。因为buf是一个字符数组,发生值拷贝不会出现错误。
因此,为了解决这种深拷贝类型问题,在拷贝和扩容的时候使用循环赋值,因为循环赋值就会调用深拷贝类型的赋值运算符重载完成深拷贝。虽然循环赋值效率低于memcpy(循环要遍历所有元素时间复杂度是O(N)memcpy只需要计算字节一次拷贝过去就可以了)所以在早期的C++STL中,为了追求极致的效率,使用了类型萃取,也就是区分了内置类型和自定义类型,对于内置类型使用memcpy,自定义类型使用循环+赋值。

operator[ ]重载和内容修改函数

//赋值运算符重载
		T& operator[](size_t n)
		{
			return _start[n];
		}

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

/
//modify
		void push_back(const T& val)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = val;
			_finish++;
		}

		void pop_back()
		{
			assert(size());
			_finish--;
		}

		iterator insert(iterator pos, const T& val)
		{
			if (_finish == _endofstorage)
			{
                //扩容需要更新pos迭代器
				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 = val;
			_finish++;
			return pos;
		}
		iterator erase(iterator pos)
		{
			iterator l = pos + 1;
			while (l < _finish)
			{
				*(l - 1) = *l;
				l++;
			}
			_finish--;
			return pos;
		}
		void swap(vector<T>& v)
		{
			::swap(_start, v._start);
			::swap(_finish, v._finish);
			::swap(_endofstorage, v._endofstorage);
		}

insert和erase返回值按照官方文档要求,为了解决迭代器失效的问题。需要使用返回值给迭代器重新赋值。

swap使用的是三次浅拷贝::域作用限定符限定了访问全局域中的swap,因为std在vector头文件之前展开了。所以在全局域中可以找到swap,如果在vector这个头文件之后展开就会找不到,可以使用std::指定访问std里面的swap。

类模板内的嵌套类型

定义了一个可以打印任何类型的模板函数

	template<class Con>
	void PrintV(const Con& v)
	{
		typename Con::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

这里去取Con这个模板类型里面内嵌的const迭代器的时候,前面必须加上typename,因为模板类型还没有实例化,也就是不知到具体的类型。此时取模板类型里面的内嵌类型是不可行的。编译器不认识后面的迭代器类型,加上typename就是告诉了编译器这个Con是一个类,等到模板实例化之后再去里面取内嵌类型。
如果不加会报如下错误:记住就好了
image.png

全部代码

tip:由于博客进行了二次更新,部分代码与上文匹配不当请谅解。

#pragma once

#include<assert.h>
namespace xzj
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

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

/
//构造and析构
		vector()
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
		{}

		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
		}
			
		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)     
		{ 
			while (n--)
				push_back(val);
		}

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

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

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

/
//capacity(内容相关的函数)
		size_t size()const
		{
			return _finish - _start;
		}

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//更深层次关于vector内元素的深拷贝比如string
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

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

		}

			
/
//赋值运算符重载
		T& operator[](size_t n)
		{
			return _start[n];
		}

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

/
//modify
		void push_back(const T& val)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = val;
			_finish++;
		}

		void pop_back()
		{
			assert(size());
			_finish--;
		}

		iterator insert(iterator pos, const T& val)
		{
			if (_finish == _endofstorage)
			{
				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 = val;
			_finish++;
			return pos;
		}
		iterator erase(iterator pos)
		{
			iterator l = pos + 1;
			while (l < _finish)
			{
				*(l - 1) = *l;
				l++;
			}
			_finish--;
			return pos;
		}
		void swap(vector<T>& v)
		{
			::swap(_start, v._start);
			::swap(_finish, v._finish);
			::swap(_endofstorage, v._endofstorage);
		}


	private:
		iterator _start;//指向内存块的开始位置
		iterator _finish;//指向最后一个元素的下一个位置
		iterator _endofstorage;//指向最后一个有效空间的下一个位置
	};

	template<class Con>
	void PrintV(const Con& v)
	{
		typename Con::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}
}

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

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

相关文章

IntelliJ IDEA 面试题及答案整理,最新面试题

IntelliJ IDEA中的插件系统如何工作&#xff1f; IntelliJ IDEA的插件系统工作原理如下&#xff1a; 1、插件架构&#xff1a; IntelliJ IDEA通过插件架构扩展其功能&#xff0c;插件可以添加新的功能或修改现有功能。 2、安装和管理&#xff1a; 通过IDEA内置的插件市场下载…

【海贼王的数据航海】排序——直接选择排序|堆排序

目录 1 -> 选择排序 1.1 -> 基本思想 1.2 -> 直接选择排序 1.2.1 -> 代码实现 1.3 -> 堆排序 1.3.1 -> 代码实现 1 -> 选择排序 1.1 -> 基本思想 每一次从待排序的数据元素中选出最小(或最大)的一个元素&#xff0c;存放在序列的起始位置&…

springCloudeAlibaba的使用

父pom文件&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.o…

【Docker篇】数据卷相关操作

文章目录 &#x1f388;前言&#x1f354;数据卷&#x1f6f8;操作命令⭐创建一个数据卷&#xff0c;并查看数据卷在宿主机的目录位置 &#x1f339;挂载数据卷 &#x1f388;前言 在前面文章的nginx案例中&#xff0c;修改nginx的html页面时&#xff0c;需要进入nginx内部。并…

k8s-高可用etcd集群 26

reset掉k8s2&#xff0c;k8s3&#xff0c;k8s4节点 清理完网络插件后重启 快速创建一个k8s集群 修改初始化文件 添加master节点 备份 查看etcd配置 启动docker 将etcd二进制命令从容器拷贝到本机 备份 查看快照状态 删除集群资源 恢复 停掉所有的核心组件 从快照恢复 重启所有…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:StepperItem)

用作Stepper组件的页面子组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 支持单个子组件。 接口 StepperItem() 属性 参数名参数类型参数描述prevLabelstring设置左侧文本按钮内…

python 统计中国观鸟记录中心官网已观测的鸟类种类

python 统计中国观鸟记录中心官网已观测的鸟类种类 中国观鸟记录中心网站&#xff1a;https://www.birdreport.cn/ 先下载官网 Excel 文件 文件放置目录如下&#xff1a; home dataset xxx.xlsxxxx.xlsxxxx.xlsx Excelgrep.py &#xff08;进行文件内容提取的程序&#xff…

day-21 前 K 个高频元素

思路&#xff1a;用ans[]存储频次最高的k个元素&#xff0c;用anslen[]存储对应的索引&#xff0c;将nums进行排序依次统计每个元素出现次数&#xff0c;再判断是否需要对ans[]和anslen[]进行替换&#xff0c;最后ans即为答案 注意点&#xff1a;遍历结束后&#xff0c;还需要…

数据库引论:2.SQL简介

SQL(Structured Query Language,结构化查询语言) 2.1 SQL查询语言概览 SQL语言包含 数据定义语言(Data-Definition Language,DDL)。SQL DDL提供定义关系模式、删除关系以及修改关系模式的命令。数据操纵语言(Data-Manipulation Language,DML)。SQL DML提供从数据库中查询信息…

【算法】AC自动机的优化:增量更新与删除

一、概述 AC自动机&#xff08;Aho-Corasick Automation&#xff09;是著名的多模匹配算法&#xff0c;源于贝尔实验室&#xff0c;并且在实际应用中得到广泛的引用&#xff0c;且具有以下特点&#xff1a; 只需要扫描一次文本&#xff0c;即可获取所有匹配该文本的模式串复杂…

小红书根据关键词取商品列表 API 返回值说明

小红书根据关键词取商品列表的API返回值通常包含与搜索请求相关的商品列表信息。这些信息包括匹配到的商品列表、商品详情、排序方式等。以下是一个简化的示例&#xff0c;展示了小红书根据关键词取商品列表API可能返回的JSON格式数据&#xff1a;获取调用详情链接 item_searc…

王道机试C++第8章递归与分治 Day35和蓝桥杯两道真题程序

第 8 章 递归与分治 递归是指&#xff1a;函数直接或间接调用自身的一种方法&#xff0c;通常可把一个复杂的大型问题层层转化为与原问题相似但规模较小的问题来求解。 递归策略只需少量的程序就可描述解题过程所需的多次重复计算&#xff0c;因此大大减少了程序的代码量。 8.…

一个命令查看自己的WIFI密码

一个命令查看自己的WIFI密码 忘记wifi密码怎么办&#xff1f;一个命令查看自己的wifi密码。 一、打开命令行 使用快捷键“WinR”&#xff0c;打开运行窗口&#xff0c;输入“cmd”后回车即可。 二、输入命令network shell命令 输入命令network shell&#xff0c;简称netsh&…

Android 面试题及答案整理,最新面试题

Android中Intent的作用及分类 Intent在Android中用作组件之间传递信息&#xff0c;它可以用于启动活动(activity)、服务(service)和发送广播(broadcast)。Intent主要分为显式Intent和隐式Intent两大类。 1、显式Intent&#xff1a; 直接指定了要启动的组件名称&#xff08;比如…

【网络】详解HTTPS及探究加密过程

目录 一、什么是HTTPS1、加密解密是什么2、为什么要加密3、常见的加密方式1、对称加密2、非对称加密 二、探究HTTPS如何实现加密1、方案一----只使用对称加密2、方案二----只使用非对称加密3、方案三----双方都使用非对称加密4、方案四----非对称加密 对称加密5、中间人攻击6、…

企业微信 API 接口调用教程:深入解析企业微信 API 的用法

本文通过 access_token 凭证的方式来讲解怎么调用 企业微信 API&#xff0c;并一步步介绍如何获取企业微信 API 的 corpsecret、corpid、access_token 凭证以及怎么向企业微信的应用发送消息。 企业微信 API 在线地址为&#xff1a;qiyeweixin.apifox.cn/ &#xff0c;这个在线…

STL:List从0到1

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

IO流(5)——转换流

不同编码读取出现乱码的问题 解决方法 字符输入转换流&#xff08;InputStreamReader&#xff09;

pytorch 入门基础知识(Pytorch 01)

一 深度学习基础相关 深度学习三个主要的方向&#xff1a;计算机视觉&#xff0c;自然语言&#xff0c;语音识别。 机器学习核心组件&#xff1a;1 数据集(data)&#xff0c;2 前向传播的model(net)&#xff0c;3 目标函数(loss)&#xff0c; 4 调整模型参数和优化函数的算法…

【Linux-网络编程】

Linux-网络编程 ■ 网络结构■ C/S结构■ B/S结构 ■ 网络模型■ OSI七层模型■ TCP/IP四层模型 ■ TCP■ TCP通信流程■ TCP三次握手■ TCP四次挥手 ■ 套接字&#xff1a;socket 主机IP 主机上的进程&#xff08;端口号&#xff09;■ TCP传输文件 ■ 网络结构 ■ C/S结构…