秒懂C++之vector(下)

news2024/9/20 18:31:59

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

 

前言

一.vector容器的模拟实现

1.1 基本配置

1.2 reserve 扩容

1.3 push_back 尾插

1.4 insert 插入

1.5 【】运算符

1.6 拷贝构造

1.7 = 运算符

1.8 resize 扩容初始化

1.9 erase 删除

1.10 迭代器构造

二.例题练习

17. 电话号码的字母组合

题目解析:

代码:

递归展开图:

三.模拟实现全部代码


前言

建议大家看完我之前关于string容器模拟实现的文章,这样在这里看vector容器会更加得心应手~

秒懂C++之string类(下)-CSDN博客


一.vector容器的模拟实现

1.1 基本配置

#include <assert.h>
#include <string.h>

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

		//构造函数
		vector()
		{
			_start = nullptr;
			_finish = nullptr;
			_endofstorage = nullptr;
		}
        //析构函数
		~vector()
		{
			if(_start)
			delete[] _start;
			_start = nullptr;
			_finish = nullptr;
			_endofstorage = nullptr;
		}
        //容量
		size_t capacity()const
		{
			return _endofstorage - _start;
		}
        //长度
		size_t size()const
		{
			return _finish - _start;
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator end()const
		{
			return _finish;
		}

		


	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	
	};


}

 

1.2 reserve 扩容

避坑指南:

假如我们的扩容函数这么写~

首先在finish指针的更新中size()里面用的仍然是新的strat,而不是没更新前的,这就导致我们的size无法算出原来空间的大小,使得b = a +b-a = b,让finish指向原空间的窘境~

所以我们得保留在异地扩容前原空间的长度,使得finish更新成功~ endofstorage也是同理不要用capacity,因为里面也有新更新的start~

​
void reserve(size_t n)
		{
			if (n>capacity())
			{
				size_t old = size();//记录原空间的大小
				T* tmp = new T[n];//开辟新空间——异地扩容

				if (_start)
				{
					memcpy(tmp, _start, old * sizeof(T));//拷贝数据
					delete[] _start;//释放原空间
				}
				_start = tmp;//指向新空间
				_finish = _start + old;
				_endofstorage = _start + n;
			}

		}

​

不过这里面留下了一个小坑,遇到深拷贝类型时,memcpy是完全靠不住的~

memcpy在遇到扩容拷贝数据采用的是浅拷贝,如果我们是int类型那还好说,可一旦涉及到string类型这种有指向空间资源的,一旦进行析构那么最终新开辟的空间所指向的资源就没有了,成为野指针~

所以最好不要用memcy进行数据挪动~

1.3 push_back 尾插

//尾插函数
		void push_back(const T& x)
		{
			//检查扩容
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}

1.4 insert 插入

//插入函数
		void insert(iterator pos, const T& x)
		{
			assert(pos <= _finish && pos >= _start);
			//检查扩容
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			//开始利用函数插入,往后挪动数据
			memmove(pos + 1, pos, (_finish - pos) * sizeof(T));
			*pos = x;
			_finish++;
		}

有时候发现插入失败了,这是什么原因呢?

避坑指南:迭代器失效~

如果Insert遇到扩容的时候会发现pos会成为野指针,这是因为我们用begin()(让pos指向start)传递过去的时候还没有扩容,那么pos就只能指向原空间而在insert扩容时,但是这个时候并没有去更新pos指向新空间,毕竟pos不可能会跟着startd的变化而实时变化。所以我们需要重新定位pos。

然后我再分享一个我遇到的错误:

使用迭代器遇到扩容也一定要先分清先后顺序,不然就会像我一样导致死循环~

这里也顺便改一下memmove,因为会跟reserve的memcpy出现一样的情况~

1.5 【】运算符

		T& operator[](size_t pos)
		{
			assert(pos < size());

			return _start[pos];
		}

1.6 拷贝构造

//拷贝构造 现代写法 v2(v1)
		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}

		}

传统写法无非就是开辟新的空间,然后拷贝原空间数据,最后更新指针指向~现代写法的话理解起来更方便,写法更简单吧~

1.7 = 运算符

		void swap(vector<T>& v)
		{
			std::swap(_start,v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

仍然是现代写法~不过这个在string类讲过了,就不画图了~

1.8 resize 扩容初始化

//resize 扩容初始化   
		void resize(size_t n, const T& val = T())
		{
			if (n > size())
			{
				reserve(n);//扩容
				while (_finish < _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

这里默认初始化的值是通过匿名对象调用构造函数进行给值的~

1.9 erase 删除

//erase 删除
		void erase(iterator pos)
		{
			assert(pos <= _finish && pos >= _start);
			iterator it = pos + 1;
			while (it<_finish)
			{
				*(it-1) = *it;
				it++;
			}
			_finish--;
		}

在数据挪动这方面利用迭代器可以避开传统挪动出现的类型提升等问题~ 

不过这样还是有问题:迭代器失效~

例如当我们多次使用erase从而引发某种编译器的缩容条件时,那么指向原本空间的pos就会失效变成野指针~

原则上insert与erase都有空间扩容与缩容的问题,而这些会引发迭代器的失效。尽管我们在insert已经遇到过了,但只是让内部的pos生效,影响不到作为实参的外部迭代器,治标不治本。

最终我们是这样处理的,为了不让使用过这两接口的迭代器失效,我们为其添加返回值。然后调用的时候以it = v.erase(it)这样的形式进行重新定位,避免其失效~

1.10 迭代器构造

//迭代器构造
		template <class Inputiterator>
		vector(Inputiterator first, Inputiterator last)
		{
			while (first != end)
			{
				push_back(*first);
				first++;
			}
		}

可以利用其他类型来为构造自己~可以当成泛型模板

不过如果我们模拟出了这个迭代器构造需要注意另外一个问题:类型匹配~

本来是想要构造连续5个1的vector,但是却匹配到了这个模板去了,因为类型更匹配~

为此还需要再搞一个函数确保匹配~

二.例题练习

17. 电话号码的字母组合

17. 电话号码的字母组合 - 力扣(LeetCode)

题目解析:

本质上是多叉树的深度优先遍历,通过考虑用递归的方式解决问题~

而递归通过需要具备两点条件,一为离开条件,二为如何处理深度递进

我们先来构造好递归函数的整体框架~

  • 首先形参里面肯定要有digits,毕竟题目在里面。
  • 然后就是设置一个可以获取数字的下标di,比如我们想要知道2的映射字符串,那就用di进去digits获取数字2~
  • 再来一个形成完整字符串(例如"adg")的stirng类型的cstr
  • 最后就是存放所有结果的容器vector<stirng> v

考虑完递归函数后我们先来尝试写出一次深度遍历~

假设digits为“23",通过下标di先获取当前数字2,然后再通过数字2映射对应字符串~

然后设置一层循环去下一次递归中链接字符‘d'

离开条件只要字符串长度达标即可,我们可以通过di与digits的关系来作为离开条件~

代码:

class Solution {
public:
    string _numb[10] = { "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz" };
    void Combin(const string& digits, size_t di, string cstr, vector<string>& v)
    {
        //设置离开条件
        if (di == digits.size())
        {
            //把完整字符串加入
            v.push_back(cstr);
            return;
        }
        //获取当前数字
        size_t num = digits[di] - '0';
        //通过数字映射对应字符串
        string str = _numb[num];
        //遍历每个数字对应的字符
        for (int i = 0; i < str.size(); i++)
        {
            //深度遍历
            Combin(digits, di + 1, cstr + str[i], v);
        }
    }

    vector<string> letterCombinations(string digits) {
        vector<string> v;
        //特殊判断
        if (digits.empty())
        {
            return v;
        }
        Combin(digits, 0, "", v);
        return v;
    }
};

递归展开图:

三.模拟实现全部代码

//vector.h
#pragma once

#include <assert.h>
#include <string.h>

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

		//构造函数
		vector()
		{
			_start = nullptr;
			_finish = nullptr;
			_endofstorage = nullptr;
		}
		//拷贝构造 现代写法 v2(v1)
		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}

		}

		T& operator[](size_t pos)
		{
			assert(pos < size());

			return _start[pos];
		}
		void swap(vector<T>& v)
		{
			std::swap(_start,v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}
		//析构函数
		~vector()
		{
			if(_start)
			delete[] _start;
			_start = nullptr;
			_finish = nullptr;
			_endofstorage = nullptr;
		}
		size_t capacity()const
		{
			return _endofstorage - _start;
		}

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

		iterator begin()
		{
			return _start;
		}

		const_iterator begin()const
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator end()const
		{
			return _finish;
		}

		void reserve(size_t n)
		{
			if (n>capacity())
			{
				size_t old = size();//记录原空间的大小
				T* tmp = new T[n];//开辟新空间——异地扩容
				if (_start)
				{
					for (size_t i = 0; i < old; i++)
					{
						tmp[i] = _start[i];//赋值拷贝为深拷贝
					}
					delete[] _start;//释放原空间
				}
				_start = tmp;//指向新空间
				_finish = _start + old;
				_endofstorage = _start + n;
			}

		}
		//尾插函数
		void push_back(const T& x)
		{
			//检查扩容
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}
		//插入函数
		iterator insert(iterator pos, const T& x)
		{
			assert(pos <= _finish && pos >= _start);
			//检查扩容
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;//记录原空间长度
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = len + _start;//让pos指向新空间并且同步长度
			}
			//开始利用函数插入,往后挪动数据
			//memmove(pos + 1, pos, (_finish - pos) * sizeof(T));
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			_finish++;
			return pos;
		}
		//resize 扩容初始化   
		void resize(size_t n, const T& val = T())
		{
			if (n > size())
			{
				reserve(n);//扩容
				while (_finish < _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

		//erase 删除
		iterator erase(iterator pos)
		{
			assert(pos <= _finish && pos >= _start);
			iterator it = pos + 1;
			while (it<_finish)
			{
				*(it-1) = *it;
				it++;
			}
			_finish--;
			return pos;
		}
		//迭代器构造
		template <class Inputiterator>
		vector(Inputiterator first, Inputiterator last)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

		

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
	
	};
	void test_vector()
	{
		//vector<int> v;
		//v.push_back(1);
		//v.push_back(2);
		//v.push_back(3);
		//v.push_back(4);
		//v.push_back(5);
		//v.push_back(5);
		//v.push_back(5);
		//v.push_back(5);

		v.insert(v.begin(), 0);
		//vector<int>::iterator it = v.begin();
		//while (it != v.end())
		//{
		//	cout << *it << ' ';
		//	it++;
		//}
		//cout << endl;
		//


		//vector<int>::iterator it1 = v.begin();错误示范,这会让it1指向原空间,变成野指针
		//vector<int> v;
		//v.push_back(1);
		//v.push_back(2);
		//v.push_back(3);
		//v.push_back(4);
		//v.push_back(5);
		//v.push_back(5);
		//v.push_back(5);
		//v.push_back(5);
		//vector<int> v1;
		//v1.push_back(1);
		//v1.push_back(2);
		//v1.push_back(3);
		//v1.push_back(4);
		//v1.insert(v1.begin(), 0);
		//vector<int>::iterator it1 = v1.begin();
		//while (it1 != v1.end())
		//{
		//	v1[0]++;
		//	cout << *it1 << ' ';
		//	it1++;
		//}
		//cout << endl;


		//vector<int> v1;
		//v1.push_back(1);
		//v1.push_back(2);
		//v1.push_back(3);
		//v1.push_back(4);
		//v1.push_back(5);
		//v1.push_back(5);
		//v1.push_back(5);
		//v1.push_back(5);
		//vector<int> v2;
		//v2 = v1;
		//vector<int>::iterator it1 = v2.begin();
		//while (it1 != v2.end())
		//{
		//	cout << *it1 << ' ';
		//	it1++;
		//}
		//cout << endl;

		//vector<int> v1;
		//v1.push_back(1);
		//v1.push_back(2);
		//v1.push_back(3);
		//v1.push_back(4);
		//v1.push_back(5);
		//v1.push_back(5);
		//vector<int>::iterator it = v1.begin();
		//while (it != v1.end())
		//{
		//	cout << *it << ' ';
		//	it++;
		//}
		//cout << endl;

		//v1.resize(3);
		//vector<int>::iterator it1 = v1.begin();
		//while (it1 != v1.end())
		//{
		//	cout << *it1 << ' ';
		//	it1++;
		//}
		//cout << endl;


		/*vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		v1.push_back(5);
		vector<int>::iterator it = v1.begin();
		v1.insert(it + 1,1);
		v1.insert(it + 1, 1);
		

		for (auto e : v1)
		{
			cout << e << ' ';
		}
		cout << endl;*/
	/*	vector<string> vstr;
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");
		vstr.push_back("1111");

		for (auto e : vstr)
		{
			cout << e << ' ';
		}
		cout << endl;*/
		int a[] = { 1,2,3 };
		vector<int> v(a, a + 3);
		for (auto e : v)
		{
			cout << e << ' ';
		}
		cout << endl;

	}
}

//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>

using namespace std;
#include "vector.h"

int main()
{
	lj::test_vector();

	return 0;
}

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

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

相关文章

pytorch学习笔记3 tensor索引和切片

dim 0 占先 切片 &#xff08;前N或者后N个&#xff09; &#xff1a;2 表示 0到2&#xff08;不包含2&#xff09;&#xff0c; 1&#xff1a;表示 1到末尾&#xff0c; -1表示最后一个元素&#xff0c;-2表示倒数第二个 0:28:2 表示从0到27隔点采样 &#xff1a;&#xff…

【反序列化漏洞】serial靶机详解

一、安装靶机 首先创建新的虚拟机。 然后选择客户机版本为Ubuntu 64位。 然后选择使用现有磁盘&#xff0c;选择下载的vmdk磁盘文件即可。剩下的都是默认 二、信息收集 发现主机192.168.204.143 访问 扫描端口nmap -A 192.168.204.143 -p-&#xff0c;发现只有ssh:22和http:8…

科普文:微服务之分布式链路追踪SkyWalking单点服务搭建

1. 概述 1.1 概念 SkyWalking 是什么&#xff1f; SkyWalking 极简入门 | Apache SkyWalking FROM Apache SkyWalking 分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Docker、K8s、Mesos&#xff09;架构而设计。 提供分布式追…

【Bugku】Web系列第二更~

1.你必须让他停下 查看源代码&#xff0c;一直刷新&#xff0c;直到看见flag 2.矛盾 构造一个?num1[字母&#xff0c;随便写到最后都会被PHP转化成0] 这里利用的是PHP对数字和字符串比较的时候的一个特性&#xff0c;他会试图把字符串转化为数字&#xff0c;所以1XXX转化到最后…

学习笔记 - 二极管的参数与选型

二极管 普通二极管&#xff1a; 1N4148(高频开关二极管) 整流二极管&#xff1a; 1N4007 1A 1000V1N5408 3A 1000V 肖特基二极管 &#xff08;白线边为阴极&#xff09; SS14 SS34 SS54 常见肖特基二极管参数 快恢复二极管 FR107 FR207 FR307 UF4007 可以用快恢复二…

Golang | Leetcode Golang题解之第315题计算右侧小于当前元素的个数

题目&#xff1a; 题解&#xff1a; var a, c []intfunc countSmaller(nums []int) []int {resultList : []int{}discretization(nums)c make([]int, len(nums) 5)for i : len(nums) - 1; i > 0; i-- {id : getId(nums[i])resultList append(resultList, query(id - 1))…

前端开源插件

Luckysheet&#xff1a;类似Excel&#xff0c;在线电子表格工具 源码地址 https://github.com/dream-num/Luckysheet Luckysheet 已不再维护&#xff0c;推荐使用 Univer 替代 | Luckysheet文档Luckysheet &#xff0c;一款纯前端类似excel的在线表格&#xff0c;功能强大、…

Java重修笔记 第二十七天 匿名内部类

匿名内部类 1. 定义&#xff1a;无类名&#xff08;底层自动分配类名“外部类名$1”&#xff09;&#xff0c;既是类也是对象&#xff0c;定义在外部类的局部位置&#xff0c;例如方法体和代码块中&#xff0c;通过new类或接口并在大括号里重写方法来实现。 2. 使用场景&…

自定义监控

代码说明&#xff1a; 导入必要的库 import time import psutil import GPUtil from prometheus_client import start_http_server, Summary, Counter, Gaugepsutil&#xff1a;用于获取系统的CPU、内存、磁盘和网络信息。GPUtil&#xff1a;用于获取GPU信息。prometheus_cli…

git拉完代码总是自动创建一个新的节点

git拉完代码&#xff0c;总是自动生成弹出这个信息 然后还会在git上面留下一个节点&#xff0c;这个节点没啥用&#xff0c;显示着感觉有点碍事。 而且后续的git push 之后&#xff0c;会覆盖掉自己的git commit 的提示&#xff0c;其他人cr代码的时候看到的是 解决方法&#…

【深度学习】【语音TTS】OpenVoice v2,测评,中英文语料,Docker镜像,对比GPT-SoVITS、FishAudio、BertVITS2

https://github.com/myshell-ai/OpenVoice/blob/main/docs/USAGE.md 实际体验OpenVoice v2的TTS效果。 文章目录 环境启动 jupyter代码代码分析主要模块和功能测试一些别的中文和中英文混合总结优点缺点对比GPT-SoVITS、FishAudio、BertVITS2使用我的Docker镜像快速体验OpenVo…

uni-app封装组件实现下方滑动弹出模态框

子组件 <template><div class"bottom-modal" :class"{show: showModal}"><div class"modal-content" :class"{show: showModal}"><!-- 内容区域 --><slot></slot></div></div></…

收银系统源码-分销商城视频介绍

系统介绍 专门为零售行业的连锁店量身打造的收银系统&#xff0c;适用于常规超市、生鲜超市、水果店、便利店、零食专卖店、服装店、母婴用品、农贸市场等类型的门店使用。同时线上线下数据打通&#xff0c;线下收银的数据与小程序私域商城中的数据完全同步&#xff0c;如商品…

欧科云链7月安全月报 | 私钥泄露损失约占总损失88%,超2.6亿美元

7 月全网累计造成损失约 2.9 亿美元&#xff0c;因私钥泄露所造成损失占总损失的 88.31%&#xff0c;其中 WazirX 因多签钱包私钥泄露&#xff0c;造成约 2.35 亿美元的损失&#xff0c;为 7 月最大安全事件。 最大安全事件-私钥泄漏 7 月 18 日&#xff0c;WazirX 多签钱包私…

Spring Boot集成Resilience4J实现断路器功能

1.什么是Resilience4J&#xff1f; Netflix Hystrix 断路器是 Spring Cloud 中最早就开始支持的一种服务调用容错解决方案&#xff0c;但是目前的 Hystrix 已经处于维护模式了&#xff0c;虽然这并不影响已经上线的项目&#xff0c;并且在短期内&#xff0c;你甚至也可以继续在…

【AD域】搭建AD域服务器

环境 服务器&#xff1a;Windows Server 2016 Standard&#xff0c;版本1607 准备 1、设置主机名 2、配置静态IP地址 3、以本地管理员权限登录服务器 步骤 1、在服务器添加【Active Directory】域服务功能 2、AD域服务器配置

ERP系统提高生产企业库存周转率的抓手

引言 生产企业库存积压&#xff0c;有市场波动的原因&#xff0c;也有内部管理的原因。常见的内部管理原因有&#xff1a; 物料买多了&#xff0c;长期积压在仓库 缺料&#xff0c;生产不能及时完工&#xff0c;在制品积压 物料买早了&#xff0c;在仓库呆滞时间过长 在ERP…

RIP路由协议之网络工程师软考中级

几种常见的路由协议 路由协议名称路由协议分类&#xff08;工作原理&#xff09;协议分类&#xff08;工作区域&#xff09;路由算法RIP距离矢量IGPBellman-FordOSPF-ISIS链路状态IGPDijkstraBGP路径向量EGP/ IGP称为内部网关协议&#xff08;I人&#xff0c;内向&#xff09…

瑞芯微Android设备指定应用开启性能模式

本文档主要针对特定应用&#xff08;如 安兔兔&#xff09;如何进行加速及性能的提升做相关说明。 快速开启方法 开启与关闭 在 device/rockchip/rk3xxx/下配置文件:package_performance.xml&#xff0c;在其中的节点中加入需要使用性能模式的包名&#xff1a;&#xff08;…

Java零基础之多线程篇:如何保证线程安全?

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…