C++ list

news2024/11/26 12:16:46

目录

一.   初步了解

1.构造、析构、赋值

2.容量

3.元素访问

4.增删

二.   模拟实现

框架

push_back

迭代器

带参构造、析构、赋值

增删

反向迭代器

所有代码


说白了,就是一个双向循环带头链表,由于我们在数据结构中已经学习过链表的知识,所以在接口的使用上还是很简单的,我们就放一些cplusplus中相关结构的图片和一些代码来作简单的使用,重点依旧是放在模拟实现上

一.   初步了解

1.构造、析构、赋值

 

​void test1()
{
	list<int> l1;
	list<int> l2(2, 4);
	list<int> l3(l2.begin(), l2.end());
	list<int> l4(l3);
    list<int> l5;
    l5=l2;
}

​

2.容量

由于链表没有容量的概念,因此也就没有reserve,但resize还是有的,归到增 里面去了

void test2()
{
	list<int> l1;
	list<int> l2(2, 4);
	cout << l1.empty() << endl;
	cout << l2.size() << endl;
}

3.元素访问

由于链表无法做到随机访问,因此与string和vector不同,没有元素访问(Element access) (硬要说也有个front和back,没啥用)

4.增删

与string、vector不同,由于链表的插入删除很便利,不需要进行元素的大量移动,所以多了几个相关的接口

 

 

void test3()
{
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.pop_back();
	l1.resize(2, 4);
	l1.push_front(3);
	list<int>::iterator it = l1.begin();
	it++;
	l1.insert(it, 2, 5);
	l1.pop_front();
	l1.erase(l1.begin(), it);
	l1.clear();
}

1 -> 1,2 -> 1 -> 1,4 -> 3,1,4 -> 3,5,5,1,4 -> 5,5,1,4 -> 1,4 -> 

有点抽象,凑活看吧,不行就自己扔编译器里调试去

大概就这么些东西,当然接口远远不止这些,这里挑了点模拟实现可能用得到的说了说,反正接口就那么些东西,功能好多都不变,实在不懂看cplusplus去吧,主要还是专注模拟实现


二.   模拟实现

框架

与string与vector不同的是,在定义主体前,list还需要定义一下节点,当然,为了避免冲突,别忘了放在命名空间里

template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;

	ListNode(const T& data = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(data)
	{}
};

之后便是主体的定义,由于是一个带头链表,类成员变量只需要一个头结点的指针

而为了使用起来方便一些,我们可以typedef一下节点的类型

template<class T>
class list
{
public:
	typedef ListNode<T> Node;
private:
	Node* _head;
};

然后,就是无参的构造函数(有参的、拷贝构造、析构、赋值这些涉及到了迭代器,后面再说)

由于是双向循环的,因此只需要让prev和next都指向自己就好了

list()
{
	_head = new Node;
	_head->prev = _head;
	_head->next = _head;
}

push_back

其他的增删大多都涉及到迭代器,但链表里没有节点也不好讲迭代器,就先把push_back讲讲

尾插节点嘛,由于是双向循环的,尾节点也很好找

push_back(const T& x)
{
	Node* newnode(x);
	_head->prev->next = newnode;
	newnode->prev = _head->prev;
	newnode->next = _head;
	_head->_prev = newnode;
}

迭代器

与模拟string与vector不同,它们的迭代器实质上就是指针,只需要typedef一下就能正常的实现解引用和++等操作,而list不同,list的迭代器若是直接使用指针,解引用得到的是节点,而不是节点中的数据,而由于链表各个节点在内存中都不是连续的,所以++操作也无法使指针移向下一个节点,一次,我们还需要定义一个迭代器的类模板,通过操作符重载来达到想要的效果

	template<class T>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		Node* _node;
		__list_iterator(Node* x)
			:_node(x)
		{}

		T& operator*()
		{
			return _node->_data;
		}

		__list_iterator<T>& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		__list_iterator<T> operator++(int)
		{
			__list_iterator<T> tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		__list_iterator<T>& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		__list_iterator<T> operator--(int)
		{
			__list_iterator<T> tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const __list_iterator<T>& it) const
		{
			return _node != it._node;
		}

		bool operator==(const __list_iterator<T>& it) const
		{
			return _node != it._node;
		}
	};

在定义好迭代器后,就可以随手把begin和end写出来了(rbegin、rend后面讲)

typedef __list_iterator<T> iterator;
iterator begin()
{
	return iterator(_head->next);//第一个节点,就是头结点的下一个
}

iterator end()
{
	return iterator(_head);//最后一个节点的下一个,由于是循环的,也就是头结点
}

然后我们可以测试一下

void test_list1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	for (list<int>::iterator it = lt.begin();it != lt.end();it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	for (list<int>::iterator it = lt.begin(); it != lt.end(); it++)
	{
		*it *= 2;
		cout << *it << " ";
	}
	cout << endl;
}

而在使用的过程中,涉及到了迭代器的拷贝、析构等,而迭代器中的成员变量依旧是链表中节点的地址,不需要进行深拷贝,也不需要随着迭代器的销毁而将节点也销毁掉,因此只需要使用默认构造的即可

而当出现其他情况,例如容器被const修饰了,那迭代器应该怎么办呢

例如我们定义一个print函数

void print_list(const list<int>& lt)
{
	list<int>::const_iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

若是想要使用const修饰的迭代器,首先我们不能只是将*重载添加一个consr版本,因为被const修饰的是容器而不是迭代器,当然,我们可以重新写一个类定义const迭代器

template<class T>
struct __const_list_iterator
{
	typedef ListNode<T> Node;
	Node* _node;

	__const_list_iterator(Node* x)
		:_node(x)
	{}
	const T& operator*()
	{
		return _node->_data;
	}

	__const_list_iterator<T>& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	__const_list_iterator<T> operator++(int)
	{
		__const_list_iterator<T> tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	__const_list_iterator<T>& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	__const_list_iterator<T> operator--(int)
	{
		__list_iterator<T> tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	bool operator!=(const __const_list_iterator<T>& it) const
	{
		return _node != it._node;
	}

	bool operator==(const __const_list_iterator<T>& it) const
	{
		return _node != it._node;
	}
};

但,这样做代码会过于冗杂,因为除了名称不同以及*重载时的返回类型不同,绝大多数代码都是一样的

为了精简,我们可以利用一下模板参数

我们可以通过一个模板参数来控制解引用操作符重载的返回类型

template<class T, class Ref>
struct __list_iterator
{
	typedef ListNode<T> Node;
	Node* _node;

	__list_iterator(Node* x)
		:_node(x)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	__list_iterator<T, Ref>& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	__list_iterator<T, Ref> operator++(int)
	{
		__list_iterator<T, Ref> tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	__list_iterator<T, Ref>& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	__list_iterator<T, Ref> operator--(int)
	{
		__list_iterator<T, Ref> tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	bool operator!=(const __list_iterator<T, Ref>& it) const
	{
		return _node != it._node;
	}

	bool operator==(const __list_iterator<T, Ref>& it) const
	{
		return _node != it._node;
	}
};

而在list中,就需要根据情况来判断模板参数是否应该被const修饰

typedef __list_iterator<T, T&> iterator;
typedef __list_iterator<T, const T&> const_iterator;

const_iterator begin() const
{
	return const_iterator(_head->_next);
}

const_iterator end() const
{
	return const_iterator(_head);
}

const_iterator begin() const
{
	return const_iterator(_head->_next);
}

const_iterator end() const
{
	return const_iterator(_head);
}

这样,就能完成const迭代器了

但上面所用到的都是基于list类型为内置类型的情况下,而若是为自定义类型呢?

再次搬出我们的日期类

struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
};
void test_list2()
{
	list<Date> lt;
	lt.push_back(Date(2022, 3, 12));
	lt.push_back(Date(2022, 3, 13));
	lt.push_back(Date(2022, 3, 14));

	list<Date>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
		++it;
	}
	cout << endl;
}

在打印日期类的年月日时,使用的是(*it)._year这种类型,而我们其实更习惯于it->_year,因此,我们可以对->操作符也进行重载

T* operator->()
{
	return &_node->_data;
}

重载的确是这样的,但原式就变成了_node->_data _year这样,而我们其实想要

_node->_data-> _year这样,这也就需要写作it->->_year,为了方便统一,直接将它优化为了

it->_year这样。

while (it != lt.end())
{
	cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
	++it;
}

而同样,->运算符重载的返回类型也有const之分,因此也可以向上面那样改变一下模板。同时,随着模板参数越来越多,我们也可以在迭代器内部将迭代器名称typedef一下

template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef ListNode<T> Node;
	typedef  __list_iterator<T, Ref, Ptr> self;
	Node* _node;

	__list_iterator(Node* x)
		:_node(x)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	bool operator!=(const self& it) const
	{
		return _node != it._node;
	}

	bool operator==(const self& it) const
	{
		return _node != it._node;
	}
};
template<class T>
class list
{
public:
	typedef ListNode<T> Node;
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;
    //。。。。。。
}

带参构造、析构、赋值

首先就是传n和val以及传迭代器的

list(int n, const T& val = T())
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	for (size_t i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

template<class InputIterator>
list(InputIterator first, InputIterator last)
{
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

这两种方式看起来简单,其实也是有些问题需要注意

void test_list3()
{
	list<int> lt(5, 1);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

若是我们这样使用,会出现非法的间接寻址这样的报错,这是因为,在构造lt时,传入的参数是5和1,都是int类型,而size_t与int并不很匹配,但由于都是int类型,因此更加匹配传迭代器的构造,因此就导致非法间接寻址,我们无法做到将n的类型改变为size_t,只能是将n的类型改变为int。因此在这里我们也就不能向往常一样因为n不能为负数就将其类型写作size_t

之后,就是拷贝构造,依旧是涉及到传统写法和现代写法

list(const list<T>& lt)
{
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;
			
	for (auto e : it)
	{
		push_back(e);
	}
}
list(const list<T>& lt)
{
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;

	list<T> tmp(lt.begin(), lt.end());
	std::swap(_head, tmp._head);
}

而我们可以看到,与往常不同,在交换之前,我们先是初始化头结点,这是因为头结点是肯定存在的,若是不进行初始化,会在交换给tmp后作为随机值被销毁,发生问题。

然后就是赋值

list<T>& operator=(list<T> lt)
{
	std::swap(_head, lt._head);
	return *this;
}
list<T>& operator=(const list<T>& lt)
{
	if (this != &lt)
	{
		clear();
		for (auto e : lt)
		{
			push_back(e);
		}
	}
}

同时,在传统写法中还用到了clear,而clear中涉及的erase,先用着,后面马上讲了

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		erase(it++);
	}
}

而由于赋值是在对象定义之后,已经进行了初始化,因此就没有拷贝构造当中的问题

最后就是析构,也没有什么需要注意的

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

增删

最主要的就两个,insert和erase

挺简单的,就是链表的插入删除,只是要注意erase删除后pos会失效

iterator insert(iterator pos, const T& x)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(x);

	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;

	return iterator(newnode);
}

iterator erase(iterator pos)
{
	assert(pos != end());

	Node* prev = pos._node->_prev;
	Node* next = pos._node->_next;
	delete pos._node;
	prev->_next = next;
	next->_prev = prev;

	return iterator(next);
}

而头插,尾删之类的复用就行了

void push_front(const T& x)
{
	insert(begin(), x);
}

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

void pop_back()
{
	erase(--end());
}

void pop_front()
{
	erase(begin());
}

反向迭代器

首先,我们通过原码可以得知,反向迭代器其实是正向迭代器的封装,rbegin取的是end,即最后一个节点的下一个位置,而rend取得是begin,即第一个节点的位置

而这并不是我们原本理解的反向迭代器

为啥呢?我也不知道,设计的大佬就这样写的

而若是这样写,那么在取地址时取得应该就是该迭代器下一个位置的数据了

而++与--实际就是正向迭代器的--与++

而与迭代器不同的是,迭代器的第一个模板参数是List存储的数据类型,而反向迭代器需要通过正向迭代器来运行,所以第一个模板参数采用的便是正向迭代器

同时,最好是把这块命名空间单独存放在一个头文件中,至于为什么后面说

namespace szt
{
	template <class Iterator, class Ref, class Ptr>
	class reverse_iterator
	{
	public:
		typedef reverse_iterator<Iterator, Ref, Ptr> self;
		reverse_iterator(Iterator it)
			:_it(it)
		{}

		Ref operator*()
		{
			Iterator prev = _it;
			return *--prev;
		}

		Ptr operator->()
		{
			return &operator*();
		}

		self& operator++()
		{
			--_it;
			return *this;
		}

		self& operator--()
		{
			++_it;
			return *this;
		}

		bool operator!= (const self& rit) const
		{
			return _it != rit._it;
		}

	private:
		Iterator _it;
	};
}

而在list类中也别忘了typedef一下

typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
typedef reverse_iterator<iterator, T&, T*> reverse_iterator;

而我们完全可以按照正向迭代器的方式来写一个新的反向迭代器,那么我们为什么要这样写呢?

因为若是利用正向迭代器来完成反向迭代器,那么我们可以直接使用这段代码来完成其他容器的反向迭代器,只需要对原本的类做出一点小小的改动,这也就是为什么我们将它单独存储在一个头文件中的原因。

而我们通过原码可以得知,原码中的反向迭代器只使用了迭代器这一个模板参数,这是因为可以通过正向迭代器中的内嵌类型来得到反向迭代器的Ref与Ptr

具体是怎么实现的呢?

首先需要typedef一下正向迭代器中的Ref与Ptr从而获取内嵌类型

typedef Ref reference;
typedef Ptr pionter;

之后typedef一下正向迭代器中的reference和pionter

但由于reference和pionter是模板参数,在实例化之前得不到具体的类型,所以我们需要在它们之前加上typename使得在实例化之后再进行typedef。

typedef typename Iterator::reference Ref;
typedef typename Iterator::pionter Ptr;

当然我们也可以直接使用typename Iterator::reference和typename Iterator::pionter


所有代码

反向迭代器

#include<iostream>
using namespace std;

namespace bit
{
	template <class Iterator>
	class reverse_iterator
	{
		typedef reverse_iterator<Iterator> self;

		typedef typename Iterator::reference Ref;
		typedef typename Iterator::pointer Ptr;

	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}

		Ref operator*()
		{
			Iterator prev = _it;
			return *--prev;
		}

		Ptr operator->()
		{
			return &operator*();
		}

		self& operator++()
		{
			--_it;
			return *this;
		}

		self& operator--()
		{
			++_it;
			return *this;
		}

		bool operator!= (const self& rit) const
		{
			return _it != rit._it;
		}

	private:
		Iterator _it;
	};
}

list

#include"reverse_iterator.h"

namespace szt
{
	struct Date
	{
		int _year;
		int _month;
		int _day;

		Date(int year = 1, int month = 1, int day = 1)
			:_year(year)
			, _month(month)
			, _day(day)
		{}
	};

	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _data;

		ListNode(const T& data = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{}
	};

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef  __list_iterator<T, Ref, Ptr> self;
		typedef Ref reference;
		typedef Ptr pionter;
		Node* _node;

		__list_iterator(Node* x)
			:_node(x)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}

		bool operator==(const self& it) const
		{
			return _node != it._node;
		}
	};

	template<class T>
	class list
	{
	public:
		typedef ListNode<T> Node;
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
		typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
		list()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}

		list(int n, const T& val = T())
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list(const list<T>& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;

			list<T> tmp(lt.begin(), lt.end());
			std::swap(_head, tmp._head);
		}

		list<T>& operator=(list<T> lt)
		{
			std::swap(_head, lt._head);
			return *this;
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
			}
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

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

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;
			delete pos._node;
			prev->_next = next;
			next->_prev = prev;

			return iterator(next);
		}

	private:
		Node* _head;
	};

	void print_list(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			//*it = 10;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		for (list<int>::iterator it = lt.begin();it != lt.end();it++)
		{
			cout << *it << " ";
		}
		cout << endl;
		for (list<int>::iterator it = lt.begin(); it != lt.end(); it++)
		{
			*it *= 2;
			cout << *it << " ";
		}
		cout << endl;
	}
	void test_list2()
	{
		list<Date> lt;
		lt.push_back(Date(2022, 3, 12));
		lt.push_back(Date(2022, 3, 13));
		lt.push_back(Date(2022, 3, 14));

		list<Date>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
			++it;
		}
		cout << endl;
	}

	void test_list3()
	{
		list<int> lt2(5, 1);
		for (auto e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

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

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

相关文章

macOS Big Sur 11.7.3 (20G1116) Boot ISO 原版可引导镜像

本站下载的 macOS Big Sur 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。 请访问原文链接&#xff1a;https://sysin.org/blog/macOS-Big-Sur-boot-iso/&a…

Nginx学习整理|入门记录

目录 1. Nginx概述 1.1 Nginx介绍 1.2 Nginx下载和安装 1.3 Nginx目录结构 2. Nginx命令 3. Nginx配置文件结构 4. Nginx具体应用 4.1 部署静态资源 4.2 反向代理 4.3 负载均衡 1. Nginx概述 1.1 Nginx介绍 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件…

积分电路的并联电阻、反向放大电路的并联电容的区别?

运放反相比例放大电路中反馈电阻两端经常并联一个电容&#xff0c;而运放积分电路的反馈电容上常常并联一个电阻&#xff0c;两者电路结构相似&#xff0c;如下所示&#xff08;隐去阻容值&#xff09;&#xff0c;二者有何区别呢&#xff1f;电阻、电容分别又起到什么作用&…

Catboost

CatBoost简介 CatBoost是俄罗斯搜索巨头Yandex在2017年开源的机器学习库&#xff0c;是Boosting算法的一种&#xff0c;CatBoost和XGBoost&#xff0c;Lightgbm并称为GBDT三大主流神器&#xff0c;都是在GBDT算法框架下的一种改进实现&#xff0c;XGBoost是被广泛应用于工业界…

使用jstack解决线程爆满问题

问题发现生产应用现存在问题&#xff0c;影响到系统的使用&#xff0c;前端页面只配置了35个派生指标&#xff0c;后台任务生成20000多线程任务&#xff0c;占用了全部资源&#xff0c;导致其他系统也没资源可用&#xff0c;指标工厂也无法进一步使用&#xff0c;今天上午发的死…

Email Signature Manager 9.3 Crack

概述 Email Signature Manager为所有用户创建和部署电子邮件签名 包括合并的联系方式、公司徽标、社交媒体图标 和链接&#xff0c;甚至个性化内容&#xff0c;如用户照片 创建和附加电子邮件活动&#xff0c;向所有人介绍奖项&#xff0c; 活动或促销&#xff0c;或设置运行的…

基于STM32的FreeRTOS开发(1)----FreeRTOS简介

为什么使用freertos FreeRTOS 是一个免费和开源的实时操作系统&#xff0c;它主要用于嵌入式系统。它非常轻量级&#xff0c;可以在很小的硬件资源上运行&#xff0c;因此非常适合在限制硬件资源的嵌入式系统中使用。 FreeRTOS提供了一组简单的任务管理功能&#xff0c;可以让…

基于Springboot vue前后端分离在线培训考试系统源码

# 云帆培训考试系统 管理账号&#xff1a;admin/admin 学员账号&#xff1a;person/person # 介绍 一款多角色在线培训考试系统&#xff0c;系统集成了用户管理、角色管理、部门管理、题库管理、试题管理、试题导入导出、考试管理、在线考试、错题训练等功能&#xff0c;考…

C++:运算符重载与类的赋值运算符重载函数

目录 章节知识架构 一.运算符重载 1. 运算符重载的基本概念 代码段1 2.关于运算符重载的重要语法细则 二.运算符重载在类中的使用 三.类的默认成员函数&#xff1a;重载函数(赋值运算符重载) 1.自定义重载函数 代码段2 2.编译器默认生成的重载函数 四.前置(--)和后置…

Facebook小组与主页:哪个更适合SEO?

在 SEO中&#xff0c;对于优化人员来说有两种策略&#xff1a;一种是在 Facebook组上投放广告&#xff1b;另一种则是在主页上投放广告。那么&#xff0c;这两种策略哪种更好呢&#xff1f;对于 SEO来说又有什么影响呢&#xff1f;如果你已经在 Facebook上进行了一些优化工作&a…

Python---文件操作

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些python的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 文件操作思维导图前言文件是什么文件路径文件操…

SpringBoot基础回顾:场景启动器

上一章我们回顾了 SpringBoot 的自动装配&#xff0c;以及承载自动装配的核心——自动配置类。自动配置类的定义位置通常在每个场景的 jar 包中&#xff0c;配置 spring.factories 文件中 EnableAutoConfiguration 的位置通常在相应的 autoconfigure jar 包下。本章会着重回顾和…

SpringMVC简介

SpringMVC简介什么是MVC?MVC的工作流程什么是SpringMVC&#xff1f;HelloWorld创建maven工程配置web.xml创建请求控制器配置springMVC.xml配置文件什么是MVC? MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分 M:Model,模型层,指工程中的javaBean,作用是是处理数…

恶意代码分析实战 9 隐蔽的恶意代码启动

9.1 Lab12-1 分析 查看程序的导入函数。 通过这几个函数&#xff0c;可以推断出是远程线程注入。 使用ProMon检测&#xff0c;并没有看到什么有用的信息。 使用Proexproer检查。 也没有什么有用的信息。 拖入IDA中分析一下。 将这几个字符串重命名&#xff0c;便于识别。 …

【MyBatis】| MyBatis使用⼩技巧

目录 一&#xff1a;MyBatis使用⼩技巧 1. #{}和${} 2. typeAliases 3. mappers 4. IDEA配置⽂件模板 5. 插⼊数据时获取⾃动⽣成的主键 一&#xff1a;MyBatis使用⼩技巧 1. #{}和${} #{}&#xff1a;先编译sql语句&#xff0c;再给占位符传值&#xff0c;底层是Prepar…

【C语言进阶】一文带你学会C语言文件操作

前言 我们前面学习结构体时&#xff0c;写了通讯录的程序&#xff0c;当通讯录运行起来的时候&#xff0c;可以给通讯录中增加、删除数据&#xff0c;此时数据是存放在内存中&#xff0c;当程序退出的时候&#xff0c;通讯录中的数据自然就不存在了&#xff0c;等下次运行通讯录…

Python---自动生成二维码

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些python的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 自动生成二维码 二维码的本质上&#xff0c;就…

人工智能学习06--pytorch05--torchvision中的数据集使用DataLoader的使用

torchvision中的数据集使用 test_set的class属性 把数据集每一部分都变成tensor类型 现在输出的就是tensor数据类型了 DataLoader的使用 batch_size 一摞牌中&#xff0c;每次抓几张shuffle 打乱&#xff0c;第二次打牌前&#xff0c;牌的顺序要跟第一次不一样&#xff0…

【JavaSE】一文看懂构造器/构造方法(Cunstructor)

&#x1f331;博主简介&#xff1a;大一计科生&#xff0c;努力学习Java中!热爱写博客~预备程序媛 &#x1f4dc;所属专栏&#xff1a;Java冒险记【从小白到大佬之路】 ✈往期博文回顾: 【JavaSE】保姆级教程|1万字10张图学会类与对象–建议收藏 &#x1f575;️‍♂️近期目标…

CSS边框、边距、轮廓(边框宽度/颜色/各边/简写属性/圆角边框/内外边距/高度宽度/框模型/轮廓宽度/颜色/属性/偏移)——万字长文|一文搞懂

目录 CSS边框 CSS 边框属性 CSS 边框样式 实例 CSS 边框宽度 实例 特定边的宽度 实例 CSS 边框颜色 实例 特定边框的颜色 实例 HEX 值 实例 RGB 值 实例 HSL 值 实例 CSS 边框 - 单独的边 实例 不同的边框样式 实例 它的工作原理是这样的&#xff1a; …