c++顺序表初始(vector)

news2024/11/13 11:13:35

前言

std是一个容器和算法相关的库,顺序表作为一个常见的容器也在标准库中有相应的实现--vector。今天我们就来简单的认识一下vector的使用,并且简单的模拟实现一个我们的vector

具体vector类的描述可以参考vector - C++ Reference (cplusplus.com)

在不同的编译器下string类的实现各有差异,这里我们使用的是Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.8.5

构造函数

介绍

了解一个类,首先我们要先了解它的构造函数

声明一下,allocator_type是std六大组件之一的空间配置器,目前我们可以直接将其忽略。

首先vector提供了一个默认的构造函数--default(1),我们简单的写一段程序来观察一下

此时我们首先可以得到一个结论,那就是默认的构造函数会将属性赋值为0(这里泛指各种0,例如数字0,0.0亦或是指针nullptr)。其次我们发现标准库使用的是三个指针来维护这个顺序表的。

我们合理猜测_Myfirst标识这块空间的开始位置,_Mylast标识这块空间最后一个有效元素的下一个位置,_Myend标识这块空间结束的下一个位置。

这样_Mylast-_Myfirst就能得到size(有效元素个数),_Myend_Myfirst就能得到capacity(有效元素个数)

第二个构造函数可以一次性初始化n个元素,这个元素可以指定,不指定则使用默认值(这里会调用元素类型的默认构造)

第三个构造函数可以使用迭代器区间(左开右闭)来初始化,这可以和其他容器适配(因为容器是std六大组件之一,std的使用容器都支持迭代器)

第四个就是一个拷贝构造,这里还是要注意深浅拷贝的问题

实现

首先vector应该可以存储各种各样的数据类型,我们也实现一个类模板来适配各种数据类型,我们也学习标准库里用三个指针维护顺序表的方式实现

namespace zzzyh {
	template<class T>
	class vector
	{
	public:
        typedef T* iterator;
    private:
	    iterator _start = nullptr;//开始位置
	    iterator _finish = nullptr;//有效元素的下一个位置
	    iterator _end_of_storage = nullptr;//结束位置的下一个位置
    };
}

这里T类型就是要存储的数据类型,我们也发现顺序表这种结构下的指针就是天然的迭代器,我们定义T*为迭代器(当然标准库的实现要比我们这复杂很多,包括但不限于各种检查,我们这里就实现它基础的功能)

有了类的框架,我们就可以来实现他的构造函数

vector()
	:_start(nullptr),
	_finish(nullptr),
	_end_of_storage(nullptr)
{}

默认构造很简单,直接全部赋为空值即可

vector(int size,const T& x = T())
{
	T* tmp = new T[size];
	_start = _finish = tmp;
	_end_of_storage = tmp + size;
	for (size_t i = 0; i < size; i++)
	{
		push_back(x);
	}
}

这里我们可以初始化n个元素,并且指定初始化的值。这里我们复用了push_back方法,它的作用是尾插一个指定元素,但它的实现还是略微有点复杂,还牵扯到扩容(reserve)的逻辑,这两块内容我们在后面展开,这里先都直接使用了

我们再来实现一个拷贝构造

vector(const vector& tmp)
{
	size_t size = tmp.size();
	reserve(size);
	for (auto& x : tmp) {
		push_back(x);
	}
}

这里还是要注意深浅拷贝的问题,不能简答的理解为赋值操作,而是要开辟一块独立的空间出来

最后我们要来实现一个迭代器区间的构造函数

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

在类模板中我们依然可以使用函数模板,此时使用函数模板就可以实现接收各种各样的迭代器

注意,while条件里最好还是使用!=号,因为某些容器在内存上是不连续的(例如链表)

析构函数

介绍

显然,这个类是有资源需要释放的,那么使用我们默认生成的析构函数是有内存泄露的风险的,需要我们再自己实现一份

实现

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

这里如果_start是nullptr不会做任何处理,我们就不检查了

operator=

operator=也是默认生成的函数,但我们在拷贝构造的部分就讲了,vector是存在深浅拷贝的问题就的,所以我们同样还需要手动实现operator=

//template<class T> 类的内部不需要
vector<T>& operator=(const vector<T>& tmp) {
	if (this == &tmp) {
		return *this;
	}
	vector<T> v(tmp);
	swap(v);
	return *this;
}

迭代器

这里的迭代器用法和功能上和我们再string里讲的完全相同,我们就挑前四个简单实现一下

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

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

empty

判断容器是否为空,即有没有有效元素,空返回true,否则返回false

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

reserve

reserve可以将容量增加到不小于指定大小。如果指定大小小于等于当前容量不做任何处理,大于当前容量会将容量增加到不小于指定大小,具体多大取决于编译器的实现,只保证不会影响数据

void reserve(size_t n) {
			size_t sz = size();
			size_t oldCapacity = capacity();
			if (n <= oldCapacity) {
				return;
			}
			int newCapacity = n > oldCapacity*2 ? n : oldCapacity * 2;
			T* rem = new T[newCapacity];
			if (sz != 0) {
				//memmove(rem, _start, sizeof(T) * sz);
				for (size_t i = 0; i < sz; i++)
				{
					rem[i] = _start[i];
				}
				//delete[] _start;
			}
			delete[] _start;
			_start = rem;
			_finish = rem + sz;
			_end_of_storage = rem + newCapacity;
}

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代
价缺陷问题

resize

resize则会影响数据。如果指定大小<原大小,会将数据减少至指定大小;如果指定大小==原大小数据层面可以理解为不做处理,如果指定大小>原大小则会扩容至指定大小,可以指定扩容使用的值,不指定依然使用默认值。resize在开空间的同时还会进行初始化,影响size

void resize(size_t n,const T& x = T()) {
			if (n == size()) {
			}
			else if (n < size()) {
				_finish = _start + n;
			}
			else {
				reserve(n);
				int i = n-size() ;
				while (i > 0)
				{
					this->push_back(x);
					i--;
				}
			}
			return;
}

增删改查操作

push_back

这可以尾插一个指定的元素,前面我们多次使用它,现在我们就来简单的实现一下

void push_back(const T& data) {
			if (_finish == _end_of_storage) {
				reserve(capacity() == 0 ? 1 : capacity() * 2);
			}
			new(_finish)T(data);//这里和下面注释的代码起到相同的效果,不过这里是拷贝构造
			//*_finish = data; 这里是赋值重载
			_finish++;
}

pop_back

尾删一个元素,并且弹出元素的值(标准库中未弹出,没有返回值,我们这里实现一个弹出版本的)

T pop_back() {
	if (!empty()) {
		T ret = *(_finish - 1);
		_finish--;
		return ret;
	}
}

find

查询第一个符合指定的元素,如果查询到返回此元素的迭代器,否则返回有效元素的下一个位置

注意,这是std算法模块提供的函数,所以我们在vector中不提供具体实现,只简单介绍其用法

insert

在pos位置之前插入一个指定的元素

iterator insert(iterator pos, const T& data) {
	assert(pos >= _start && pos <= _finish);
	if (pos == _finish) {
		push_back(data);
		return pos+1;
	}
	if (_finish == _end_of_storage) {
		int i = pos - _start;
		reserve(capacity() == 0 ? 1 : capacity() * 2);
		pos = _start + i;
	}
	iterator end = this->end()-1;
	while (end >= pos) {
		//memmove(end + 1, end, sizeof(T));
		*(end + 1) = *(end);
		end--;
	}
	new(pos)T(data);
	_finish++;
	return pos+1;
}

clear

清除所有元素

void clear() {
			_finish = _start;
		}

 operator[]

得到指定下标的元素

		T& operator[](int i) {
			return *(_start + i);
		}

		const T& operator[](int i) const 
		{
			return *(_start + i);
		}

swap

交换两个容器的内容

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

erase

删除指定位置的元素

iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			if (pos == _finish - 1) {
				pop_back();
				return pos-1;
			}
			//delete (this[pos - _start]);
			//int i = pos - _start;
			//memmove(pos, pos + 1, sizeof(T) * (size() - 1 - (pos - _start)));
			iterator left = pos;
			while (left!=_finish-1) {
				*left = *(left + 1);
				left++;
			}
			//pos = _start+i;
			_finish--;
			return pos;
		}

迭代器失效

迭代器在vector中的本质就是指针,在我们进行容量相关的操作的时候,不可避免的会对元素存储位置进行修改(例如扩容时会释放旧空间,开辟新空间),如果我们在使用未更新的迭代器会产生类似于野指针的问题,这就是迭代器失效

迭代器失效的原因:

  • 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、
    assign、push_back等
  • 指定位置元素的删除操作--erase

等……

注意,vs对迭代器的检查是较为严格的,使用失效的迭代器程序就直接崩溃,但在Linux中gcc编译下检查的不严格不一定会崩溃。但是失效的迭代器我们认为使用是有风险的,建议更新迭代器后再使用,这也是为什么前面我们在实现容量相关函数的时候,返回值是一个迭代器,就是为了解决迭代器失效的问题

string的迭代器也同样存在这样的问题,不过string的迭代器与下标相比并不常用,所以没在string中强调迭代器失效的问题

迭代器失效的解决办法就是及时更新迭代器

memmove/memcpy在移动类时的问题

memmove/memcpy只是单纯的复制拷贝,并不会开辟额外的空间

如果我们在vector中存储类,例如string,在扩容等容量操作时进行单纯的复制拷贝,此时进行的是浅拷贝,会导致新旧两块空间指向同一块内存,此时我们再释放旧空间会导致新空间跟着一起被释放

所以我们在实现这些容量相关的操作时建议使用深拷贝

结语

以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。

因为这对我很重要。

编程世界的小比特,希望与大家一起无限进步。

感谢阅读!

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

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

相关文章

八股之 Java 常用框架

一、Spring 1.IOC 1.将一个类声明为 Bean 的注解有哪些? Component&#xff1a;通用的注解&#xff0c;可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层&#xff0c;可以使用Component 注解标注。Repository : 对应持久层即 Dao 层&#xff0c;主要用于数据库…

【网络 day1】

服务器可以循环接收客户端的数据&#xff1b;当客户端退出后&#xff0c; 服务器阻塞等待下一个客户端的连接&#xff0c;而后继续通信&#xff1b;当有客户端连接时&#xff0c; 服务器端 打印客户端的IP 和 Port信息&#xff1b;将代码的 send 和 recv 改为 write 和 read&am…

2024年5款值得推荐的图表数据可视化工具推荐,不会Excel做表必备!

只推荐5个数据可视化图表制作网站&#xff0c;保证让你相见恨晚&#xff01; 模板类型全、数量丰富&#xff0c;支持在线编辑&#xff0c;还免费~~ 1、Dycharts 推荐指数&#xff1a;☆☆☆☆☆ 网址&#xff1a;dycharts.com 这是一个功能强大且免费的在线数据可视化制作工…

产品分析 | 便利蜂

​产品信息 产品名称&#xff1a;便利蜂 Slogan&#xff1a;小小的幸福 在你身边 版本号&#xff1a;V1.11.3 大小&#xff1a;23.6M 体验环境&#xff1a;Android6.0.1 品牌概述 便利蜂成立于2016年12月&#xff0c;算是起步较早的企业了&#xff0c;17年2月就开了第一家…

记录一次edu web端渗透测试实录

0x01前言 由于是直接接到的相关需求&#xff0c;所以是针对性的一次渗透测试&#xff0c;以下内容仅仅作为思路分享以及打法扩展 0x02 进后台小妙招 弱口令永远是永恒不变的0day漏洞&#xff0c;这也是我们挖漏洞时的敲门砖&#xff0c;以下给出的是一个很神奇的关于寻找后台…

看 逆行人生

电影和我的职业本身有相关性&#xff0c;而且我特别喜欢徐峥执导的电影&#xff0c;这次的题材也算是碰上自己的胃口。 周六&#xff0c;下了大半天的雨&#xff0c;早上驱车到公司加班&#xff0c;下午六点多到时候特别想去看电影&#xff0c;果断再驱车从公司赶回来&#xff…

excel计算时间差-显示每堂课时间

TEXT(H2 - INDEX($H$2:$H$1000, MATCH(B2, $B$2:$B$1000, 0)), "mm:ss")import pandas as pd# 假设你已经加载了数据 df pd.read_excel(你的文件路径.xlsx)# 将开始时间列转换为datetime类型 df[开始时间] pd.to_datetime(df[开始时间])# 计算每个课堂号组内的时间…

批发供应系统:提升效率与竞争力的关键

在当今复杂多变的商业环境中&#xff0c;批发供应系统作为连接生产商、分销商与零售商的重要纽带&#xff0c;其效率与智能化水平直接决定了供应链的运作效率与市场竞争力。随着信息技术的飞速发展&#xff0c;尤其是大数据、云计算、人工智能&#xff08;AI&#xff09;及物联…

Python基础—数据分析中的可视化技巧

数据分析中的可视化技巧是帮助我们将复杂的数据转化为直观、易于理解的图表和图像的过程。这些技巧不仅有助于发现数据中的模式和趋势&#xff0c;还能增强数据故事的讲述能力。以下是一些常用的数据可视化技巧&#xff0c;以及相应的Python代码示例&#xff08;使用matplotlib…

Diffusion Model相关论文整理(二)

目录 1、AnoDDPM: Anomaly Detection With Denoising Diffusion Probabilistic Models Using Simplex Noise [CVPR Workshop 2022]2、Unsupervised Visual Defect Detection with Score-Based Generative Model[2022]3、DiffusionAD: Denoising Diffusion for Anomaly Detectio…

接口自动化-代码实现

接口自动化基础 1、接口自动化测试 接口自动化&#xff1a;使用工具或代码代替人对接口进行测试的技术测试目的&#xff1a; 防止开发修改代码时引入新的问题测试时机&#xff1a; 开发进行系统测试转测前&#xff0c;可以先进行接口自动化脚本的编写开发进行系统测试转测后&…

Tensorflow实现深度学习案例7:咖啡豆识别

本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 一、前期工作 1. 导入数据 from tensorflow import keras from tensorflow.keras import layers,models import numpy as np import matplotlib.pyplot as plt import os,PIL,p…

地平线旭日X3开发板--图像获取时间戳问题

需求 需要获得图像接收完成后的帧时间戳。 sensor f37, MIPI 通信 问题 按我的了解&#xff0c;一般是在内核中产生MIPI数据接收完成中断并打印时间戳&#xff0c; 一般是CLOCK_MONOTONIC方式的时间 &#xff0c; X3无法获得MIPI数据接收完成的时间戳。 X3平台HB_VIN_GetC…

4 - Linux远程访问及控制

目录 一、SSH远程管理 1. SSH概述 2.SSH的优点 3.配置OpenSSH客户端 4.sshd服务支持的两种验证方式 5. 使用SSH客户端程序 5.1 ssh - 远程登录 5.2 scp - 远程复制 6.配置密钥对验证 二、TCP Wrappers访问控制 1.TCP Wrappers 概述 2. TCP Wrappers 机制的基本原则 …

MS SQL Server partition by 函数实战二 编排考场人员

目录 需求 输出效果 范例运行环境 表及视图样本设计 功能实现 生成考场数据 生成重复的SQL语句 封装为统计视图 编写存储过程实现统计 小结 需求 假设有若干已分配准考证号的考生&#xff0c;准考证号示例&#xff08;01010001&#xff09;共计8位&#xff0c;前4位…

ZeroEA阅读笔记

ZeroEA阅读笔记 摘要 实体对齐(EA)是知识图(KG)研究中的一项关键任务&#xff0c;旨在识别不同知识图谱中的等效实体&#xff0c;以支持知识图谱集成、文本到SQL和问答系统等下游任务。考虑到KG中丰富的语义信息&#xff0c;预训练语言模型(PLM)凭借其卓越的上下文感知编码功…

使用SSMS操作AdventureWorks 示例数据库

简介 AdventureWorks 示例数据库&#xff0c;官方文档&#xff1a;https://learn.microsoft.com/zh-cn/sql/samples/adventureworks-install-configure?viewsql-server-ver16&tabsssms 下载备份文件 OLTP 数据适用于大多数典型的联机事务处理工作负载。数据仓库 (DW) 数据…

网络设备监控工具 PIGOSS BSM 网络设备-Ruijie设备SNMP配置及监控

目录 1. 全局模式 2. 配置SNMP V2 3. 配置SNMP V3 4. 配置SNMP Trap 5. 保存配置 6. 查看配置结果 7. 锐捷设备监控 1. 全局模式 SNMP 的配置工作在网络设备的全局配置模式下完成&#xff0c;在进行SNMP 配置前&#xff0c;请先进入全局配置模式。 Ruijie>enable …

Excel“取消工作表保护”忘记密码并恢复原始密码

文章目录 1.前言2.破解步骤3. 最终效果4.参考文献 1.前言 有时候别人发来的Excel中有些表格不能编辑&#xff0c;提示如下&#xff0c;但是又不知道原始密码 2.破解步骤 1、打开您需要破解保护密码的Excel文件&#xff1b; 2、依次点击菜单栏上的视图—宏----录制宏&#xf…

Spring Boot内嵌Tomcat处理请求的链接数和线程数

Spring Boot内嵌Tomcat处理请求的连接数和线程数 处理请求的连接数和线程数配置 Spring Boot的配置项 #等待连接数 server.tomcat.accept-count100 #最大链连接数 server.tomcat.max-connections8192#最小备用线程数 server.tomcat.threads.min-spare10 #最大工作线程数 ser…