C++《vector的模拟实现》

news2024/10/27 23:34:58

在之前《vector》章节当中我们学习了STL当中的vector基本的使用方法,了解了vector当中各个函数该如何使用,在学习当中我们发现了vector许多函数的使用是和我们之前学习过的string类的,但同时也发现vector当中一些函数以及接口是和string不同的。那么接下来在本篇我们就将试着模拟实现vector,在模拟实现过程当中我们就会了解当vector当中迭代器失效的问题,相信在本篇之后你会对vector有更深的了解,接下来就开始本篇的学习吧!!!


 1.实现各个函数之前的工作 

在模拟实现我们先要解决在模拟实现vector过程文件该如何创建

在vector由于是要是要使用到类模板来实现,因此在实现就和实现string不同不再将vector成员函数的声明和定义分离到两个文件,因此在此就创建一个头文件vecor.h来存放类vector的实现,在创建一个.cpp文件test.cpp来测试在vector当中实现的各个成员函数是否满足要求以及是否能正常运行。

注:在此为了避免我们实现的vector和std命名空间当中的vector冲突,因此就先新创建一个命名空间,之后将我们模拟实现的vector类放在创建的命名空间内,这样就不会出现冲突了

完成了文件的创建之后接下来就来分析在我们模拟实现的类vector当中成员变量该如何创建

在此你可能就会直接认为由于vector就是顺序表,那么就直接和之前在数据结构当中实现顺序表一样使用三个变量来实现即可,第一个变量就是指向数组的指针;第二个变量就是底层数组的有效元素个数;最后一个变量就是数组的内存空间大小


以上你这种也是可以实现的,但是在通过vector源码就可以发现在实现vector时,vector当中的成员变量是三个迭代器,这时你可能就会疑惑这三个迭代器分别表示的是什么呢?

其实在以上三个迭代器当中start指向的是vector对象内第一个元素,finish指向的是vector对象当中最后一个有效元素之后的位置,end_of_storage指向的是vector对象当中最后内存空间之后的位置。并且在此我们还要了解到由于vector的底层是用数组来实现的那么各个元素在物理逻辑上就是连续存放的,那么在vector当中迭代器底层就可以直接使用对应的指针来实现。那么这时将finish减start就可以得到有效元素,将end_of_storage减finish就可以此时底层数组的内存空间大小

因此通过以上的分析就可以发现使用vector源码当中的成员变量这种形式也是可以满足我们的要求的,那么接下来在模拟实现vector当中也按照源码的这种方式来实现成员变量

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

using namespace std;

namespace zhz
{
	template<class T>
	class vector
	{
	public:
		//成员函数. . .


	private:
		T* _start = nullptr;
		T* _finish = nullptr;
		T* _endofstorage = nullptr;

	};

}

解决了以上的问题接下来就可以开始实现vector当中的各个成员函数了

2.vector模拟实现

在模拟实现vector我们是先实现插入和删除的函数,这时因为在构造函数当中通过调用插入的函数就可以实现各个接口的构造函数,这样就不需要我们自己来实现插入了这就让构造函数的代码写起来简单多了

2.1 size和capacity

在以上我们就分析出了在vector当中该如何来得到size与capacity,因此接下来就直接实现代码

在vector.h内实现函数的代码

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

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

2.2 下标+[ ]访问

在vector由于各个元素之间的物理空间是连续的,那么就可以实现[ ]的运算符重载函数,在此要实现const与非const对象的两个版本

在vector.h内实现函数的代码

T& operator[](size_t i)
{
	assert(i < size());
	return _start[i];
}
const T& operator[](size_t i)const
{
	assert(i < size());
	return _start[i];
}

注:在此在使用[ ]来访问vector对象内的元素不能超出对象内底层数组的大小,因此在此在实现 [ ]的运算符重载函数就需要使用assert断言

2.3 迭代器

在vector当中迭代器的实现直接使用指针来使用就可以满足要求,在此vector的迭代器就是原生态指针T*, 因此在此在实现begin和end函数之前先要将指针重命名为迭代器

typedef T* iterator;//普通迭代器
typedef const T* const_itreator;//const迭代器

完成以上操作就可以来实现begin和end函数的代码了

在vector.h内实现函数的代码

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}
//const迭代器
const_itreator begin()const
{
	return _start;
}
const_itreator end()const
{
	return _finish;
}

2.4 reserve和resize

通过之前的学习我们了解过了reverse是用于调整内存空间的大小,resize是用于调整有效元素,那么接下来我们就试着来实现这两个函数

先在vector.h内实现reserve函数的代码

在此和之前string实现一样只有当指定的大小比当前的空间大时才进行调整,这时要将原内存空间调整为大小为n的内存空间就需要先申请大小为n个变量的空间之后再将原空间内的数据拷贝到新申请的内存空间内,再将原空间释放。最后将类成员变量的指向改变就完成了

这时reserve实现出的代码就如下所示:

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

以上的代码看起来完全可以满足我们的要求,但是其实存在两个严重的问题,你能看出来吗?

 首先是在以上代码中我们将_start指向tmp指向的内存空间后,_finish = _start + size()这时在这条语句其实调用size()函数就有问题了,这时在进入size()函数之后由于_start已经指向新的内存空间但是_finish还是指向原空间,再将这两个指针相减就无法得到有效元素个数,并且会导致程序奔溃

那么要解决以上这个问题就先再改变_start之前创建一个变量oldsize将原来的有效元素个数存储在该变量内,之后_finish = _start + size()修改为_finish = _start + oldsize就可以解决该问题

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

以上代码其实还存在一个问题就是memcpy进行的是浅拷贝,这在T是内置类型时确实没什么问题,但我们实现的vector是类模板那么就是为了也能满足vector内的元素是自定义类型这种情况,那么这时再进行浅拷贝就会在使用delete出现问题
就例如当vector对象内的元素是string类型时

在使用完memcpy之后就会使得原内存空间内的string对象内的指针和新空间内string对象对象的指针指向同一块空间,这在调用delete将原空间释放时由于会想调用string的析构函数将string对象内指针指向的空间释放,那么在这之后就会使得在新空间内的string对象内的指针变为空指针 

那么要解决以上这个问题就要再拷贝时进行深拷贝而不是浅拷贝

void reserve(int n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		size_t oldsize = size();
		//memcpy(tmp, _start, sizeof(T) * size());//memcpy完成的是浅拷贝
		for (int i = 0; i < oldsize; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;
		_start = tmp;
		_finish = _start + oldsize;
		_endofstorage = _start + n;
	}
}

完成了reserve函数之后接下来在vector.h内实现resize函数的代码

在此在resize函数当中要分为两种情况一种是当要调整的大小比原有效元素个数小时就直接改变_finish指针指向即可,但当要调整的大小比原有效元素个数大时就要从_finish指针开始插入新的元素;并且在最后还要改变_finish指针指向

这时resize实现出的代码就如下所示:

void resize(size_t n,T x=T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{	
		reserve(n);//当n比原内存空间还大时,避免多次扩容
		while (_finish < _start + n)
		{
			*_finish = x;
			_finish++;
		}
		
	}
}

注:在以上代码在插入元素后使用缺少参数T(),在此使用到了匿名对象,这时由于类vector是类模板那么变量x的类型就是不确定的,因此就不能直接将变量的缺省值使用0或者nullptr来实现。因此在此就可以使用到之前在类和对象当中学到的匿名对象来实现这个缺省值

2.5 insert和erase

通过之前的学习我们知道insert和erase是分别实现任意位置的插入与删除,并且在vector当中这两个函数的接口都是迭代器

先在vector.h内实现reserve函数的代码

在insert函数当中要实现的是在pos迭代器位置插入指定的数据,在此在插入数据之前要先检查空间是否已经满了,如果满了就要先进行扩容,并且在此在vector当中我们是使用指针来实现遍历的这就不会出现之前我们在string当中实现insert使用下标时的问题

void insert(iterator pos, const T& x)
{
	assert(pos >= _start && pos <= _finish);//判断pos指针是否合法
	if (_finish == _endofstorage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容
	}
	iterator i = _finish -1;
	while (pos <= i)
	{
		*(i + 1) =*i;
		i--;
	}
	*pos = x;
	_finish++;

}

在以上我们就实现了insert的代码,但是以上代码其实存在迭代器失效的问题,首先在解决以上问题之前我们先要来了解什么是迭代器失效

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

在了解了什么是迭代器失效之后接下来就可以来分析为什么以上的代码存在迭代器失效的问题了

在插入前当内存空间不足时也就是_finish = _endofstorage时就需要先进行扩容,那么在调用reserve之后_start就指向新的内存空间,但问题是此时指针pos还指向原来的内存空间,但是原内存空间内的数据已经被释放,此时pos指针就变为空指针,所以之后的将要插入位置之后的数据都往后移动一位时就会造成程序奔溃。

所以要解决以上pos迭代器失效的问题就要在扩容之后更新迭代器,并且将该函数的返回值为新插入位置的迭代器

iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start && pos <= _finish);//判断pos指针是否合法
	if (_finish == _endofstorage)
	{
	    size_t len = pos - _start;//记录pos迭代器位置
		reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容
		pos = _start + len;//更新pos迭代器
	}
	iterator i = _finish;
	while (pos <= i)
	{
		*(i + 1) =*i;
		i--;
	}
	*pos = x;
	_finish++;
	return pos;
}

完成了insert函数之后接下来在vector.h内实现erase函数的代码

在此erase函数就不会出现insert当中pos指针为空的情况,因此你可能就会直接实现出以下代码

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

但其实迭代器失效除了包括扩容引起的野指针,其实还包括删除数据,导致数据挪动,原本的迭代器已经不是指向之前的位置了,这时该迭代器其实野失效了,这是因为之后再使用原来的迭代器可能会导致逻辑问题

就例如以下示例:

#include <iostream>
using namespace std;
#include <vector>

int main()
{
	int a[] = { 1, 2, 3, 4 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));
	// 使用find查找3所在位置的iterator
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据,导致pos迭代器失效。
	v.erase(pos);
	cout << *pos << endl; // 此处会导致非法访问
	return 0;
}

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理
论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end
的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素
时,vs就认为该位置迭代器失效了。

再来看以下代码,以下代码的功能是删除vector中所有的偶数,请问那个代码是正确的,为什么?

#include <iostream>
using namespace std;
#include <vector>
int main()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}

	return 0;
}
int main()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			it = v.erase(it);
		else
			++it;
	}
	return 0;
}

以上两段代码再VS下只有第二段代码是能通过的,第一段代码会由于迭代器失效直接报错。而在Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端,所以以上两段代码都能实现要求

那么在VS当中为什么在出现使用失效的迭代器就直接报错呢?
这是由于在一些情况下使用失效的迭代器会造成程序奔溃,就例如将以上数组v内数据改为以下时

vector<int> v{ 1, 2, 2, 3, 4 , 5, 2 };

这时再将以上第一段代码中在将数组中的第一个2删除之后迭代器it就指向了3,这就会使得数组中的第二个2不会被删除,并且在删除最后的2时这时由于进入erase函数之后只会让_finish--,这时删除完2之后继续it++,在此之后it就一直不会等于_finish,所以while就会死循环,造成越界。这种情况下在Linux下也会出现错误

通过以上的分析就知道为什么VS下一旦出现了使用失效的迭代器就报错,那么为了避免使用失效的迭代器,在我们模拟实现的erase也要作出修改

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

在以上代码将pos迭代器之后位置的迭代器作为erase的返回值,这样在调用了erase之后就可以通过其返回值来更新迭代器

2.6 push_back与pop_back

在以上我们实现insert和erase函数之后,在实现尾插push_back和尾删pop_back函数可以直接通过调用insert和erase来实现

void push_back(const T& x)
{
	insert(end(), x);
}


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

void pop_back()
{
    assert(!Empty());
	erase(end()-1);
}

2.7 swap

在vector当中实现swap就可以避免在调用swap时调用到算法库内的swap从而导致深拷贝,而在我们在vector内实现的swap只需要交换指针即可实现vector对象的交换

void swap(vector<T>& x)
{
	std::swap(_start, x._start);
	std::swap(_finish, x._finish);
	std::swap(_endofstorage, x._endofstorage);
}

2.7 构造函数

在以上实现了插入函数之后接下来实现vector构造函数的各个接口就很简单了

//空构造
vector()
{

}

//使用迭代器区间构造
template<class InputIterator>
vector(InputIterator fist, InputIterator last)
{
	while (fist != last)
	{
		push_back(*fist);
		fist++;
	}
}

//调用initializer_list构造
vector(initializer_list<T> t1)
{
	reserve(t1.size());
	for (auto& x: t1)
	{
		push_back(x);
	}

}

vector(int n, const T& x = T())
{
	reserve(n);
	while (n--)
	{
		push_back(x);
	}
}


//n个x构造
vector(size_t n, const T& x = T())
{
	reserve(n);
	while(n--)
	{
		push_back(x);
	} 
}



//拷贝构造
vector(const vector<T>& x)
{
	reserve(x.capacity());
	for (auto& e : x)
	{
		push_back(e);
	}
}

 在以上构造函数中在实现n个变量构造时为什么还要提供以下接口呢?

这是因为当vector对象类型是int时,在使用原本的n个变量构造时由于迭代器区间构造参数类型会比n个变量构造的参数类型更加匹配,这时就构造会调用到迭代器区间构造。因此实现以上函数就是为了避免这个问题

2.8 析构函数

在vector当中析构函数的作用是在对象清除时完成资源清理的工作,又因为在vector当中底层的资源就是数组,那么析构函数内要实现的就是将start指向的内存空间释放

完成了分析接下来就在vector.h内实现函数的代码

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

2.9 赋值运算符重载

在vector赋值运算符重载函数和string中一样可以直接使用以下的现代写法实现

vector<int> operator=(vector<T> x)
{
	swap(x);
	return *this;
}

以上就是本篇的所有内容了,希望能得到你的点赞、收藏❤️

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

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

相关文章

【纯血鸿蒙】HarmonyOS和OpenHarmony 的区别

一、开源鸿蒙&#xff08;Open Harmony&#xff09; 鸿蒙系统愿来的设计初衷&#xff0c;就是让所有设备都可以运行一个系统&#xff0c;但是每个设备的运算能力和功能都不同&#xff0c;所以内核的设计上&#xff0c;采用了微内核的设计&#xff0c;除了最基础的功能放在内核…

云岚到家 即刻体检 优惠卷管理 总结不熟练的点

多个条件的分页查询 public PageResult<ActivityInfoResDTO> queryPage(ActivityQueryForPageReqDTO reqDTO) {Page<Activity> pagequery new Page<>(reqDTO.getPageNo(),reqDTO.getPageSize());LambdaQueryWrapper<Activity> wrapper new LambdaQ…

C++ | Leetcode C++题解之第500题键盘行

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<string> findWords(vector<string>& words) {vector<string> ans;string rowIdx "12210111011122000010020202";for (auto & word : words) {bool isValid true;cha…

数组实例之三子棋的实现(C语言)

目录 前言 一、三子棋实现的逻辑 二、三子棋的实现 2.1文件的创建添加 2.2 test文件基本逻辑 2.2.1菜单的实现 2.2.2菜单的选择 2.2.3game函数棋盘的实现 2.3game.c文件的编写 2.3.1初始化函数的模块 2.3.2棋盘打印的模块 2.3.3实现棋盘界面的打印 2.3.4实现玩家下…

【论文阅读】Reliable, Adaptable, and Attributable Language Models with Retrieval

文章目录 OverviewCurrent Retrieval-Augmented LMsArchitectureTraining Limitations & Future Work Overview Parametic language models的缺点&#xff1a; 事实性错误的普遍存在验证的难度&#xff08;可溯源性差&#xff09;难以在有顾虑的情况下排除某些序列适应调整…

Unity插件-Intense TPS 讲解

目录 关于TPS 打开场景&#xff1a;WeaponTest.unity&#xff0c; 只要把这些枪点&#xff0c;打开&#xff08;默认隐藏&#xff0c;不知道为何), 一开始不能运行如何修复 总结 关于TPS 个人不是TPS&#xff0c;FPS的射击游戏爱好者&#xff0c; 不过感觉这个枪感&…

Linux系统中使用yum命令详细安装MariaDB数据库的步骤

前言 mysql和mariadb的区别 MySQL&#xff1a;最初由瑞典的MySQL AB开发&#xff0c;后被Sun Microsystems收购&#xff0c;最终被Oracle收购。MariaDB&#xff1a;由MySQL的原始开发者Michael Widenius领导的团队开发&#xff0c;目标是保持与MySQL的兼容性&#xff0c;并提…

部署前后端分离若依项目--CentOS7宝塔版

准备&#xff1a; CentOS7服务器一台 通过网盘分享的文件&#xff1a;CentOS 7 h 链接: https://pan.baidu.com/s/17DF8eRSSDuj9VeqselGa_Q 提取码: s7x4 大家有需要可以下载这个&#xff0c;密码61 若依前端编译后文件 通过网盘分享的文件&#xff1a;ruoyi-admin.jar 链…

【K8S】快速入门Kubernetes

之前企业都是使用容器化和来构建自己的服务和应用程序&#xff0c;其中容器化优点有很多&#xff1a;提升了部署效率、稳定性、提高了资源的利用率降低了成本。 但是也带来了一些新的问题&#xff1a;容器的数量变得很多&#xff0c;管理就是一个新的问题。所以Kubernetes就出…

【K8S系列】Kubernetes 中 Service IP 地址和端口不匹配问题及解决方案【已解决】

在 Kubernetes 中&#xff0c;Service 是实现 Pod 之间和 Pod 与外部之间通信的关键组件。Service 的 IP 地址和端口配置不当可能导致应用无法正常访问。本文将详细分析 Service IP 地址和端口不匹配的问题&#xff0c;常见原因及其解决方案。 一、问题描述 Service IP 地址和…

草稿1111

服务端&#xff1a; 启动nfs服务器服务和rpcbind 服务&#xff1a; 写配置文件&#xff1a; 重新导出所有当前已导出的文件系统&#xff1a; 这个命令会立即停止 firewalld 服务&#xff0c;并将其设置为在系统启动时不自动启动&#xff1a; 客户端&#xff1a; 启动nfs服务器…

练习LabVIEW第十九题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第十九题&#xff1a; 创建一个程序把另外一个VI的前面板显示在Picture控件中 开始编写&#xff1a; 在前面板放置一个二…

银河麒麟V10通过tigervnc实现远程桌面和windows系统连接

1、查看系统版本:uname -a Linux localhost.localdomain 4.19.90-89.16.v2401.ky10.x86_64 #1 SMP Sat Sep 14 13:09:47 CST 2024 x86_64 x86_64 x86_64 GNU/Linux 2、查看是否具有桌面环境:yum grouplist 安装VNC需要具有桌面环境 3.、安装tigervnc: yum install tigervnc…

C++——string的模拟实现(上)

目录 引言 成员变量 1.基本框架 成员函数 1.构造函数和析构函数 2.拷贝构造函数 3.容量操作函数 3.1 有效长度和容量大小 3.2 容量操作 3.3 访问操作 (1)operator[]函数 (2)iterator迭代器 3.4 修改操作 (1)push_back()和append() (2)operator函数 引言 在 C—…

GoFly快速开发框架已集成了RTSP流媒体服务器(直播、录播)代码插件-开发者集成流媒体服务器更加方便

温馨提示&#xff1a;我们分享的文章是给需要的人&#xff0c;不需要的人请绕过&#xff0c;文明浏览&#xff0c;误恶语伤人&#xff01; 说明 本代码包是音视频流媒体服务器&#xff0c;支持RTSP推流到服务器、服务端录像与回放。在视频直播、安防及车辆监控视频上云、无人…

解读AVL树:平衡二叉搜索树的奥秘

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C&#xff1a;由浅入深篇 小新的主页&#xff1a;编程版小新-CSDN博客 前言&#xff1a; 前面我们已经介绍了二叉搜…

编译却出现Error: C9555E: Failed to check out a license.问题解决

学stm32的时候&#xff0c;照着操作突然发现编译报错。 去网上搜索答案的时候发现了这篇文章&#xff1a;参考博客:KEI5许可证没到期&#xff0c;编译却出现Error: C9555E: Failed to check out a license.问题解决 根据网上找的资料发现应该是版本问题导致的。在上一篇博客…

Prompt提示词设计:如何让你的AI对话更智能?

Prompt设计&#xff1a;如何让你的AI对话更智能&#xff1f; 在人工智能的世界里&#xff0c;Prompt&#xff08;提示词&#xff09;就像是一把钥匙&#xff0c;能够解锁AI的潜力&#xff0c;让它更好地理解和响应你的需求。今天&#xff0c;我们就来聊聊如何通过精心设计的Pr…

【纯血鸿蒙】安装hdc工具

这里我先写Mac版的,Windows的在下面 首先要知道你的SDK安装在哪里了,不知道的话,可以打开DevEco Studio,打开设置页面里的HarmonyOS SDK,这个我们之前配置环境变量的时候用过。 其实主要是用到这里toolchains下的hdc命令。 所以我们需要配置环境变量。 1、打开Mac下的…

关于OpenFeign中@GetMapping不支持Java对象传递

原因&#xff1a;在使用openfeign远程调用的时候&#xff0c;使用get请求时候发现&#xff0c;如果feign里面传的是实体&#xff0c;当然接口里面也是需要传实体&#xff0c;但是发现出错了&#xff0c;后来去官网找了一下&#xff0c;发现get请求不支持传入Java实体对象&#…