vector类的模拟实现

 实现基本的vector框架

参考的是STL的一些源码,实现的vector也是看起来像是一个简略版的,但是看完能对vector这个类一些接口函数更好的认识。

我们写写成员变量,先来看看STL的成元变量是那些

namespace tjl
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_eof(nullptr)
		{}

	private:
		iterator _start;//
		iterator _finish;
		iterator _eof;//表示的是end_of_storage
	};

}

这里的成员变量有些不一样,我们也用iterator来表示,其实这里就是一个指针,但是用迭代器的名称来称呼它,还有我们这里加上了模板,更加凸显了vector的不一样,这样的好处就是我们这个vector这个类就可以去适应各种不同的类型,达到一个____效果(填一个四字成语)。

实现构造函数

构造函数的意义就是来进行初始化,因为我们构造函数里面都是会走初始化列表的这个过程的,所以我们就可以写成这个样子。

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

但是要注意的是我们的库里面的构造函数可不是只有这些,大家可以也来查询我们的网站来查看

vector构造函数

我们可以看到他是可以支持迭代器进行构造的,也就是说我们这里可以使用任意类型来进行构造

也可以用给值的方式,所以vector的作用还是很大的。
 

析构函数的实现

析构函数的作用就是在我们程序结束的时候对我们的空间进行释放,所以我们可以这样写

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

这里我们可以增加if这个判断,因为_start可能是为空的,所以我们可以写一个对它进行检查的功能。

实现vector的size()和capacity()

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

我们这里的实现细节其实只要注意的是const,因为我们可能是const对象进行调用,所以这里可以加上const,防止我们的权限进行放大。

实现push_back

这里的实现会有点小细节值的我们注意,我们先来想想push_back是在尾部插入数据·就可以了,但是我们可不可能如果当我们的空间是满的时候,就会遇到需要扩容的问题,所以这个时候我们需要先来写一个reserve的函数来进行扩容

reserve函数的实现

因为我们每次扩容的时候到要用到这个函数,比如尾插还是随机插入都会用到,那我们的思路是那些还有注意事项呢?

首先我们得先知道一个问题就是我们当什么时候才是要扩容的时候,因为reserve会传一个参数n

表示我们得开多大的空间,所以我们需要做的时候就是先判断要不要开这么大的空间。

先来看我们的代码

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

			}
		}

思路就是我们重新开辟一块空间,然后把原来空间的内容拷贝到新内容上,这里要注意的是当我们赋值的时候,就是给_start和_finish给值的时候要先记录pos位置,因为我们扩容的时候是重新开辟的,可能存在_finish进行赋值的时候它还是指向空,这样就出现空指针的现象了。

那我们实现reserve之后就可以继续来实现push_back函数了。

void push_back(const T& val)
		{
			if (_finish == _eof)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);

			}
			*_finish = val;
			++_finish;
		}

先检查空间是不是够,如果空间不够我们就需要进行扩容,其次就是大家这里要明白一个点,为什么我们可以对_finish进行解引用,然后直接进行给值,原因就是val的类型。

实现operator[]

这个很简单直接一把过了。

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

用[]进行遍历来看看。

void test1()
{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		for (int i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";

		}
}

迭代器的实现

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

因为迭代器我们必须要给它命名begin和end要不然就不能使用范围for来进行遍历,我们现在可以用迭代器来对我们的数据进行遍历,也能使用范围for,这里两种方式都写出来给大家看看。

void test1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		for (int i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";

		}
		cout << endl;
		for (auto e : v)
		{
			cout << e << " ";
		}
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

这样有三种方式可以对我们进行遍历了,完成迭代器只后需要来处理一些细节问题,还是我们权限放大的问题,因为当我们用const的对象去调用的时候就不行了,这里只需要在写一个const版本的🆗了,我们可以先typedef一下。

typedef const T* const_iterator;

这样就表示这个迭代器是const修饰过的迭代器。那它的begin和end就是这样写的。

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

不过我们对const的迭代器只能进行读的操作就不能进行写操作了。

迭代器失效问题

这个问题是出现在insert和erase的时候是会出现问题

首先我们实现insert需要注意的细节有哪些

和我们之前遇到的顺序表其实本质是没有多大区别的,这里用的是指针,而不是数组下标,因为这里模拟实现的时候,其实我们的迭代器就是原生指针,虽然和VS里的不一样,Vs里的iterator并不是原生指针,所以很好实现我们这里的insert,来看看代码吧。

iterator insert(iterator pos, const T& val = T())
		{
			assert(pos >= begin());
			assert(pos <= end());
			size_t len = pos - _start;
			if (_finish == _eof)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			++_finish;
			return pos;
		}

其实这里我就一步到位了,但是还是有很多的细节值的我们去注意,第一个就是为什么说这里会有迭代器失效的问题,首先就是如果我们步扩容的时候迭代器是不会失效的,但是扩容之后的pos还是指向原来控空间的,因为我们这里的扩容的步骤是开空间,然后对我们的内容进行值拷贝,释放之前的空间,这样pos就是一个野指针,所以我们需要做的就是更新pos位置到新的空间上,可以先记住pos的相对位置,然后更新。所以这里这个坑是没有位置的,还有一个需要注意的是我们需要更新外面的pos,因为形参的改变是不会影响实参的改变的,所以这里的一个重要的步骤就是返回pos的值。

迭代器的失效:第一个重要的原因就是这个指针变成野指针了,我们需要更新它

第二个原因就是当我们进行insert的时候需要进行返回,因为我们在这个函数内改变了,但是在外面还是没有进行改变(形参的改变是不会影响实参的改变的)

我们来进行测试一下

void test2()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		auto pos = find(v.begin(), v.end(), 2);
		v.insert(pos, 100);
		for (auto e : v)
		{
			cout << e << " ";
		}
	}

发现最后的结果也是正确的,这样还要提醒大家,我们认为insert和erase之后的迭代器是失效的,不再使用,虽然我们可以接受pos位置,但是最好还是不要使用。

实现erase

iterator erase(iterator pos)
		{
			assert(pos >= begin());
			assert(pos < end());
			iterator start = pos;
			while (start < _finish)
			{
				*start = *(start + 1);
				start++;
			}
			_finish--;
			return pos;
		}

这个erase也是很简单,就是移动进行数据的覆盖就可以解决问题了。我们也来测试一下看看结果是不是对的。

void test3()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		auto pos = find(v.begin(), v.end(), 2);
		v.erase(pos);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

深浅拷贝问题

先来实现拷贝构造。

拷贝构造的实现可以有现代写法和原始写法,我们先来写原始写法那个,然后引出我们的深浅拷贝的问题。

	vector(const vector<T>& v)
		{
			reserve(v.capacity());
			memcpy(_start, v._start, sizeof(T) * v.size());

		}

我们这里进行的值拷贝,是按照字节的拷贝的,如果我们T是内置类型的时候,我们的的代码是不会出现问题的,但是如果是string或者里面还是一个vector<int>的时候代码就会出现问题,我们可以先来看看string的结果。

void test4()
	{
		vector<string> v;
		v.push_back("111111");
		v.push_back("111111");
		v.push_back("111111");
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		vector<string> v1(v);
		for (auto e : v1)
		{
			cout << e << " ";
		}

	}

我们这样写的时候就是会出现错误,原因是我们这里的拷贝构造是进行的值拷贝,不过如果我们不写这个拷贝构造的时候是不会出现问题的,原因string会去调用它自己的拷贝构造。

但是其根本原因还是我们扩容的是进行的是值拷贝和我们没有写拷贝构造造成的问题。现在来修改一下reserve。

void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t pos = _finish - _start;
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < size(); i++)
					{
						tmp[i] = _start[i];
					}
					delete[]_start;
				}
				_start = tmp;
				_finish = _start + pos;
				_eof = _start + n;

			}
		}

然后加上我们的考拷贝构造。

vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_eof = _start + v.capacity();
		}

这个就是原始的写法,这样我们的代码是没有问题的,但是这里为什么没有问题的第一个原因就是string是我们库里面的,进行赋值的时候会去调用它的赋值重载,但是如果我们这里的类型是vector<vector<int>>又会出现问题,我们可以来看看,如果我们需要拷贝一个杨辉三角的时候,会不会有问题。

下面来演示一下,我们可以先把杨辉三角的代码直接先拿过来

class Solution {
	public:
		vector<vector<int>> generate(int numRows) {
			vector<vector<int>> vv;
			vv.resize(numRows, vector<int>());//进行初始化
			//进行的是每行初始化,因为这里表示的是顺序表里面是个顺序表
			for (int i = 0; i < vv.size(); i++)//初始化没列
			{
				vv[i].resize(i + 1, 0);
				vv[i][0] = vv[i][vv[i].size() - 1] = 1;
			}
			for (int i = 0; i < vv.size(); i++)
			{
				for (int j = 0; j < vv[i].size(); j++)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
					}
				}
			}
			return vv;
		}
	};

然后进行拷贝一个杨辉三角,看看有没有什么问题

void test5()
	{

		vector<vector<int>> v = Solution().generate(5);
		auto v1(v);
		for (int i = 0; i < v1.size(); i++)
		{
			for (int j = 0; j < v1[i].size(); j++)
			{
				cout << v1[i][j] << " ";
			}
			cout << endl;
		}
	}

熟悉的感觉,真是太美妙了 

那我们其实可以通过调试来看看问题所在的地方。

 

可以看到什么不一样的地方,一个就是我们的外面的大vector是完成了深拷贝,里面还是没有,为什么其他类型的string就可以,因为string有它自己的赋值重载,我们这里没有写vector的赋值重载,所以才会有这样的问题,那我们只需要写一个赋值重载就可以解决问题了。

 

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

这样我们的问题就能够很好的解决了。

那我们再来完善一下其他的接口函数就解决了。

pop_back的实现
void pop_back()
		{
			erase(--end());
		}
	template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

完整的代码实现

#pragma once
#include<assert.h>
namespace tjl
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_eof(nullptr)
		{}
		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _eof(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}

		}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_eof, v._eof);
		}
		vector<T>& operator=( vector<T> v)
		{
			swap(v);
			return *this;
		}
		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _eof(nullptr)
		{
			reserve(n);

			while (n--)
			{
				push_back(val);
			}

		}
		template<class inputiterator>
		vector(inputiterator first, inputiterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _eof(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		vector(const vector<T>& v)
			: _start(nullptr)
			, _finish(nullptr)
			, _eof(nullptr)
		{
			reserve(v.capacity());
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_eof = _start + v.capacity();
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t pos = _finish - _start;
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < size(); i++)
					{
						tmp[i] = _start[i];
					}
					delete[]_start;
				}
				_start = tmp;
				_finish = _start + pos;
				_eof = _start + n;

			}
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const T& operator[](size_t pos) const
		{
			return _start[pos];
		}
		T& operator[](size_t pos) 
		{
			return _start[pos];
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else 
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}
		void push_back(const T& val)
		{
			if (_finish == _eof)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);

			}
			*_finish = val;
			++_finish;
		}
		size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _eof - _start;
		}
		iterator insert(iterator pos, const T& val = T())
		{
			assert(pos >= begin());
			assert(pos <= end());
			size_t len = pos - _start;
			if (_finish == _eof)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			++_finish;
			return pos;
		}
		iterator erase(iterator pos)
		{
			assert(pos >= begin());
			assert(pos < end());
			iterator start = pos;
			while (start < _finish)
			{
				*start = *(start + 1);
				start++;
			}
			_finish--;
			return pos;
		}

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

	
	private:
		iterator _start;//
		iterator _finish;
		iterator _eof;//表示的是end_of_storage
	};

	class Solution {
	public:
		vector<vector<int>> generate(int numRows) {
			vector<vector<int>> vv;
			vv.resize(numRows, vector<int>());//进行初始化
			//进行的是每行初始化,因为这里表示的是顺序表里面是个顺序表
			for (int i = 0; i < vv.size(); i++)//初始化没列
			{
				vv[i].resize(i + 1, 0);
				vv[i][0] = vv[i][vv[i].size() - 1] = 1;
			}
			for (int i = 0; i < vv.size(); i++)
			{
				for (int j = 0; j < vv[i].size(); j++)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
					}
				}
			}
			return vv;
		}
	};
	void test1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		for (int i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";

		}
		cout << endl;
		for (auto e : v)
		{
			cout << e << " ";
		}
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

	void test2()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		auto pos = find(v.begin(), v.end(), 2);
		v.insert(pos, 100);
		for (auto e : v)
		{
			cout << e << " ";
		}
	}

	void test3()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		auto pos = find(v.begin(), v.end(), 2);
		v.erase(pos);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test4()
	{
		vector<string> v;
		v.push_back("111111");
		v.push_back("111111");
		v.push_back("111111");
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		vector<string> v1(v);
		for (auto e : v1)
		{
			cout << e << " ";
		}

	}

	void test5()
	{

		vector<vector<int>> v = Solution().generate(5);
		auto v1(v);
		for (int i = 0; i < v1.size(); i++)
		{
			for (int j = 0; j < v1[i].size(); j++)
			{
				cout << v1[i][j] << " ";
			}
			cout << endl;
		}
	}

}

今天的分享就到这里了,我们下次再见!!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言|数据结构】数据结构顺序表

目录 一、数据结构 1.1概念 1.2总结 1.3为什么需要数据结构&#xff1f; 二、顺序表 1.顺序表的概念及结构 1.1线性表 2.顺序表分类 2.1顺序表和数组的区别 2.2顺序表的分类 2.2.1静态顺序表 2.2.1.1概念 2.2.1.2缺陷 2.2.2动态顺序表 三、动态顺序表的实现 3.1新…

Pandas文本数据处理技术指南—从查找到时间序列分析【第66篇—python:文本数据处理】

文章目录 Pandas文本数据处理技术指南引言 1. 查找文本数据2. 替换文本数据3. 拼接文本数据4. 正则表达式操作5. 虚拟变量6. 处理缺失值7. 分割文本数据8. 字符串处理方法9. 文本数据的合并与连接10. 文本数据的排序11. 文本数据的统计分析12. 文本数据的分组与聚合13. 文本数据…

使用Softing edgeConnector模块将云轻松连接到Siemens PLC

一 工业边缘的连接解决方案 云服务提供商 (CSP) 引入了服务和功能&#xff0c;以简化基于云的工业物联网解决方案的实施。Azure Industrial IoT Platform或AWS IoT SiteWise支持标准协议和接口&#xff0c;例如OPC UA或MQTT。但是&#xff0c;如果您希望在典型的旧改项目中连接…

【代理模式】

定义&#xff1a;代理模式是一种结构型设计模式&#xff0c;它允许我们创建一个代理对象&#xff0c;用于控制对另一个对象的访问。 代理对象充当了被代理对象&#xff08;目标对象&#xff09;的代表&#xff0c;与被代理对象实现相同的接口&#xff0c;从而实现对被代理对象…

【PowerShell】修改Windows网络配置的常用命令

PowerShell&#xff08;PS&#xff09;是一种强大的任务自动化和管理框架&#xff0c;具有丰富的命令和语法&#xff0c;可以用于编写脚本来管理Windows操作系统和其他应用程序。它的开放式架构和跨平台支持使得它成为一个灵活和可扩展的工具。 在网络配置方面&#xff0c;Powe…

C++ 日期计算器

日期计算器 概要 Date类的规划Date类的实现Date 构造函数Date 拷贝构造函数~Date 析构函数GetMonthDay 求某年某月的天数operator 赋值操作符重载operator 加等操作符重载operator 加号操作符重载operator- 减等操作符重载operator- 减法操作符重载 &#xff08;日期 - 天数&am…

分享66个行业PPT,总有一款适合您

分享66个行业PPT&#xff0c;总有一款适合您 66个行业PPT下载链接&#xff1a;https://pan.baidu.com/s/1kcUOfR_xtH9CAJC12prcTw?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知…

算法学习——华为机考题库3(HJ21 - HJ25)

算法学习——华为机考题库3&#xff08;HJ21 - HJ30&#xff09; HJ21 简单密码 描述 现在有一种密码变换算法。 九键手机键盘上的数字与字母的对应&#xff1a; 1–1&#xff0c; abc–2, def–3, ghi–4, jkl–5, mno–6, pqrs–7, tuv–8 wxyz–9, 0–0&#xff0c;把密码…

Swift Combine 发布者订阅者操作者 从入门到精通二

Combine 系列 Swift Combine 从入门到精通一 1. Combine核心概念 你只需要了解几个核心概念&#xff0c;就能使用好 Combine&#xff0c;但理解它们非常重要。 这些概念中的每一个都通过通用协议反映在框架中&#xff0c;以将概念转化为预期的功能。 这些核心概念是&#x…

Cocos creator 3.x 刚体组件碰撞无效

Cocos creator 3.x 刚体组件碰撞无效 问题描述&#xff1a;只有一个circleCollider2D时&#xff0c;可以在碰撞时正确输出结果&#xff0c;但是当我在外围加了一个circle之后&#xff0c;期望character进入圆圈范围时就触发方法&#xff0c;此时原代码失效 import { _decorat…

简单说网络:TCP+UDP

TCP和UPD: (1)都工作在传输层 (2)目的都是在程序之中传输数据 (3)数据可以是文本、视频或者图片(对TCP和UDP来说都是一堆二进制数没有太大区别) 一、区别:一个基于连接一个基于非连接 将人与人之间的通信比喻为进程和进程之前的通信:基本上有两种方式(1)写信;(2)打电话;这…

【51单片机】实现一个动静态数码管显示项目(前置知识铺垫,代码&图演示)(5)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY…

Redis的数据类型Hash使用场景实战

Redis的数据类型Hash使用场景 常见面试题&#xff1a;redis在你们项目中是怎么用的&#xff0c;除了String数据类型还使用什么数据类型&#xff1f; 怎么保证缓存和数据一致性等问题… Hash模型使用场景 知识回顾&#xff1a; redisTemplate.opsForHash() 方法是 Redis 的 …

QAnything之BCEmbedding技术路线

QAnything和BCEmbedding简介 QAnything[github]是网易有道开源的检索增强生成式应用&#xff08;RAG&#xff09;项目&#xff0c;在有道许多商业产品实践中已经积累丰富的经验&#xff0c;比如有道速读和有道翻译。QAnything是一个支持任意格式文件或数据库的本地知识库问答系…

python的数据类型

&#x1f388;srting&#xff08;字符串&#xff09;&#xff1a; 操作符&#xff1a; &#xff1a;字符串连接 aabc befg print(ab) #输出 abcdefg * : 重复输出字符串 aabc print(a*3) #输出 abcabcabc [ : ]:截取字符串中的一部分&#xff0c;遵循左闭右开的原则&am…

vue实现购物车案例

话不多说&#xff0c;先上效果图。 安装elementui组件库&#xff0c;可直接食用。 <template><div><!-- 购物车部分 --><el-container><el-header><h1>购物车案例一条龙</h1></el-header><el-main><!-- 折叠面板…

springboot Feign方式注入注解详解

一、FeignClient注解详解 FeignClient是Spring Cloud中用于声明Feign客户端的注解&#xff0c;它使得编写HTTP客户端变得更简单。通过Feign的自动化配置机制&#xff0c;可以很容易地编写HTTP API客户端。以下是FeignClient的详解&#xff1a; 作用&#xff1a;FeignClient注解…

龙年立 Flag,Whale 帷幄 2024 的五大关键词

回顾 2023&#xff0c;AIGC 浪潮的出现&#xff0c;为各行各业带来了更多的商业可能性。在农历新年到来之际&#xff0c;我们也展望 2024&#xff0c;为打好新的硬仗做好充分的准备。 以下 5 大关键词即是「Whale 帷幄」接下来努力的方向和目标。 「盈利」 在 2024 年&#xff…

骨传导运动蓝牙耳机哪个好?五款性价比骨传导运动蓝牙耳机推荐

近两年来&#xff0c;骨传导运动蓝牙耳机在运动领域内日益流行。与传统耳机相比&#xff0c;它的显著优势是能够保持双耳开放&#xff0c;不会堵塞耳道&#xff0c;消除了入耳式耳机可能引起的不适感。此外还能避免运动时耳内出汗可能导致的各种卫生和健康问题。很多人就问了&a…