C++ STL vector 模拟实现

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:C++之STL
🔥<3>创作者:我的代码爱吃辣
☂️<4>开发环境:Visual Studio 2022
💬<5>前言:上次我们已经数字会用了vector,这次我们对其底层更深一步挖掘,其中重点是,Vector中一些深浅拷贝问题。

目录

一.Vector模拟实现的整体框架

二. Vector的构造与析构

三.size(),capacity()

 四.reserve(),resize()

1.reserve()

2.resize

五.push_back(),pop_back()

1.push_back()

2. pop_back()

六.Vector的迭代器

 七.operator [ ]

 八.insert(),erase()

1.迭代器失效

2.insert()

3.erase()

九.再看Vector构造函数

十.拷贝构造

1.深浅拷贝

2.正确的拷贝构造代码:

3.正确的 reserve()

4.赋值运算符重载

十一.总体代码


一.Vector模拟实现的整体框架

我们先认识一下Vector的整体模拟实现框架,Vector在功能上就是我们数据结构阶段实现的顺序表基本一致,但是Vector在成员框架上与顺序表有所不同,且Vector使用类和对象封装支持模板泛型。

template<class T>
class Vector
{
public:
    //迭代器类型
	typedef T* iterator;
	typedef const T* const_iterator;

    //...

private:
	iterator _start;//数据存储首地址
	iterator _finish;//有效数据尾部地址下一个地址。
	iterator _end_of_storage;//容量尾地址下一个地址
};

Vector的迭代器是对顺序表原生类型指针的封装。

二. Vector的构造与析构

Vector主要是对成员变量初始化:

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

析构主要释放我们申请的空间:

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

三.size(),capacity()

size()返回当前顺序表存储的数据个数,capacity()返回当前顺序表的容量。

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

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

 四.reserve(),resize()

1.reserve()

设置Vector的容量,注意容量支持增加,但是不支持减小。

void reserve(size_t capa)
	{
		//仅支持容量扩大,不支持容量减小
		if (capacity() < capa)
		{
			size_t sz = size();
			iterator tmp = new T[capa];
			//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,
			//如果之前没有容量仅需,将新开的空间指向我们的_start.
			if (_start)
			{
				memcpy(tmp, _start, sizeof(T) * capacity()); /*error !!*/
				delete[] _start;
			}
			//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,
			//然后计算的size也并非是,准确的size。
			_start = tmp;
			_finish = tmp + sz;
			_end_of_storage = _start + capa;
		}
	}

注意:

此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,然后计算的size也并非是,准确的size。除此之外这份代码依旧是有问题的。我们后面解释。

错误代码如下:

    void reserve(size_t capa)
    {
		if (capacity() < capa)
		{
			iterator tmp = new T[capa];
			if (_start)
			{
				memcpy(tmp, _start, sizeof(T) * capacity());
				delete[] _start;
			}
			//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,
			//然后计算的size也并非是,准确的size。
			_start = tmp;
			_finish = tmp + size();
			_end_of_storage = _start + capa;
		}
	}

2.resize

提供改变存储数据的个数的能力。如果 n < size 时就是删除数据,n > size且空间不够时需要扩容+初始化,空间足够,仅需要初始化剩下的空间。

    void resize(size_t n, T val = T())
	{	
		//1.n < size;-->删除数据
		if (n < size())
		{
			_finish = _start + n;
		}
		//2.n > size
		else 
		{
			//(1)如果空间不足,需要扩容+初始化
			if (n >= capacity())
			{
				reserve(n);
			}
			//(2)空间足够,仅需要初始化剩下的空间
			while (_finish != _start + n)
			{
				*(_finish) = val;
				_finish++;
			}
		}
	}

五.push_back(),pop_back()

1.push_back()

从尾部插入一个数据。

	void push_back(const T& val)
	{
        //检查是否需要扩容
		if (_finish == _end_of_storage)
		{
			capacity() == 0 ? reserve(5) : reserve(capacity() * 2);
		}
        //插入数据
		*(_finish) = val;
		_finish++;
	}

2. pop_back()

	bool empty() const 
	{
		return size() == 0;
	}

	void pop_back()
	{
		//判空
		assert(!empty());
		//我们仅需将维护尾部数据的指针向前挪一位。
		_finish--;
	}

六.Vector的迭代器

	typedef T* iterator;
	typedef const T* const_iterator;

Vector底层就是顺序存储的结构,所以可以使用原生指针作为迭代器。

	//普通迭代器
	iterator begin()
	{
		return _start;
	}
	iterator end()
	{
		return _finish;
	}

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

有了迭代器就可以支持迭代器访问,和范围for。

int main()
{
	Vector<int> v1;
	v1.push_back(100);
	v1.push_back(200);
	v1.push_back(300);
	v1.push_back(400);
	v1.push_back(500);
	v1.push_back(600);
	v1.push_back(700);

	Vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	for (auto e : v1)
	{
		cout << e << " ";
	}

	return 0;
}

 七.operator [ ]

	//穿引用返回
	T& operator[](size_t pos)
	{
		//判断位置的合法性
		assert(pos < size());
		return _start[pos];
	}

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

 八.insert(),erase()

1.迭代器失效

在模拟实现之前我们先看一下什么是迭代器失效问题:

迭代器失效问题通常发生在容器类的成员函数中,例如erase和insert。在这些函数中,迭代器被重置或修改,导致原始迭代器不再指向容器的正确位置,从而导致迭代器失效。

int main()
{
	vector<int> v1;
	v1.push_back(100);
	v1.push_back(200);
	v1.push_back(300);
	v1.push_back(400);
	v1.push_back(500);
	v1.push_back(600);
	v1.push_back(700);  
	vector<int>::iterator pos = find(v1.begin(), v1.end(),200);
	//对pos位置插入
	v1.insert(pos, 150);
	//pos已经失效
	v1.insert(pos, 170);
 
      return 0;
}

原理图:

情况一:

 上述代码中的迭代器失效问题也是属于这种情况。

情况二:

2.insert()

    iterator insert(iterator pos,T val)
	{
		assert(pos >= _start);
		assert(pos < _finish);
		//迭代器失效问题,记录pos的相对位置
		int len = pos - _start;
		if (_finish == _end_of_storage)
		{
			capacity() == 0 ? reserve(5) : reserve(capacity() * 2);
		}
		//扩容后重新计算pos,没有发生扩容pos不变
		pos = _start + len;
		iterator end = _finish;
		//数据挪动
		while (end >= pos)
		{
			(*end) = *(end - 1);
			end--;
		}
		_finish++;
		(*pos) = val;
		return pos;
	}

在使用pos时要注意扩容会使得pos失效,需要重新计算pos位置。

3.erase()

	iterator erase(iterator pos)
	{
        //判断位置是否合法
		assert(pos >= _start);
		assert(pos < _finish);
		iterator end = pos ;
        /挪动数据删除
		while (end < _finish)
		{
			*end = *(end + 1);
			end++;
		}
		_finish--;
		return pos;
	}

九.再看Vector构造函数

std中的vector还支持使用指定个数和初始化值初始化,和迭代器区间初始化。这两个功能在我们平时也是能用到的。

    //1.Vector<T> v(5,10);创建一个Vector并且初始化前5个值为10
	Vector(size_t n, const T& val = T())
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

	//2.迭代器初始化,[frist,lest)
	template<class InputIterator>
	Vector(InputIterator frist, InputIterator lest)
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		reserve(lest - frist);
		while (frist != lest)
		{
			push_back(*frist);
			frist++;
		}
	}

	//3.防止构造函数调用错误
	Vector(int n, const T& val = T())
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

第三个构造函数的作用是防止构造调用错误冲突,在我们进行如下的调用时:

Vector<int> v2(5, 10);

编译器会以为我们在调用迭代器区间初始化构造函数,因为经过模板的推导,只有迭代器区间初始化构造函数,更适合这个调用。然后将一个整形当作地址在迭代器区间初始化构造函数里面解引用了,报错是:非法的间接寻址

 正常调用结果:

十.拷贝构造

今天这里编译器默认生成的拷贝构造显然是不能用了。

1.深浅拷贝

万万不可以直接使用拷贝函数按二进制或者按字节直接拷贝了。

错误代码1:

	Vector(const Vector<T>& v)
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
        reserve(v.capacity());
		//万万不可以直接按二进制拷贝
		memcpy(_start, v._start, sizeof(T) * v.capacity()); /*error!!!!*/
		_finish = _start + v.size();
		_end_of_storage = _start + v.capacity();
	}

原因:

调用处代码:


int main()
{
	string str("abcdefg");
	Vector<string> v2(5,str);
	Vector<string> v3(v2);
	return 0;
}

 会使得我们同一块空间被delete两次从而引发内存错误。

2.正确的拷贝构造代码:

	Vector(const Vector<T>& v)
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		reserve(v.capacity());
		//这里我们将数据一个一个push进去,这样我们借助push_back底层插入的时候,
		//会使用string的赋值构造,完成深拷贝。
		for (int i = 0; i < v.size(); i++)
		{
			push_back(v[i]);
		}
	}
    //现代写法
	Vector(const Vector<T>& v)
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		Vector<T> tmp(v.begin(), v.end());
		swap(tmp);
	}

错误代码2:reserve()

	void reserve(size_t capa)
	{
		//仅支持容量扩大,不支持容量减小
		if (capacity() < capa)
		{
			size_t sz = size();
			iterator tmp = new T[capa];
			//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,
			//如果之前没有容量仅需,将新开的空间指向我们的_start.
			if (_start)
			{
				//这里千万不能按二进制直接拷贝.
				memcpy(tmp, _start, sizeof(T) * capacity());   /*error !!*/
				delete[] _start;
			}
			//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,
			//然后计算的size也并非是,准确的size。
			_start = tmp;
			_finish = tmp + sz;
			_end_of_storage = _start + capa;
		}
	}

这里我们仍然是使用了memcpy。

调用处代码:

int main()
{

	string str("abcdefg");
	Vector<string> v2;
	for (int i = 0; i < 6; i++)
	{
		v2.push_back(str);
	}

	return 0;
}

3.正确的 reserve()

void reserve(size_t capa)
	{
		//仅支持容量扩大,不支持容量减小
		if (capacity() < capa)
		{
			size_t sz = size();
			iterator tmp = new T[capa];
			//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,
			//如果之前没有容量仅需,将新开的空间指向我们的_start.
			if (_start)
			{
				//这里千万不能按二进制直接拷贝.
				//memcpy(tmp, _start, sizeof(T) * capacity());   /*ror !!*/
				for (int i = 0; i < size(); i++)
				{
                    //=内置类型直接赋值,自定义类型使用赋值构造
					tmp[i]=_start[i];
				}
				delete[] _start;
			}
			//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,
			//然后计算的size也并非是,准确的size。
			_start = tmp;
			_finish = tmp + sz;
			_end_of_storage = _start + capa;
		}
	}

这里有一个细节就是在reserve和拷贝构造的拷贝数据的时候我们都是使用了赋值。问题我们并没有重载赋值运算符,编译器自动生成,简单来说就是这里又会是一个浅拷贝。

4.赋值运算符重载

    //传统写法
    Vector<T>& operator=(const Vector<T>& v)
	{
		T* tmp = new T[v.capacity()];
		if (_start)
		{
			for (int i = 0; i < v.size(); i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + v.size();
		_end_of_storage = _start + v.capacity();
		
		return *this;
	}

    //现代写法
	void swap(Vector<T>& v )
	{
		std::swap(v._start, _start);
		std::swap(v._finish, _finish);
		std::swap(v._end_of_storage, _end_of_storage);
	}
    
	Vector<T>& operator=(Vector<T> v)
	{
		swap(v);
		return *this;
	}

现代写法利用,拷贝构造拷贝出来的对象,然后交换对象的成员。

十一.总体代码

#pragma once
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cassert>
using namespace std;

template<class T>
class Vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;
	Vector()
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
	}

	//1.Vector<T> v(5,10);创建一个Vector并且初始化前5个值为10
	Vector(size_t n, const T& val = T())
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

	//2.迭代器初始化,[frist,lest)
	template<class InputIterator>
	Vector(InputIterator frist, InputIterator lest)
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		reserve(lest - frist);
		while (frist != lest)
		{
			push_back(*frist);
			frist++;
		}
	}

	//3.防止构造函数调用冲突
	Vector(int n, const T& val = T())
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

	//传统写法 拷贝构造
	//Vector(const Vector<T>& v)
	//	:_start(nullptr),
	//	_finish(nullptr),
	//	_end_of_storage(nullptr)
	//{
	//	reserve(v.capacity());
	//	//这里我们将数据一个一个push进去,这样我们借助push_back底层插入的时候,
	//	//会使用string的赋值构造,完成深拷贝。
	//	for (int i = 0; i < v.size(); i++)
	//	{
	//		_start[i] = v[i];
	//	}
	//}
	//现代写法,拷贝构造
	Vector(const Vector<T>& v)
		:_start(nullptr),
		_finish(nullptr),
		_end_of_storage(nullptr)
	{
		Vector<T> tmp(v.begin(), v.end());
		swap(tmp);
	}

	//传统写法,赋值拷贝
	//Vector<T>& operator=(const Vector<T>& v)
	//{
	//	T* tmp = new T[v.capacity()];
	//	if (_start)
	//	{
	//		for (int i = 0; i < v.size(); i++)
	//		{
	//			tmp[i] = _start[i];
	//		}
	//		delete[] _start;
	//	}
	//	_start = tmp;
	//	_finish = _start + v.size();
	//	_end_of_storage = _start + v.capacity();
	//	
	//	return *this;
	//}
	

	void swap(Vector<T>& v )
	{
		std::swap(v._start, _start);
		std::swap(v._finish, _finish);
		std::swap(v._end_of_storage, _end_of_storage);
	}

	//现代写法,赋值拷贝
	Vector<T>& operator=(Vector<T> v)
	{
		swap(v);
		return *this;
	}


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

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

	void reserve(size_t capa)
	{
		//仅支持容量扩大,不支持容量减小
		if (capacity() < capa)
		{
			size_t sz = size();
			iterator tmp = new T[capa];
			//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,
			//如果之前没有容量仅需,将新开的空间指向我们的_start.
			if (_start)
			{
				//这里千万不能按二进制直接拷贝.
				//memcpy(tmp, _start, sizeof(T) * capacity());   /*ror !!*/
				for (int i = 0; i < size(); i++)
				{
					//=内置类型直接赋值,自定义类型使用赋值构造
					tmp[i]=_start[i];
				}
				delete[] _start;
			}
			//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,
			//然后计算的size也并非是,准确的size。
			_start = tmp;
			_finish = tmp + sz;
			_end_of_storage = _start + capa;
		}
	}

	void resize(size_t n, T val = T())
	{	
		//1.n < size;-->删除数据
		if (n < size())
		{
			_finish = _start + n;
		}
		//2.n > size
		else 
		{
			//(1)如果空间不足,需要扩容+初始化
			if (n >= capacity())
			{
				reserve(n);
			}
			//(2)空间足够,仅需要初始化剩下的空间
			while (_finish != _start + n)
			{
				*(_finish) = val;
				_finish++;
			}
		}
	}
	//穿引用返回
	T& operator[](size_t pos)
	{
		//判断位置的合法性
		assert(pos < size());
		return _start[pos];
	}

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

	void push_back(const T& val)
	{
		if (_finish == _end_of_storage)
		{
			capacity() == 0 ? reserve(5) : reserve(capacity() * 2);
		}
		//内置类型直接赋值,自定义类型使用赋值构造
		*(_finish) = val;
		_finish++;
	}

	bool empty() const 
	{
		return size() == 0;
	}

	void pop_back()
	{
		//判空
		assert(!empty());
		//我们仅需将维护尾部数据的指针向前挪一位。
		_finish--;
	}

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

	iterator insert(iterator pos,T val)
	{
		assert(pos >= _start);
		assert(pos < _finish);
		//迭代器失效问题,记录pos的相对位置
		int len = pos - _start;
		if (_finish == _end_of_storage)
		{
			capacity() == 0 ? reserve(5) : reserve(capacity() * 2);
		}
		//扩容后重新计算pos,没有发生扩容pos不变
		pos = _start + len;
		iterator end = _finish;
		//数据挪动
		while (end >= pos)
		{
			(*end) = *(end - 1);
			end--;
		}
		_finish++;
		(*pos) = val;
		return pos;
	}

	//普通迭代器
	iterator begin()
	{
		return _start;
	}
	iterator end()
	{
		return _finish;
	}

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

	~Vector()
	{
		delete[] _start;
		_start = _finish = _end_of_storage = nullptr;
	}
private:
	iterator _start;//数据存储首地址
	iterator _finish;//有效数据尾部地址下一个地址。
	iterator _end_of_storage;//容量尾地址下一个地址
	int tmp;
};




 

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

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

相关文章

Leetcode34 在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 代码&#xff1a; c…

若依-plus-vue启动显示Redis连接错误

用的Redis是windows版本&#xff0c;6.2.6 报错的主要信息如下&#xff1a; Failed to instantiate [org.redisson.api.RedissonClient]: Factory method redisson threw exception; nested exception is org.redisson.client.RedisConnectionException: Unable to connect t…

Vue中使用Tailwind css

1.什么是Tailwind 就是一个CSS框架&#xff0c;和你知道的bootstrap&#xff0c;element ui&#xff0c;Antd&#xff0c;bulma。一样。将一些css样式封装好&#xff0c;用来加速我们开发的一个工具。 Tailwind解释 tailwind css 中文文档 2.Vue使用Tailwind配置 1. 新建vu…

GB/T28181设备接入端如何应用到数字城管场景?

什么是数字城管&#xff1f; 数字城管&#xff0c;又称“数字化城市管理”或“智慧城管”&#xff0c;是一种采用信息化手段和移动通信技术来处理、分析和管理整个城市的所有城管部件和城管事件信息&#xff0c;促进城市管理现代化的信息化措施。 数字城管通过建立城市管理信息…

为什么String要设计成不可变的

文章目录 一、前言二、缓存hashcode缓存 三、性能四、安全性五、线程安全 一、前言 为什么要将String设计为不可变的呢&#xff1f;这个问题一直困扰着许多人&#xff0c;甚至有人直接向Java的创始人James Gosling提问过。在一次采访中&#xff0c;当被问及何时应该使用不可变…

腾讯云COS的快速接入

背景 最近在研究一个剪贴板粘贴工具&#xff0c;实现粘贴图片&#xff0c;返回可访问的地址&#xff0c;这个在我的哔哩哔哩上有出一期视频&#x1f92d;。但是&#xff0c;我发现部分博客平台不能正常的转载我的图片链接&#xff0c;于是研究了一下腾讯云的COS&#xff08;阿…

CSDN博客批量查询质量分https://yma16.inscode.cc/请求超时问题(设置postman超时时间)(接口提供者设置了nginx超时时间)

文章目录 查询链接问题请求超时原因解决谷歌浏览器超时问题办法&#xff08;失败了&#xff09;谷歌浏览器不支持设置请求超时时间&#xff08;谷歌浏览器到底有没限制请求超时&#xff1f;貌似没有限制&#xff1f;&#xff09;看能否脱离浏览器请求&#xff0c;我们查看关键代…

Java负载均衡算法实现与原理分析(轮询、随机、哈希、加权、最小连接)

文章目录 一、负载均衡算法概述二、轮询&#xff08;RoundRobin&#xff09;算法1、概述2、Java实现轮询算法3、优缺点 三、随机&#xff08;Random&#xff09;算法1、概述2、Java实现随机算法 四、源地址哈希&#xff08;Hash&#xff09;算法1、概述2、Java实现地址哈希算法…

面试笔记:Android 架构岗,一次4小时4面的体验

作者&#xff1a;橘子树 此次面试一共4面4小时&#xff0c;中间只有几分钟间隔。对持续的面试状态考验还是蛮大的。 关于面试的心态&#xff0c;保持悲观的乐观主义心态比较好。面前做面试准备时保持悲观&#xff0c;尽可能的做足准备。面后积极做复盘&#xff0c;乐观的接受最…

【MongoDB】数据库、集合、文档常用CRUD命令

目录 一、数据库操作 1、创建数据库操作 2、查看当前有哪些数据库 3、查看当前在使用哪个数据库 4、删除数据库 二、集合操作 1、查看有哪些集合 2、删除集合 3、创建集合 三、文档基本操作 1、插入数据 2、查询数据 3、删除数据 4、修改数据 四、文档分页查询 …

Selenium之css怎么实现元素定位?

世界上最远的距离大概就是明明看到一个页面元素站在那里&#xff0c;但是我却定位不到&#xff01;&#xff01; Selenium定位元素的方法有很多种&#xff0c;像是通过id、name、class_name、tag_name、link_text等等&#xff0c;但是这些方法局限性太大&#xff0c; 随着自动…

PS透明屏,在科技展示中,有哪些优点展示?

PS透明屏是一种新型的显示技术&#xff0c;它将传统的显示屏幕与透明材料相结合&#xff0c;使得屏幕能够同时显示图像和透过屏幕看到背后的物体。 这种技术在商业展示、广告宣传、产品展示等领域有着广泛的应用前景。 PS透明屏的工作原理是利用透明材料的特性&#xff0c;通…

旷视科技AIoT软硬一体化走向深处,生态和大模型成为“两翼”?

齐奏AI交响曲的当下&#xff0c;赛道玩家各自精彩。其中&#xff0c;被称作AI四小龙的商汤科技、云从科技、依图科技、旷视科技已成长为业内标杆&#xff0c;并积极追赶新浪潮。无论是涌向二级市场还是布局最新风口大模型&#xff0c;AI四小龙谁都不甘其后。 以深耕AIoT软硬一…

机器学习-自定义Loss函数

1、简介 机器学习框架中使用自定义的Loss函数&#xff0c; 2、应用 &#xff08;1&#xff09;sklearn from sklearn.metrics import max_error from sklearn.metrics import make_scorer from sklearn.model_selection import cross_val_score from sklearn.linear_model …

腾讯云标准型CVM云服务器详细介绍

腾讯云CVM服务器标准型实例的各项性能参数平衡&#xff0c;标准型云服务器适用于大多数常规业务&#xff0c;例如&#xff1a;web网站及中间件等&#xff0c;常见的标准型云服务器有CVM标准型S5、S6、SA3、SR1、S5se等规格&#xff0c;腾讯云服务器网来详细说下云服务器CVM标准…

Ubuntu22.04安装docker

在ubuntu22.04上安装docker还是比较容易的&#xff0c;之前在公司的centos6上边装docker&#xff0c;那才真是一言难尽呀&#xff0c;废话不多说&#xff0c;开始安装 1、更新包管理器 apt update 2、安装必要的软件包&#xff0c;以便允许 apt 使用 HTTPS 仓库 sudo apt i…

手撕Java集合——链表

链表 一、链表概念特性二、不带头单向非循环链表实现&#x1f351;1、定义结点&#x1f351;2、打印链表&#x1f351;3、使用递归逆序打印链表&#x1f351;4、头插&#x1f351;5、尾插&#x1f351;6、指定位置插入&#x1f351;7、查找是否包含关键字key是否在单链表当中&a…

多传感器融合相关技术

重要说明&#xff1a;本文从网上资料整理而来&#xff0c;仅记录博主学习相关知识点的过程&#xff0c;侵删。 一、参考资料 多传感器融合定位学习 深蓝-多传感器定位融合 深蓝学院 多传感器融合定位 作业 多传感器融合详解 二、相关介绍 1. 毫米波雷达&#xff08;Radar&a…

R语言4_安装BayesSpace

环境Ubuntu22/20, R4.1 你可能会报错说你的R语言版本没有这个库&#xff0c;但其实不然。这是一个在Bioconductor上的库。 同时我也碰到了这个问题&#xff0c;ERROR: configuration failed for package systemfonts’等诸多类似问题&#xff0c;下面的方法可以一并解决。 第…

790. 多米诺和托米诺平铺

题目描述&#xff1a; 主要思路&#xff1a; class Solution { public:int numTilings(int n) {long long f[n][4],mod1e97;f[0][0]1;f[0][1]f[0][2]0;f[0][3]1;for(int i1;i<n;i){f[i][0]f[i-1][3];f[i][1] (f[i-1][0]f[i-1][2])%mod;f[i][2] (f[i-1][0]f[i-1][1])%mod;f…