vector(2)

news2024/11/13 8:38:03

前言

通过上一节的学习,我们知道了vector中可以存放各种类型的数据,这就意味着vector之中不仅仅可以存放int、char等内置类型,还可以存放vector和string等类型,我们结合底层的具体情况来具体分析

vector的复用(套娃)

我们首先需要知道,顺序表中含有三个核心成员:

1.指针:该指针指向具体的数组

2.size:表示该数组之中含有有效数据的个数

3.capacity:表示该数组的容量

我们先来假设顺序表的类型是int,那么此时指针的类型就是int*,那么此时数组中的每个数据的类型就是int:

同理,我们把int类型换成vector<int>类型,情况就会如下图所示:

此时指针的类型就变成了vector<int>*,那么此时数组中的每个数据的类型就是vector<int>了。而每个vector<int>类型的数据中都含有三个核心成员:指针、size、capacity,其中每个指针的数据类型都是int*

这样的情况就有点像二维数组了,假设我们需要开辟一个10*5的int类型的二维数组:

	vector<int> v(5, 1);
	vector<vector<int>> vv(10, v);

假设此时我们需要访问二维数组中的某个数据,就可以用如下的方式访问:

	vv[2][1] = 2;

之所以可以用这样的形式去访问,是因为它的底层实现是这样的:

template<class t>
class vector
{
	t& operator[](int i)
	{
		assert(i < _size);

		return _a[i];
	}
private:
	t* _a;
	size_t _size;
	size_t _capacity;
};

vector的底层运用了模板,当我们写出上述代码的时候,编译器就会自动生成这些代码:

// vector<int>
class vector
{
	int& operator[](int i)
	{
		assert(i < _size);

		return _a[i];
	}
private:
	int* _a;
	size_t _size;
	size_t _capacity;
};

 vector<vector<int>>
class vector
{
	vector<int>& operator[](int i)
	{
		assert(i < _size);

		return _a[i];
	}
private:
	vector<int>* _a;
	size_t _size;
	size_t _capacity;
};

所以我们就可以知道,下面的两句代码是等效的

	vv[2][1] = 2;
	vv.operator[](2).operator[](1) = 2;

假设我们需要遍历这个二维数组就会很方便:

	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;

介绍到这里我们就来看几个题目加深印象(leetcode第118题):

在做这个题目之前我们需要知道这个题目使用静态数组是无法解决问题的,原因有以下两点:

1.该数组是需要动态开辟

2.该数组不是规整的,即每一行的数据个数都是不相同的

下面我们来讲解这个题目的解题步骤:

首先我们需要开辟numRows行的数据:

        vector<vector<int>> vv(numRows);

开完行以后我们就需要去开列,这里需要注意:每一列的数据个数都是不相同的,我们先默认给每一列的数据初始化为0。再来观察一下杨辉三角的特征,我们可以知道每一行的第一个数据和最后一个数据都是1(或者resize的时候全部改成1):

        for (size_t i = 0; i < numRows; i++)
        {
            vv[i].resize(i + 1, 0);
            vv[i].front() = vv[i].back() = 1;
            // == vv[i][0] = vv[i][vv[i].size() - 1] = 1;
        }

随后我们要对其他位置上的数据进行处理,我们通过观察可以知道,某位的数据等于上一行同一列和上一列的数据之和

        for (size_t i = 2; i < vv.size(); i++)
        {
            for (size_t j = 1; j < vv[i].size()-1; j++)
            {
                vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
            }
        }

到这里,代码就全部完成了,我们将代码整合一下并且提交:

class Solution
{
public:
    vector<vector<int>> generate(int numRows)
    {
        vector<vector<int>> vv(numRows);
        for (size_t i = 0; i < numRows; i++)
        {
            vv[i].resize(i + 1, 0);
            vv[i].front() = vv[i].back() = 1;
            // == vv[i][0] = vv[i][vv[i].size() - 1] = 1;
        }

        for (size_t i = 2; i < vv.size(); i++)
        {
            for (size_t j = 1; j < vv[i].size()-1; j++)
            {
                vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
            }
        }

        return vv;
    }
};

vector的模拟实现

下面我们来模拟实现一下vector

首先我们先创造一个命名空间来封装一下

在开始之前我们需要知道:

1.模板是不能分离到两个文件中的,因为如果分离到两个文件中去的话,就会出现链接错误(之前string分离了是因为没有写模板)

2.vector的源代码在底层实现的时候有三个核心成员:_start、_finish、_end_of_storage

那么了解到这里我们就可以先写出vector的基本框架:

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

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

接下来我们就来逐步完成成员函数代码的编写:


size、capacity、begin、end等函数的实现比较简单,也不做过多的解释了:

		size_t size()
		{
			return _finish - _start;
		}

		size_t capacity()
		{
			return _end_of_storage - _start;
		}

 我们再来实现reserve:

我们首先来判断要开辟的空间是否大于当前的空间,如果要开辟的空间小于当前空间,则不做任何处理,保持原空间不变;大于则继续扩容

我们首先要先创建一个变量tmp,用于保存新开辟的空间(不能直接赋值),接着用memcpy拷贝原来的数据,最后释放旧的空间并且指向新的空间:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				memcpy(tmp, _start, size() * sizeof(T));
				delete[] _start;
				_start = tmp;
				_finish = _start + size();
				_end_of_storage = tmp + n;
			}
		}

我们继续实现push_back函数,首先我们需要判断剩余的空间是否足够,如果没有满就直接实现尾插,如果空间满了就开辟新空间:

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

接下来我们测试一下push_back能否正常使用:

为了方便完成调试,我们先来重载一下[]:

		T& operator[](size_t i)
		{
			assert(i < size());
			return _start[i];
		}
	void test_vector01()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;
	}

通过测试我们发现使用push_back函数程序会崩溃,经过检查发现_finish是一个空指针,所以此时我们就需要先检查reserve的代码:

通过调试可以知道是这个代码出现了问题,在这里调用size函数会出现很大的问题,因为在最开始的时候三个变量都是旧空间的数据,经过     _start = tmp;   这一串代码,start变量已经指向了新空间,然而finish此时仍旧是旧空间

此时调用size就会有问题,因为finish指向的是扩容前的,start指向的是扩容后的,修改以后的reserve代码应该为:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				memcpy(tmp, _start, size() * sizeof(T));
				delete[] _start;

				_finish = _start + size();
				_start = tmp;
				_end_of_storage = tmp + n;
			}
		}

或者可以这样写(标准库的写法):

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();
				T* tmp = new T[n];
				memcpy(tmp, _start, size() * sizeof(T));
				delete[] _start;

				_start = tmp;
				_finish = _start + old_size;
				_end_of_storage = tmp + n;
			}
		}

**************************************************************************************************************

假设我们要测试下面函数的运行:

	template<class T>
	void print_vector(const vector<T>& v)
	{
		vector<T>::const_iterator it = v.begin();
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

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

我们会很奇怪的发现:该函数报了一个很离谱的错误:

我们明明写了;,但是为什么提示没有写呢?

因为此时类模板是没有实例化的,::在类中取东西的时候可以识别是取类型,也可以识别是取静态成员变量,只有实例化以后才可以直接用::取东西

规定:没有实例化的类模板里面取东西,编译器不能够区分,此时要取的是类型就要加上typename,没有加typename就默认是静态成员变量

	template<class T>
	void print_vector(const vector<T>& v)
	{
		// 规定,没有实例化的类模板里面取东西,编译器不能区分这里const_iterator
		// 是类型还是静态成员变量
		typename vector<T>::const_iterator it = v.begin();
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

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

************************************************************************************************************** 


接着实现一下pop_back:

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

因为vector的接口实现起来都不复杂,所以这后面就仅仅讲解一下稍微复杂一点的接口:


insert接口的实现:

假设我们要在某一个位置pos处插入一个数据,那么我们先要将pos之后的数据都整体向后挪动一位再插入数据我们根据逻辑可以写出代码如下:

		iterator insert(iterator pos, const T& x)
		{
			// 扩容
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;

			++_finish;

			return pos;
		}

我们来测试一下:

	void test_vector2()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		print_vector(v);

		v.insert(v.begin() + 2, 30);
		print_vector(v);
    }

此时代码不需要扩容,没有任何问题,但是我们把测试代码改一下:

	void test_vector2()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		print_vector(v);

		v.insert(v.begin() + 2, 30);
		print_vector(v);
    }

此时代码需要扩容而且出现了问题

我们通过调试发现:扩容的代码并没有问题,而是挪动数据的过程出现了问题,数据挪动到pos没有停止,而是一直持续的向后挪动,这是为什么呢?

这个问题就叫做迭代器失效,我们画一个图来看一下:

因为在扩容后pos指针没有指向新空间,还是指向原来的空间,此时pos指针就类似于一个野指针,这样就会越界访问,一直持续的往后挪动数据。

要解决这个问题我们就要记录相对位置,确定pos改变以后的位置

		iterator insert(iterator pos, const T& x)
		{
			// 扩容
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;

			++_finish;

			return pos;
		}

************************************************************************************************************** 

假设我们要在vector中查找,但vector没有实现find接口,此时库中有一个find函数可以供我们使用:

find需要传两个迭代器表示开始和结束的区间、还有要查找的值,注意这里的迭代器区间是左闭右开,如果没有找到就返回last(右边是开区间,不包含last)

我们来测试一下:

	void test_vector2()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		//v.push_back(5);

		print_vector(v);

		v.insert(v.begin() + 2, 30);
		print_vector(v);

		int x;
		cin >> x;
		auto p = find(v.begin(), v.end(), x);
		if (p != v.end())
		{
			v.insert(p, 40);
			(*p) *= 10;
		}
		print_vector(v);
	}

 我们发现这里出了错误,因为我们本来打算让2乘以10,但这里是40乘以10,这是为什么呢?

这里其实也是一种迭代器失效,迭代器insert以后pos就失效了,不要去访问,因为p指向的是旧空间。但是我们刚刚明明修正了,为什么还是不行呢?

因为形式参数的改变不会影响实际参数,所以还会失效。

并且这里还不能使用引用,因为这样很多语法就不会支持,例如:

		v.insert(v.begin() + 2, 30);

 这里修改以后返回的就是一个临时变量了,就不支持这样的语法了

所以这里我们需要注意以后insert了就不要直接访问,要访问就要更新位置:

			p = v.insert(p, 40);
			(*(p+1)) *= 10;

 

************************************************************************************************************** 

结尾

因为这里牵扯到了迭代器失效,要详细讲解又得需要大块内容,所以本章就先到这里结束了,下一节我们接着学vector,希望可以给您带来帮助,谢谢您的浏览!!!

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

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

相关文章

光控资本:股票增发是什么意思?股票增发的形式?

股票增发配售是已上市的公司通过指定投资者&#xff08;如大股东或组织投资者&#xff09;或全部投资者额定发行股份搜集资金的融资办法。 留意&#xff1a;股票增发后&#xff0c;股价会除权下降。由于增发后股本扩大了&#xff0c;那么每股收益与每股净资产均下降&#xff0…

今天一次讲明白C++条件变量

在C中&#xff0c;std::condition_variable 条件变量是一个同步原语&#xff0c;它允许一个或多个线程在某个条件成立时&#xff0c;被另一个线程唤醒。std::condition_variable 条件变量通常与互斥锁&#xff08;std::mutex&#xff09;一起使用&#xff0c;以保护共享数据和同…

David Baker 任科学顾问,初创公司发布世界最大蛋白质相互作用数据库,已获 8 轮融资

蛋白质-蛋白质相互作用 (Protein-Protein Interactions, PPI) 是细胞生命活动的重要组成部分&#xff0c;在调控和维持细胞的生理功能中&#xff08;如细胞的信号传导、代谢反应和基因表达&#xff09;发挥着不可或缺的作用。 然而目前 PPl 数据库中的数据相对较少&#xff0c…

穿越病毒区-第15届蓝桥省赛Scratch中级组真题第2题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第187讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

CCF201912_1

题解&#xff1a; #include<bits/stdc.h>using namespace std;int n;bool shouldSkip(int num) {if (num % 7 0){return true;}while (num > 0){if (num % 10 7){return true;}num / 10;}return false; } int main() {scanf("%d", &n);int b[4] { 0…

Android Studio 安装配置教程(Windows最详细版)

目录 前言 Android Studio 下载 Android Studio 安装 Android Studio 使用 一、创建默认项目&#xff08;Compose&#xff09; 二、创建常规项目 三、使用ViewBinding 四、查看Gradle版本、SDK版本、JDK版本 ① Gradle版本 ② SDK版本 ③ JDK版本 前言 Android开发…

跟《经济学人》学英文:2024年09月14日这期 The sweet story of Peru’s blueberry boom

The sweet story of Peru’s blueberry boom Plucky farmers have transformed the market in only ten years plucky&#xff1a;英 [ˈplʌki] 勇敢的&#xff1b;无畏的&#xff1b;有胆识的 原文&#xff1a; Peru’s blueberry harvest is just beginning, and Ivan Ja…

自动驾驶:LQR、ILQR和DDP原理、公式推导以及代码演示(七、CILQR约束条件下的ILQR求解)

&#xff08;七&#xff09;CILQR约束条件下的ILQR求解 CILQR&#xff08;(Constrained Iterative Linear Quadratic Regulator)&#xff09; 是为了在 iLQR 基础上扩展处理控制输入和状态约束的问题。在这种情况下&#xff0c;系统不仅要优化控制输入以最小化代价函数&#x…

NET WPF使用组件库HandyControl

一、背景 WPF原生控件提供的API功能不够强大&#xff0c;设置一般的功能都需要进行很复杂的配置和实现。 1.1 原生按钮控件 例如&#xff0c;原生控件<Button/> 默认效果是这样的&#xff1a; MainWindow.xaml代码&#xff1a; <Window x:Class"wpf_demo.Mai…

SAP_ABAP_编程基础

SAP ABAP 顾问能力模型(同心圆方法论)_sap abap 顾问能力模型(同心圆方法论)-CSDN博客文章浏览阅读1.8k次,点赞5次,收藏35次。目标:基于对SAP abap 顾问能力模型的梳理,给一年左右经验的abaper 快速成长为三年经验提供超级燃料!_sap abap 顾问能力模型(同心圆方法论)htt…

我们的Python服务器开发脚手架开放了

pdServer是我们实践过程中的产物&#xff0c;当我们在开发各类python应用时&#xff0c;经常需要一个server来提供服务。于是我们使用fastApi来提实现&#xff0c;并在这个过程中不断的完善&#xff0c;实现了JWT\SQL等。 我们的脚手架项目可以&#xff1a; fastApi实现一个服…

数据线电子行业MES系统功能与生产过程管理

万界星空科技针对数据线电子行业的MES系统&#xff08;制造执行系统&#xff09;&#xff0c;其具体功能、生产过程管理以及注塑机设备数据采集等方面&#xff0c;可以详细介绍如下&#xff1a; 一、电子行业MES系统具体功能 计划管理&#xff1a; MES系统能够根据客户需求和销…

潍坊网站建设-高端建站

在当今数字化时代&#xff0c;企业的在线形象越来越重要。潍坊作为一个经济发展迅速的城市&#xff0c;许多企业都意识到拥有一个高端网站的重要性。潍坊网站建设不仅仅是技术层面的开发&#xff0c;更是品牌形象和市场竞争力的体现。高端建站的核心在于用户体验、视觉设计和功…

论文解读《NewsBench:一个评估中文新闻大型语言模型编辑能力的系统评估框架》

引言&#xff1a;感觉这篇文章&#xff0c;对 LLMs 的新闻编辑能力做了一个详细的实验和分析&#xff0c;而且还贡献了一个宝贵的中文新闻数据集&#xff0c;蛮不错的&#xff0c;后面或许可以用起来&#xff0c;就拜读了一下。 这篇博客的题目说是解读&#xff0c;其实大部分…

赵进喜:不透析、不用肾移植,“三维护肾”巧治尿毒症

潜心研究中医药治疗尿毒症等慢性肾脏重症40余年来&#xff0c;北京名老中医&#xff0c;慢性肾病国医大师吕仁和教授医术传承人&#xff0c;全国优秀基层名中医赵进喜总结出弥足珍贵的重症良方&#xff0c;临床应用无数次守护近10万肾病重症患者生命。让仅有22岁的慢性肾衰尿毒…

DORIS - DORIS之倒排索引

什么是倒排索引&#xff1f; 倒排索引&#xff08;Inverted index&#xff09;&#xff0c;也常被称为反向索引、置入档案或反向档案&#xff0c;是一种索引方法&#xff0c;用于存储在全文搜索场景下某个单词在一个文档或者一组文档中的存储位置的映射&#xff0c;它是文档检…

AI开发-FAQ

1 需求 2 接口 3.1 pip下载指定版本 pip install package1.0.4 3.2 pip设置代理 pip install --proxyhttp://10.10.1.10:3128 somepackage 3.3 PyCharm设置代理 import os os.environ[http_proxy] http://your_proxy:port os.environ[https_proxy] http://your_proxy:portf…

java开发中间件学习记录(持续更新中~)

1 Redis 2JVM 3 java基础底层 4Mysql 5 spring 6 微服务 7.......(持续更新) One:Redis篇 1:Redis 1.穿透 1.1缓存穿透 1.1.1布隆过滤器 1.2缓存击穿 2&#xff1a;击穿 1.3&#xff1a;缓存雪崩 1.4:双写一致 1.5.持久化&#xff08;RDB,AOF&#xff09; 1.6…

80V降24V2A同步降压WT6037

80V降24V2A同步降压WT6037 WT6037是一款适用于36V-80V输入电压范围&#xff0c;输出24V2A电流的同步降压恒压芯片。该芯片具有宽输入电压范围&#xff0c;高转换效率&#xff0c;低静态电流消耗等特点&#xff0c;可广泛应用于电池组系统&#xff0c;电动自行车&#xff0c;电动…

SD-WAN网络如何实现双向访问?

在SD-WAN架构中&#xff0c;双向访问是一个重要的概念&#xff0c;它允许网络流量在不同方向上流动&#xff0c;从分支机构到数据中心或云端&#xff0c;再从数据中心或云端返回分支机构。本文将详细探讨SD-WAN如何实现双向访问&#xff0c;并解释其背后的工作原理。 什么是双向…