C++ STL vector 模拟实现

news2024/11/24 19:31:54

✅<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;
};




 

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

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

相关文章

创建多图层叠加效果的背景与人物图像

引言&#xff1a; 在现代应用程序开发中&#xff0c;图形资源的使用是非常常见的&#xff0c;特别是在用户界面设计中。通过使用TImageList和TGlyph组件的组合&#xff0c;我们可以实现令人印象深刻的多图层叠加效果。本文将介绍如何使用这两个组件来创建背景和人物的多图层叠加…

doubletrouble靶机通关详解

信息收集 漏洞发现 扫目录 发现secret路径 里面有个图 qdPM9.1 网上找找exp 反弹shell http://192.168.0.107//uploads/users/632300-backdoor.php?cmdecho "<?php eval(\$_POST[1]);?>" > 1.php 蚁剑连上去传php-reverse-shell.php 提权 优化shell…

企业微信爆出漏洞,公司员工被迫摸鱼

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 昨晚加班好好的&#xff0c;突然接到公司通知&…

不可能,绝对不可能

前言 有 2 个月未更了&#xff0c;读者朋友微信留言&#xff1a;“亮哥&#xff0c;最近是不是颓了&#xff1f;好久未更了” 我随即回复&#xff1a; 没想到这时兄弟发来了&#xff1a; 好吧&#xff0c;给大家汇报下近况&#xff0c;不枉大家一直激励我前行。 项目管理 前段时…

MySQL分表实现上百万上千万记录分布存储的批量查询设计模式

我们知道可以将一个海量记录的 MySQL 大表根据主键、时间字段&#xff0c;条件字段等分成若干个表甚至保存在若干服务器中。唯一的问题就是跨服务器批量查询麻烦&#xff0c;只能通过应用程序来解决。谈谈在Java中的解决思路。其他语言原理类似。这里说的分表不是 MySQL 5.1 的…

STM32入门学习之定时器PWM输出

1.脉冲宽度调制PWM(Pulse Width Modulation)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。PWM可以理解为高低电平的占空比&#xff0c;即输出高电平时间与低电平时间的比值。PWM的应用是否广泛&#xff0c;比如在步进电机的控制中&#xff0c;可以通过P…

WebRTC本地视频通话使用ossrs服务搭建

iOS开发-ossrs服务WebRTC本地视频通话服务搭建 之前开发中使用到了ossrs&#xff0c;这里记录一下ossrs支持的WebRTC本地服务搭建。 一、ossrs是什么&#xff1f; ossrs是什么呢&#xff1f; SRS(Simple Realtime Server)是一个简单高效的实时视频服务器&#xff0c;支持RTM…

【佳佳怪文献分享】通过引导学会行走: 动态环境中的感知四足运动

标题&#xff1a;Learning to Walk by Steering: Perceptive Quadrupedal Locomotion in Dynamic Environments 作者&#xff1a;Mingyo Seo , Ryan Gupta , Yifeng Zhu , Alexy Skoutnev , Luis Sentis , and Yuke Zhu 来源&#xff1a;2023 IEEE International Conference …

Titanic--细节记录二

merge、join以及concat的方法的不同以及相同 相同之处&#xff1a;都用于合并数据。 不同之处&#xff1a; merge主要是基于列的合并。join主要是基于索引&#xff08;行标签&#xff09;的合并。concat可以沿任意轴合并&#xff0c;更灵活。 import pandas as pddf1 pd.Da…

Linux 查看内存使用情况的几种方法

在运行 Linux 系统的过程中为了让电脑或者服务器以最佳水平运行&#xff0c;常常需要监控内存统计信息。 那么今天我们就来看看有哪些方法可以访问所有相关信息并帮助管理员监控内存统计信息。 查看或者获取 Linux 中的内存使用情况既可以通过命令的方式&#xff0c;也可以通…

OptaPlanner笔记6 N皇后

N 个皇后 问题描述 将n个皇后放在n大小的棋盘上&#xff0c;没有两个皇后可以互相攻击。 最常见的 n 个皇后谜题是八个皇后谜题&#xff0c;n 8&#xff1a; 约束&#xff1a; 使用 n 列和 n 行的棋盘。在棋盘上放置n个皇后。没有两个女王可以互相攻击。女王可以攻击同一水…

Python语言基础---选择判断循环结构详解

文章目录 &#x1f340;引言&#x1f340;if语句&#x1f340;if-else语句&#x1f340;if-elif-else语句&#x1f340;for循环&#x1f340;while循环 &#x1f340;引言 在Python编程语言中&#xff0c;选择判断和循环是两个非常重要的概念。它们可以让我们根据条件执行不同的…

分布式应用:Zabbix监控Tomcat

目录 一、理论 1.Zabbix监控Tomcat 二、实验 1.Zabbix监控Tomcat 三、问题 1.获取软件包失败 2.tomcat 配置 JMX remote monitor不生效 3.Zabbix客户端日志报错 一、理论 1.Zabbix监控Tomcat &#xff08;1&#xff09;环境 zabbix服务端&#xff1a;192.168.204.214 …

模型性能的主要指标

主要参数 ROC 曲线和混淆矩阵都是用来评估分类模型性能的工具 ROC曲线&#xff08;Receiver Operating Characteristic curve&#xff09;&#xff1a; ROC曲线描述了当阈值变化时&#xff0c;真正类率&#xff08;True Positive Rate, TPR&#xff09;和假正类率&#xff0…

SAP 收藏夹Favorites介绍,收藏夹导入/导出功能

作为SAP用户&#xff0c;经常需要使用一些事务代码T-Code, 很多时候会因为不常用其中的一些遗忘这个功能&#xff0c;所以这时候分类收藏好不同Module的事务代码到收藏夹里是一个不错的选择。 经常面临的一个场景就是需要变换系统环境&#xff0c;比如从Client A01,去到Client…

springcloud 基础

SprinbCloud微服务简介 架构发展历史 SpringBoot由baiPivotal团队在2013年开始研发、2014年4月发布第一个du版本的全新开源的轻量级框架。 它基zhi于Spring4.0设计&#xff0c;不dao仅继承了Spring框架原有的优秀特性&#xff0c;而且还通过简化配置来进一步简化了Spring应用…

QIIME 2教程. 11元数据Metadata(2023.5)

QIIME 2用户文档. 11元数据 Metadata in QIIME 2 https://docs.qiime2.org/2023.5/tutorials/metadata/ 注&#xff1a;此实例需要一些基础知识&#xff0c;要求完成本系列文章前两篇内容&#xff1a;《1简介和安装Introduction&Install》和4《人体各部位微生物组分析Movin…

【硬件突击 电路】

文章目录 1. 电阻&#xff08;Resistor&#xff09;&#xff1a;2. 电容&#xff08;Capacitor&#xff09;&#xff1a;3. 电感&#xff1a;4、 RC、RL、RLC电路结构及工作原理基尔霍夫定律基尔霍夫电流定律&#xff08;KCL&#xff09;基尔霍夫电压定律&#xff08;KVL&#…

【舌尖优省PLUS】美团、饿了么外卖免费领红包,尽情享受美食与省钱!

家人们&#xff01;我昨天刚开发完并上线了一个超棒的外卖免费领红包的小程序&#xff0c;它叫做【舌尖优省PLUS】&#xff01;如果你喜欢美食&#xff0c;还想省下一些钱&#xff0c;那这个小程序绝对不能错过&#xff01; 在【舌尖优省PLUS】上&#xff0c;你可以通过简单的…

react 生命周期方法

组件的生命周期 每个组件都包含 “生命周期方法”&#xff0c;你可以重写这些方法&#xff0c;以便于在运行过程中特定的阶段执行这些方法。你可以使用此生命周期图谱作为速查表。在下述列表中&#xff0c;常用的生命周期方法会被加粗。其余生命周期函数的使用则相对罕见。 挂…