C++ List完全指南:使用方法与自定义实现

文章目录

  • list的使用
    • 几种构造函数
  • list的实现
    • 1.节点类的定义
      • 1.1节点类的构造函数
    • 2.正向迭代器实现
      • 2.1operator*重载
      • 2.2operator->重载
      • 2.3operator++重载
      • 2.4operator--
      • 2.5operator==和operator!=
    • 3.反向迭代器实现
      • 3.1operator*重载
      • 3.2operator->重载
      • 3.3operator++重载
      • 3.4operator--重载
      • 3.5operator!=和operator==重载
      • 3.6反向迭代器的全部代码
    • 4.list的成员函数
      • 4.1构造函数
      • 4.2assign函数
      • 4.3指定位置的插入
      • 4.4指定位置的删除
      • 4.5首插尾插和首删尾删
      • 4.6交换函数
      • 4.7resize函数
      • 4.8clear函数
      • 4.9迭代器的封装
  • 全部代码
  • 总结

在这里插入图片描述

list的使用

几种构造函数

在这里插入图片描述
无参默认构造函数

list<int> l1;

有参构造(使用val对list初始化n个对象)

list<int> l1(10, 1);

迭代器区间构造

list<int> l1(10, 1);
list<int> l2(l1.begin(), l1.end());

拷贝构造

list<int> l1(10, 1);
list<int> l2(l1);

还有一些老生常谈的迭代器和尾插,还有插入之类的使用我们就不用讲了,相信大家经过之前的vector和string的学习已经基本掌握使用了,但是在list中还多了一个接口,就是首插和首删,因为我们知道,在vector中我们要进行首插或者首删的代价是很大的,因为首插或者首删我们就要把整个数组移动,时间复杂度是线性的,但是对于list来说首插或者首删的代价是常数级的,因为我们库中的list使用的是带头的双向链表,所以我们可以以常数的时间复杂度进行任何位置的插入或者删除,虽然我说的list很好,但是list还有一个致命的缺陷,就是访问,对于list的访问来说,你要访问一个位置必须从头开始遍历,最大的时间复杂度是线性的,但是对于vector的访问来说,就是常数级的,所以list有好处也有不足的地方。
接下来我们来讲讲如何实现一个list
我们对链表肯定也是相当的熟悉,双向链表的结构就是两个指针,一个存放数据的成员,一个指针指向的是前一个节点,另一个指针指向的是下一个节点,我们来看看底层:
在这里插入图片描述
在这里插入图片描述
很显然底层是封装了的,底层的实现也是通过两个指针进行实现的,所以我们接下来实现也通过指针进行实现,并且先定义一个专门存放节点的结构体。

list的实现

1.节点类的定义

根据我们上面说的,我们先创建一个类来存放节点,由于我们要访问这个类的成员,所以干脆我们直接把这个类写成结构体,因为在C++中结构体默认是public。

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

上面就是我们定义的一个结构体,忘了说了,在这之前别忘了用一个命名空间将其隔离开,避免和库里的冲突了。

1.1节点类的构造函数

list_node(const T& val = T())
	:_prev(nullptr)
	, _next(nullptr)
	, _data(val)
{

}

参数部分就不需要解释了,用一个值来构造一个节点,后面的T()是临时对象,前面的const&延长了它的生命周期。

2.正向迭代器实现

对于迭代器的实现可和vector的实现不一样了,对于vector来说,有vector的空间是连续的,所以迭代器可以直接用指针书写,但是对于list来说空间根本不是连续的,我们对迭代器的要求是++就可以找到下一个节点的迭代器,然后–就可以找到上一个节点的迭代器,对于*我们就可以取到这个节点对应的值,所以这里很容易想到运算符重载,我们可以将这里的迭代器封装成一个类,然后对这个类进行封装

template<class T, class Ref, class Ptr>
struct list_iterator
{
	typedef list_node<T> node;
	typedef list_iterator<T, Ref, Ptr> self;
	node* _node;
};

为了增加可读性我们将迭代器重命名为self。

注意:这里Ref表示引用是否需要加const,这里的Ptr表示的是指针是否需要加const

2.1operator*重载

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

返回节点返回的值,注意 这里Ref代表的是引用

2.2operator->重载

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

operator->返回的是节点对应的值的指针,因为节点有可能是内置类型,所以我嗯呢重载这个运算符,所以我们需要重载这个运算符来访问他的成员

2.3operator++重载

前置++

self& operator++()//传递引用防止拷贝构造
{
	_node = _node->_next;
	return *this;
}

先将节点指向下一个节点,然后返回下一个节点的迭代器

后置++

self operator++(int)
{
	self tmp(*this);
	++*this;
	return tmp;
}

这里我们先创建一个临时的迭代器用this初始化然后对this进行++,注意,这里的++复用前面的的前置++,然后返回创建的临时的的迭代器的拷贝。注意这里返回值没有用引用,因为这里tmp出去之后要销毁,所以传递的是拷贝。

2.4operator–

//前置--
self& operator--()
{
	_node = _node->_prev;
	return *this;
}
//后置--
self operator--(int)
{
	self tmp(*this);
	--*this;
	return tmp;
}

2.5operator==和operator!=

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

注意:这里我们还需要一个构造函数可以构造一个迭代器的函数

list_iterator(node* n) :_node(n) {}

用当前节点来构造一个迭代器

3.反向迭代器实现

基于正向迭代器实现的反向迭代器
这里反向迭代器器中只需要一个成员变量就是正向迭代器,我们只需要用正向迭代器中的运算符重载来封装反向迭代器的运算符重载。

template<class iterator, class Ref, class Ptr>
struct list_reverse_iterator
{
	typedef list_reverse_iterator<iterator, Ref, Ptr> self;
	iterator _cur;
};

注意:这里的模版参数还是和上面正向迭代器中的一样。。

3.1operator*重载

Ref operator*()const
{
	//由于我们的成员变量是正向迭代器,但是我们的反向迭代器是从最后一个开始遍历的
	iterator tmp = _cur;
	--tmp;
	return *tmp;//这里应该返回的是iterator重载的*
}

3.2operator->重载

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

返回的是当前的指针

3.3operator++重载

前置++

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

注意:这里cur用的是正向迭代器中的前置–,反向迭代器的++是–向前访问

后置++

self operator++(int)
{
	iterator tmp(_cur);
	--*this;
	return tmp;
}

后置++和上面正向迭代器的后置++类似

3.4operator–重载

//反向迭代器的--是++
self& operator--()
{
	++_cur;
	return *this;
}
self operator--(int)
{
	iterator tmp(_cur);
	++*this;//这里的--是复用上面的运算符重载
	return tmp;
}

3.5operator!=和operator==重载

bool operator!=(const self& s)const
{
	//这里可以直接复用正向迭代器已经实现的!=操作
	return _cur != s._cur;
}
bool operator==(const self& s)const
{
	return _cur == s._cur;
}

3.6反向迭代器的全部代码

template<class iterator, class Ref, class Ptr>
struct list_reverse_iterator
{
	typedef list_reverse_iterator<iterator, Ref, Ptr> self;
	list_reverse_iterator(iterator it) :_cur(it) {}

	//重载反向迭代器的运算符
	Ref operator*()const
	{
		//由于我们的成员变量是正向迭代器,但是我们的反向迭代器是从最后一个开始遍历的
		iterator tmp = _cur;
		--tmp;
		return *tmp;//这里应该返回的是iterator重载的*
	}
	Ptr operator->()const
	{
		return &operator*();
	}
	//反向迭代器的++是--
	self& operator++()
	{
		--_cur;
		return *this;
	}
	self operator++(int)
	{
		iterator tmp(_cur);
		--*this;
		return tmp;
	}
	//反向迭代器的--是++
	self& operator--()
	{
		++_cur;
		return *this;
	}
	self operator--(int)
	{
		iterator tmp(_cur);
		++*this;//这里的--是复用上面的运算符重载
		return tmp;
	}
	bool operator!=(const self& s)const
	{
		//这里可以直接复用正向迭代器已经实现的!=操作
		return _cur != s._cur;
	}
	bool operator==(const self& s)const
	{
		return _cur == s._cur;
	}
	iterator _cur;
};

4.list的成员函数

首先list的成员变量只需要一个节点类就可以,接下来我们来定义一个list。

//list的成员变量
node* _head;
void empty_init()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}

由于我们每次初始化都要创建一个头节点,所以这里我们 直接封装成一个函数这样我们写构造函数的时候,就不用手动创建哨兵位的头结点了,可以直接调用函数。

4.1构造函数

4.1.1无参构造

list()
{
	empty_init();
}

对于无参构造我们可以直接调用创建头结点的函数
4.1.2有参构造(用val初始化n个节点)

list(size_t n, const T& val = T())
{
	empty_init();
	for (size_t i = 0;i < n;i++)
	{
		push_back(val);
	}
}
list(int n, const T& val = T())
{
	empty_init();
	for (int i = 0;;i < n;i++)
	{
		push_back(val);
	}
}

4.1.3有参的迭代区间构造

template <class InputIterator>
list(InputIterator first, InputIterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

4.1.4拷贝构造函数和析构函数

list(const list<T>& x)
{
	empty_init();
	for (auto e : x)
	{
		push_back(e);
	}
}
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

对于析构函数我们首先调用clear把所有除哨兵位的头结点外的节点全部给清理掉,然后再手动将头结点释放掉。
4.1.5赋值拷贝函数

list<T>& operator= (list<T> x)
{
	swap(x);//这里调用的是自己实现的swap
	return *this;
}

这是一种比较现代的写法,我们 传递的是拷贝构造,临时对象然后将这个临时对象和我们需要赋值拷贝的对象进行交换,由于这个是临时对象所以出了作用域就会销毁,这样我们的目的也达到了。
接下来我们来讲一种比较传统的写法,比较传统的写法:

list<T>& operator= (list<T> x)
{
	assign(x.begin(),x.end());
	return *this;
}

我们先用assign的迭代区间版进行拷贝,然后直接返回*this。

4.2assign函数

template <class InputIterator>
void assign(InputIterator first, InputIterator last)
{
	//先删除所有节点,只剩下一个哨兵位节点
	clear();
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}
void assign(size_t n, const T& val = T())
{
	clear();
	for (size_t i = 0;i < n;i++)
	{
		push_back(val);
	}
}
void assign(int n, const T& val = T())
{
	clear();
	for (int i = 0;i < n;i++)
	{
		push_back(val);
	}
}

注意迭代区间版本的assign函数需要重新定义一个模版,因为不妨有string或者其他的自定义类型的迭代器需要传递,如果我们传递就是当前类的迭代器,那么就只能传递当前类的迭代器,这样就一棒子打死了。

4.3指定位置的插入

注意:指定位置的删除返回的是迭代器,插入节点的迭代器,,这里我们来考虑一下会不会出现迭代器失效的情况,我们插入一个新节点,是我们重新开辟的节点,返回的也是重新开辟的节点的迭代器,所以这里不存在迭代器失效的问题。

对于插入来说,这里我们只需要记录pos位置的前一个节点,然后再pos和pos位置的前一个节点直接插入新的节点就可以了。

iterator insert(iterator pos, const T& val = T())
{
	//插入新的值,应该创建一个新的节点
	node* cur = pos._node;
	node* newnode = new node(val);
	node* prev = cur->_prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	prev->_next = newnode;
	newnode->_prev = prev;
	//这里返回的是构造函数
	//用newnode重新构造了一个迭代器
	return iterator(newnode);
}

4.4指定位置的删除

首先我们来讨论一下删除会不会出现迭代器失效的情况,这里很容易可以看出会出现迭代器失效的情况,因为我们删除的是当前节点,pos位置很明显已经被删除来了,成为了一个野的迭代器,所以这里为了防止迭代器失效的情况,我们直接返回下一个节点的迭代器就可以了

iterator erase(iterator pos)
{
	assert(pos != end());
	node* next = pos._node->_next;
	node* prev = pos._node->_prev;
	next->_prev = prev;
	prev->_next = next;
	delete pos._node;
	return iterator(next);
}

4.5首插尾插和首删尾删

这里可以直接复用上面写好的指定位置的插入和删除

void push_back(const T& val = T())
{
	insert(end(), val);
}
void pop_back()
{
	erase(--end());
}
void push_front(const T& val = T())
{
	insert(begin(), val);
}
void pop_front()
{
	erase(begin());
}

4.6交换函数

void swap(list<T>& x)
{
	std::swap(_head, x._head);
}

4.7resize函数

对于resize函数,当n小于实际的size的时候我们需要尾删节点,当大于实际的size的时候我们需要尾插节点,用给定的指定的值

void resize(size_t n, T val = T())
{
	size_t sz = size();
	while (n < sz)
	{
		pop_back();
		sz--;
	}
	while (n > sz)
	{
		push_back(val);
		sz++;
	}
}

4.8clear函数

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

4.9迭代器的封装

//封装两个正向迭代器
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<const T, const T&, const T*> const_iterator;
//封装两个反向迭代器
typedef list_reverse_iterator<T, T&, T*> reverse_iterator;
typedef list_reverse_iterator<const T, const T&, const T*> const_reverse_iterator;
iterator begin()
{
	//因为是双向带头的链表,又因为是左闭右开,所以应该用head指向下一个,传递给begin
	return iterator(_head->_next);
}
iterator end()
{
	//左闭右开的缘故,所以这里传递的是head,因为实际访问不到head
	return iterator(_head);
}
const_iterator begin()const
{
	return const_iterator(_head->_next);
}
const_iterator end()const
{
	return const_iterator(_head);
}
reverse_iterator rbegin()
{
	return reverse_iterator(end());
}
reverse_iterator rend()
{
	return reverse_iterator(begin());
}
const_reverse_iterator rbegin()const
{
	return const_reverse_iterator(end());
}
const_reverse_iterator rend()const
{
	return const_reverse_iterator(begin());
}

全部代码

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;


namespace lyrics
{
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
		list_node(const T& val = T())
			:_prev(nullptr)
			, _next(nullptr)
			, _data(val)
		{

		}
	};
	//这里封装一个类,用来控制list的迭代器
	template<class T, class Ref, class Ptr>
	//Ref表示引用是否const
	//Ptr表示指针是否const
	struct list_iterator
	{
		typedef list_node<T> node;
		typedef list_iterator<T, Ref, Ptr> self;
		node* _node;
		//构造函数
		list_iterator(node* n) :_node(n) {}

		//重载迭代器的基本操作
		//operator*用来访问迭代器对应的当前的数据
		//返回值应该是引用,我们的引用用的是Ref
		Ref operator*()const
		{
			return _node->_data;
		}

		//重载一个operator->防止list对应的是自定义类型的时候需要访问自定义类型的数据
		Ptr operator->()const
		{
			return &operator*();
		}

		//重载一个++操作,因为在迭代器遍历的时候需要用到++这个操作对迭代器进行移动
		//前置++
		self& operator++()//传递引用防止拷贝构造
		{
			_node = _node->_next;
			return *this;
		}
		//后置++
		self operator++(int)
		{
			self tmp(*this);
			++*this;
			return tmp;
		}
		//前置--
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//后置--
		self operator--(int)
		{
			self tmp(*this);
			--*this;
			return tmp;
		}
		//除了上面的操作还需要一个!=和==因为我们要判断是否迭代器多久停止
		bool operator==(const self& s)const
		{
			return _node == s._node;
		}
		bool operator!=(const self& s)const
		{
			return _node != s._node;
		}
	};
	//上面封装了正向迭代器接下来封装反向迭代器

	template<class iterator, class Ref, class Ptr>
	struct list_reverse_iterator
	{
		typedef list_reverse_iterator<iterator, Ref, Ptr> self;
		list_reverse_iterator(iterator it) :_cur(it) {}

		//重载反向迭代器的运算符
		Ref operator*()const
		{
			//由于我们的成员变量是正向迭代器,但是我们的反向迭代器是从最后一个开始遍历的
			iterator tmp = _cur;
			--tmp;
			return *tmp;//这里应该返回的是iterator重载的*
		}
		Ptr operator->()const
		{
			return &operator*();
		}
		//反向迭代器的++是--
		self& operator++()
		{
			--_cur;
			return *this;
		}
		self operator++(int)
		{
			iterator tmp(_cur);
			--*this;
			return tmp;
		}
		//反向迭代器的--是++
		self& operator--()
		{
			++_cur;
			return *this;
		}
		self operator--(int)
		{
			iterator tmp(_cur);
			++*this;//这里的--是复用上面的运算符重载
			return tmp;
		}
		bool operator!=(const self& s)const
		{
			//这里可以直接复用正向迭代器已经实现的!=操作
			return _cur != s._cur;
		}
		bool operator==(const self& s)const
		{
			return _cur == s._cur;
		}
		iterator _cur;
	};
	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		//封装两个正向迭代器
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<const T, const T&, const T*> const_iterator;
		//封装两个反向迭代器
		typedef list_reverse_iterator<T, T&, T*> reverse_iterator;
		typedef list_reverse_iterator<const T, const T&, const T*> const_reverse_iterator;
		iterator begin()
		{
			//因为是双向带头的链表,又因为是左闭右开,所以应该用head指向下一个,传递给begin
			return iterator(_head->_next);
		}
		iterator end()
		{
			//左闭右开的缘故,所以这里传递的是head,因为实际访问不到head
			return iterator(_head);
		}
		const_iterator begin()const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}
		const_reverse_iterator rbegin()const
		{
			return const_reverse_iterator(end());
		}
		const_reverse_iterator rend()const
		{
			return const_reverse_iterator(begin());
		}
		list()
		{
			empty_init();
		}
		list(size_t n, const T& val = T())
		{
			empty_init();
			for (size_t i = 0;i < n;i++)
			{
				push_back(val);
			}
		}
		list(int n, const T& val = T())
		{
			empty_init();
			for (int i = 0;;i < n;i++)
			{
				push_back(val);
			}
		}
		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		list(const list<T>& x)
		{
			empty_init();
			for (auto e : x)
			{
				push_back(e);
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		list<T>& operator= (list<T> x)
		{
			swap(x);//这里调用的是自己实现的swap
			return *this;
		}
		bool empty() const
		{
			return _head->_next == _head;
		}
		size_t size() const
		{
			size_t count = 0;
			for (auto e : *this)
			{
				count++;
			}
			return count;
		}
		template <class InputIterator>
		void assign(InputIterator first, InputIterator last)
		{
			//先删除所有节点,只剩下一个哨兵位节点
			clear();
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		void assign(size_t n, const T& val = T())
		{
			clear();
			for (size_t i = 0;i < n;i++)
			{
				push_back(val);
			}
		}
		void assign(int n, const T& val = T())
		{
			clear();
			for (int i = 0;i < n;i++)
			{
				push_back(val);
			}
		}
		iterator insert(iterator pos, const T& val = T())
		{
			//插入新的值,应该创建一个新的节点
			node* cur = pos._node;
			node* newnode = new node(val);
			node* prev = cur->_prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			prev->_next = newnode;
			newnode->_prev = prev;
			//这里返回的是构造函数
			//用newnode重新构造了一个迭代器
			return iterator(newnode);
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			node* prev = pos._node->_prev;
			next->_prev = prev;
			prev->_next = next;
			delete pos._node;
			return iterator(next);
		}
		void push_back(const T& val = T())
		{
			insert(end(), val);
		}
		void pop_back()
		{
			erase(--end());
		}
		void push_front(const T& val = T())
		{
			insert(begin(), val);
		}
		void pop_front()
		{
			erase(begin());
		}
		void swap(list<T>& x)
		{
			std::swap(_head, x._head);
		}
		void resize(size_t n, T val = T())
		{
			size_t sz = size();
			while (n < sz)
			{
				pop_back();
				sz--;
			}
			while (n > sz)
			{
				push_back(val);
				sz++;
			}
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
	private:
		node* _head;
		void empty_init()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}
	};
}

总结

在本文中,我们深入探讨了C++中std::list的使用以及如何通过模拟实现基本的链表功能。我们详细介绍了std::list的常见操作,如元素的插入、删除、访问和遍历,并解释了这些操作在底层是如何实现的。通过模拟实现一个简单的链表,我们不仅加深了对链表结构的理解,也体验了STL容器背后的设计思想和实现细节。

理解std::list的使用不仅是掌握C++标准库的重要部分,更是提高数据结构和算法水平的基础。通过亲自实现链表,我们可以更好地理解计算机内存管理和指针操作,这对于编写高效的C++程序至关重要。希望这篇文章能够帮助你更好地理解和运用std::list,并在实际编程中灵活运用。感谢你的阅读,期待你在C++编程之路上不断进步!

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

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

相关文章

音视频入门基础:像素格式专题(3)——FFmpeg源码解析BMP格式图片的底层实现原理

音视频入门基础&#xff1a;像素格式专题系列文章&#xff1a; 音视频入门基础&#xff1a;像素格式专题&#xff08;1&#xff09;——RGB简介 音视频入门基础&#xff1a;像素格式专题&#xff08;2&#xff09;——不通过第三方库将RGB24格式视频转换为BMP格式图片 音视频…

每日一问-如何设置VS Code 中 Markdown粘贴图片的位置

VS Code内的markdown编辑器应该算是比较好用的&#xff0c;但是有一个问题一直困扰着我&#xff0c;就是在编辑markdown文件时&#xff0c;粘贴图片的位置问题。默认情况下&#xff0c;VS Code会将粘贴的图片放在markdown文件的同级目录下&#xff0c;这样会导致markdown文件的…

OWASP top10--SQL注入(二)

目录 06&#xff1a;SQL注入提交方式 6.1、get提交 6.2、post提交 6.3、cookie提交 6.4、HTTP Header头提交 07&#xff1a;注入攻击支持类型 7.1、union注入&#xff1a; 7.1.1、union操作符一般与order by语句配合使用 7.1.2、information_schema注入 7.2、基于函数…

AIGC语音交互

目录 一、总体介绍 二、环境设置与安装 三、语音识别 四、语音合成 五、GPT调用 六、信息关系 七、实现效果 一、总体介绍 达成效果&#xff1a;在ROS系统中实现用户语音提问得到智能语音回答 用到的技术&#xff1a;科大讯飞的语音识别、语音合成 GPT 操作系统&#x…

顶顶通呼叫中心中间件-自动外呼输入分机号(比如隐私号)(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-自动外呼输入分机号(比如隐私号)(mod_cti基于FreeSWITCH) 比如有些人的号码是这样的就需要用上自动外呼输入分机号了 号码1&#xff1a;182XXXX8111-1234 号码2&#xff1a;182XXXX8222 如果号码是这样的就根据以下步骤配置 注意使用这个需要&#xff1a;…

web如何做接口层面自动化测试?

接口层面约等于集成化测试&#xff0c;且需要启动web容器 一般web项目的&#xff0c;代码都是按照分层开发的&#xff0c;业务主要是集中在service和dao层&#xff0c;而我们如果仅仅是利用之前的单元测试,然后把依赖的代码直接mock掉&#xff0c;仅仅测试controller这一块是没…

如何将前端项目打包并部署到不同服务器环境

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/frontlearningNotes 觉得有帮助的同学&#xff0c;可以点心心支持一下哈&#xff08;笔记是根据b站尚硅谷的前端讲师【张天禹老师】整理的&#xff0c;用于自己复盘&#xff0c;有需要学习的可以去b站学习原版视频&…

【Spring】深入理解 Spring 状态机:简化复杂业务逻辑的利器

前言 在软件开发中&#xff0c;有许多场景需要处理状态转换和状态驱动的逻辑&#xff0c;比如订单处理、工作流程管理、游戏引擎等。Spring 状态机&#xff08;Spring State Machine&#xff09;是 Spring Framework 提供的一个强大的模块&#xff0c;用于帮助开发人员轻松构建…

计算机网络 1

两台主机想通信&#xff0c;其实本质就是两个文件的资源交换&#xff0c;但是长距离的通信&#xff0c;面临的是很多的问题。这个时候需要通过一些方式来保证可靠性 什么是协议 这样一个例子&#xff0c;我是住在农村&#xff0c;我读高中了我需要去县里面读书。这个时候呢&…

01.并发编程简介

1 什么是并发编程 所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。 2 为什么我们要学习并发编程&#xff1f; 最直白的原因就是因为面试需要&#xff0c;大厂的 Java 岗的并发编程能力属于标配。 而在非大厂…

基于长短期记忆网络 LSTM 的送餐时间预测

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

Java进阶学习笔记30——BigDecimal

BigDecimal&#xff1a; 用于解决浮点型运算的&#xff0c;出现结果失真的问题。 运行结果&#xff1a; package cn.ensource.d4_bigdecimal;import java.math.BigDecimal;public class Test {public static void main(String[] args) {// 目标&#xff1a;了解BigDecimal类do…

科林Linux5_线程

一、线程基础 进程是操作系统经典的执行任务的生产力。 进程是最小的资源分配单位&#xff0c;进程的内存开销较大&#xff0c;在内存资源不变的情况下&#xff0c;提高进程的执行能力&#xff08;生产力&#xff09; 线程寄存在进程中&#xff0c;与进程共享资源&#xff0…

泛型...

定义&#xff1a;在编译过程中约束操作的数据类型。&#xff08;统一数据类型&#xff09; 格式&#xff1a;<数据类型> 泛型中不能写基本数据类型。 泛型类 在一个类中&#xff0c;某个变量的数据类型不确定时&#xff0c;可以定义带有泛型的类。 泛型的底层是Obje…

Java 泛型基础

目录 1. 为什么使用泛型 2. 泛型的使用方式 2.1. 泛型类 2.2. 泛型接口 2.3. 泛型方法 3. 泛型涉及的符号 3.1. 类型通配符"?" 3.2. 占位符 T/K/V/E 3.3. 占位符T和通配符&#xff1f;的区别。 4. 泛型不变性 5. 泛型编译时擦除 1. 为什么使用泛型 Java 为…

Pandas 模块-操纵数据(12)-处理字符串数据

目录 1. .str 模块 1.1 数据准备 1.2 .str 函数详解 1.2.1 .str capitalize() 首字母大写 1.2.2 .str casefold() 返回字符串的副本 1.2.3 .str cat() 连接输出 1.2.4 .str center(width[,fillchar]) 字符串居中 1.2.5 .str contains() 含有特定字符 1.2.6 .str count() 计…

【代码随想录——回溯算法二周目】

1. 组合总和 var (path []intres [][]int )func combinationSum(candidates []int, target int) [][]int {path make([]int, 0)res make([][]int, 0)dfs(candidates,target,0,0)return res }func dfs(candidates []int, target int,tempTarget int,start int) {if tempTarg…

【Xilinx】常用的全局时钟资源相关Xilinx器件原语

1 概述 常用的与全局时钟资源相关的Xilinx器件原语包括&#xff1a; IBUFGIBUFGDS、OBUFGDS 和 IBUFDS、OBUFDSBUFGBUFGPBUFGCEBUFGMUXBUFGDLLIBUFDS_GTXE1IBUFDS_GTE2IBUFDS_GTE3OBUFDS_GTE3IBUFDS_GTE4OBUFDS_GTE4DCM 刚开始看到这写源语&#xff0c;免不了好奇这些源语对应的…

网络空间安全数学基础·群

重点&#xff1a; 1. 群及子群的定义及相关结论 2. 群的判断,子群的判断 3. 群的阶,元素的阶,它们的相互关系 4. 同态,同构,核子群 2.1群的定义 定义&#xff1a;设G是一非空集合。如果在G上定义了一个代数运算&#xff0c;称为乘法&#xff0c;记为ab&#xff0c;而且这个运…

Ubuntu18.04 OpenSSH升级

升级前版本&#xff1a; rootecs-m2eqyb:/opt# ll total 20912 drwxr-xr-x 2 root root 4096 May 10 16:23 ./ drwxr-xr-x 24 root root 4096 May 10 14:38 ../ -rw-r--r-- 1 root root 1848766 May 10 16:23 openssh-9.7p1.tar.gz -rw-r--r-- 1 root root 18038…