【C++】list类的模拟实现

🏖️作者:@malloc不出对象
⛺专栏:C++的学习之路
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

    • 前言
    • 一、list类的模拟实现
      • 1.1 list的主体框架
      • 1.2 无参构造函数
      • 1.3 push_back
      • 1.4 正向迭代器
      • 1.5 反向迭代器
      • 1.6 insert
      • 1.7 erase
      • 1.8 clear
      • 1.9 析构函数
      • 1.10 构造函数
      • 1.11 赋值运算符重载
      • 1.12 empty
      • 1.13 front && back
      • 1.14 完整代码
    • 二、vector与list的对比


前言

本篇文章我们要来模拟实现的是list类,它的底层是用带头结点的双向循环链表实现的。

一、list类的模拟实现

1.1 list的主体框架

既然我们是用双向循环链表实现的,那么每个结点肯定都存储着next、prev与data信息,那么接下来我们就来定义一个类对它的结点进行初始化操作。

template<class T>  // 模板参数T
struct list_node
{
	list_node<T>* _next; 	//list_node<T>* 是类型
	list_node<T>* _prev;
	T _data;

	list_node(const T& val = T())  // 匿名对象初始化
		: _next(nullptr)
		, _prev(nullptr)
		, _data(val)
	{}
};

我们把节点定义好之后,我们就来定义list类了,list类的成员变量只需要一个哨兵位的头结点就可以了。

template <class T>
class list
{
	typedef list_node<T> node;	
private:
		node* _head;	// 哨兵位头节点
};

1.2 无参构造函数

list()
{
	_head = new node;		// 申请一个节点
	_head->_next = _head;   // _head->_next指向自己
	_head->_prev = _head;   // _head->_prev也指向自己
}

1.3 push_back

双向链表的插入和删除都是非常好实现的,因为每个结点都有上一个节点和下一个节点的信息。这里我们要想实现尾插,我们要找到尾结点再改变它的指向就行了,非常的简单这里我就不做过多的赘述了。另外后续在我们实现insert和erase之后全都可以进行复用,这里只是先给大家打个样。

void push_back(const T& x)
{
	node* tail = _head->_prev;
	node* newnode = new node(x);

	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;
}

1.4 正向迭代器

有了尾插之后我们可以往链表里面插入数据,下面我们想遍历一下链表,我们知道list不支持[]下标访问,原因是因为它是不连续的空间,所以我们必须使用迭代器对它进行遍历。

我们知道在实现vector类(SGI版本)时,我们的迭代器是作为一个原生指针来使用的,而在vector类(P.J.版本)中我们的迭代器是自定义类型对原生指针的封装,但本质上它们都是在模拟指针的行为!!!那么在list类中迭代器到底充当什么角色呢?我们知道迭代器支持++ - -操作这是为了找到后一个数据和前一个数据的位置,对于list而言它是双向链表它的空间是不连续的,假设迭代器是一个原生指针的话,指针++ - -一步取决于指针所指向的类型,对于不连续的空间来说++ - -能否刚好指向下一个位置或者上一个位置一切都是未知数,因此我们的迭代器在list中是对自定义类型原生指针的封装!!!

我们先来看看SGI版本下对正向迭代器的封装源码:

在这里插入图片描述

好了,也许我们有些地方可能有些不太懂,而且标准库的源码采用了非常多的命名替换,这是命名规范的问题,接下来我们模拟实现的时候不采用标准库这种方式,我们尽量的实现简洁易懂些。

最原始的代码

template<class T>
struct __list_iterator
{
	typedef list_node<T> node;
	typedef __list_iterator<T> self;
	node* _node;

	__list_iterator(node* x)  // 初始化结点
		: _node(x)
	{}

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

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

	self& operator++()
	{
		_node = _node->_next;

		return *this;
	}

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

		return tmp;
	}

	self& operator--()
	{
		_node = _node->_prev;

		return *this;
	}

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

		return tmp;
	}

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

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

这是我们最原始的代码,那么大家知道为什么源码为什么会多出两个模板参数吗?这里我们实现的是正向迭代器,那么我们要实现const正向迭代器版本呢?难道要再去写一个__list_const_iterator类吗?

显然这样出现了大量的代码重复,我们是极其不支持这种实现方式的,所以我们必须想办法让他们之间可以进行复用,我们只需要改变一下返回值类型、参数类型就能实现iterator和const_iterator版本,这里模板参数的作用就体现出来了,我们可以添加一个模板参数,到时候我们可以实例化一份iterator和const_iterator。至于第三个模板参数是为了重载->运算符函数的,它同样的有T*版本和const T*版本。

为什么要重载->运算符?

struct AA
{
	int _a1;
	int _a2;

	AA(int a1, int a2)
		: _a1(a1)
		, _a2(a2)
	{}
};

void test()
{
	list<AA> lt;
	lt.push_back(AA(1, 1));
	lt.push_back(AA(2, 2));
	lt.push_back(AA(3, 3));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << (*it)._a1 << " " << (*it)._a2 << endl;  
		++it;
	}
	cout << endl;
}

我们可以看到上诉代码对于一个自定义类型要想访问它的成员变量就必须得写成(*it)._a1、(*it)._a2,先*it得到AA对象,再访问它的成员变量,这种写法是不是未免有些麻烦了?我们平常可以直接使用->去访问它的成员变量,就像这段代码我们可以写成it->_a1、it->_a2,但是我们此时未重载->运算符,所以为了方便使用这里我们还需要重载一下->运算符。

对于__list_iterator类我们可以重载->写出下面的代码:

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

但你有没有发现一些奇怪之处??

在这里插入图片描述

好了,关于为什么要重载->运算符这里我们已经讲清楚了,那么为什么这跟添加第三个模板参数有什么关系呢?原因很简单,一个T*版,一个const T*版,添加第三个模板参数Ptr也是为了复用T*版本。

所以最终我们的__list_iterator可以写成这种版本:

template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef list_node<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& s)
	{
		return _node == s._node;
	}

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

};

我们在list类中就可以实例化iterator和const_iterator这两种版本的迭代器,list类中迭代器的定义如下:

typedef list_node<T> node;
public:
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;

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

我们来进行测试一下:

在这里插入图片描述

我们可以看到对应的过程,当list<int>显式声明模板类时,此时我们的类模板就根据类型实例化出一个具体的类。

在这里插入图片描述

1.5 反向迭代器

我们知道C++追求极致的性能,既然能复用绝不会写出两份差不多的代码,,所以我们实现反向迭代器并不会像正向迭代器那样倒着来,而是去复用正向迭代器!!!

反向迭代器其实也是一种适配器,它可以适配出各种容器的反向迭代器,其中最重要的就是将正向迭代器作为底层结构来封装反向迭代器,反向迭代器 ++ 就复用正向迭代器的 - -,反向迭代器 - - 就复用正向迭代器的 ++。

在这里插入图片描述

我们的反向迭代器既然是作为适配器去使用,那么我们就把它封装到单独的一个类中对它进行模拟实现,并且正向迭代作为它的模板参数进行复用它的功能!!

反向迭代器的模拟实现

// iterator.h
namespace curry
{
	template<class Iterator, class Ref, class Ptr>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;
		Iterator _cur;  // _cur就是一个正向迭代器

		ReverseIterator(Iterator it)  
			: _cur(it)
		{}

		Ref operator*()
		{
			Iterator tmp = _cur;
			--tmp;

			return *tmp;
		}

		Self& operator++()
		{
			--_cur;

			return *this;
		}

		Self operator++(int)
		{
			Self tmp = *this;
			--_cur;

			return tmp;
		}

		Self& operator--()
		{
			++_cur;

			return *this;
		}

		Self operator--(int)
		{
			Self tmp = *this;
			++_cur;

			return tmp;
		}
		
		// 返回当前对象的地址
		Ptr operator->()
		{
			return &(operator*());
		}

		bool operator!=(const Self& s)
		{
			return _cur != s._cur;
		}

		bool operator==(const Self& s)
		{
			return _cur == s._cur;
		}
	};
}

只要知道了反向迭代器与正向迭代器的特性,我们就能够很容易的通过复用正向迭代器的成员函数来实现反向迭代器的成员函数!!同时反向迭代器其实解决了所有的双向迭代器的问题,因为只要将对应容器的正向迭代器作为反向迭代器的模板参数我们就能够对反向迭代器进行复用,所以我们之前的vector类的反向迭代器也能够直接使用它的正向迭代器复用实现!!这是一种非常巧妙的思想!!

1.6 insert

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

实现了insert接口函数,那么我们的push_back与push_front都是可以复用的。

push_back(int x)

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

push_front(int x)

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

list类与vector类的insert不同之处在于list类insert不会导致迭代器失效,因为它的空间的不连续的,并且没有挪动数据造成迭代器失效,所以我们也可以看到它的返回值为void,并不需要放回插入位置的迭代器。

1.7 erase

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

	prev->_next = next;
	next->_prev = prev;

	delete pos._node;

	return iterator(next);
}

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了,所以对于list类的erase会导致指向删除节点的迭代器失效,其他迭代器不会受到影响而vector类进行erase会导致当前位置或者后续迭代器失效,所以正确的解决办法是给迭代器重新赋值!!

实现了erase函数接口,pop_back()以及pop_front()就可以进行复用了。

pop_back()

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

pop_front()

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

1.8 clear

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);	// erase返回下一个位置的迭代器
	}
}

clear释放链表中的结点,_head哨兵位头结点除外。

1.9 析构函数

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

析构函数的作用是释放所有结点,我们可以先调用clear依次释放链表中的结点,最后再释放头结点。

1.10 构造函数

传统写法

void empty_init()
{
	// 创建并初始化哨兵位头节点
	_head = new node;
	_head->_prev = _head;
	_head->_next = _head;
}

// 拷贝构造传统写法 lt2(lt1)
list(const list<T>& lt)
{
	empty_init();

	for (auto& e : lt)	// 加引用避免自定义类型的拷贝构造
	{
		push_back(e);
	}
}

现代写法

template <class Iterator>  // 双向迭代器类型构造
list(Iterator first, Iterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

void swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);	// 交换哨兵位的头节点
}

// 拷贝构造现代写法 lt2(lt1)
list(const list<T>& lt)
{
	empty_init();	
	list<T> tmp(lt.begin(), lt.end());	// 迭代器区间初始化
	swap(tmp);	
}

1.11 赋值运算符重载

传统写法

list<T>& operator=(const list<T>& lt)
{
	if (this != &lt)	// 防止自己给自己赋值
	{
		clear();	// 清理数据
		for (auto& e : lt)
		{
			push_back(e);
		}
	}
	
	return *this;
}

现代写法

list<T>& operator=(list<T> lt)
{
	swap(lt);
	return *this;
}

一些常用的函数接口就讲到这里了,还有一些简单的函数接口读者下来也可以自己去尝试实现一下。

1.12 empty

bool empty()
{
	return _head->_next == _head &&
		_head->_prev == _head;
}

1.13 front && back

T& front()
{
	assert(!empty());
	return *begin();
}

const T& front() const
{
	assert(!empty());
	return *begin();
}

T& back()
{
	assert(!empty());
	return *(--end());
}

const T& back() const
{
	assert(!empty());
	return *(--end());
}

1.14 完整代码

// list.h

#include "iterator.h"

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

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

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<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& s)
		{
			return _node == s._node;
		}

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

	};

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;

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

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

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

		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(_head);
		}

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

		reverse_iterator rend()
		{
			return reverse_iterator(_head->_next);
		}

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

		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(_head->_next);
		}

		list()
		{
			empty_init();
		}

		// 现代写法
		list(const list<T>& lt)
		{
			empty_init();
			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

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

		list<T>& operator=(list<T> lt)
		{
			swap(lt);

			return *this;
		}

		// 释放所有结点
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

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

		// 释放结点,但是_head头结点不处理
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		void empty_init()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}

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

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

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

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

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

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

			return iterator(next);
		}
		
		T& front()
		{
			assert(!empty());
			return *begin();
		}

		const T& front() const
		{
			assert(!empty());
			return *begin();
		}

		T& back()
		{
			assert(!empty());
			return *(--end());
		}

		const T& back() const
		{
			assert(!empty());
			return *(--end());
		}

		bool empty()
		{
			return _head->_next == _head &&
				_head->_prev == _head;
		}
		
	private:
		node* _head;
	};
}

二、vector与list的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

以上就是本文的所有内容了,如有错处或者疑问欢迎大家在评论区相互交流orz~🙈🙈

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

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

相关文章

virtuoso61x中集成calibre

以virtuoso618为例&#xff0c;在搭建完电路、完成前仿工作之后绘制版图&#xff0c;版图绘制完成之后需要进行drc和lvs【仅对于学校内部通常的模拟后端流程而言】&#xff0c;一般采用mentor的calibre来完成drc和lvs。 服务器上安装有virtuoso和calibre&#xff0c;但是打开la…

探讨uniapp的路由与页面栈及参数传递问题

1首先引入页面栈 框架以栈的形式管理当前所有页面&#xff0c; 当发生路由切换的时候&#xff0c;页面栈的表现如下&#xff1a; 页面的路由操作无非&#xff1a;初始化、打开新页面、页面重定向、页面返回、tab切换、重加载。 2页面路由 uni-app 有两种页面路由跳转方式&am…

安装搭建私有仓库 Harbor

1&#xff0c;搭建Harbor时需要安装docker compose工具 wget https://storage.googleapis.com/harbor-releases/release- 1.7.0/harbor-offline-installer-v1.7.1.tgz在github下载Harbor最新版&#xff08;第一个&#xff09; 解压文件到 /usr/local tar xf harbor-offline-i…

Spring Boot多环境指定yml或者properties

Spring Boot多环境指定yml或者properties 文章目录 Spring Boot多环境指定yml或者properties加载顺序配置指定某个yml 加载顺序 ● application-local.properties ● application.properties ● application-local.yml ● application.yml application.propertes server.port…

Redis从基础到进阶篇(二)----内存模型与内存优化

目录 一、缓存通识 1.1 ⽆处不在的缓存 1.2 多级缓存 &#xff08;重点&#xff09; 二、Redis简介 2.1 什么是Redis 2.2 Redis的应用场景 三、Redis数据存储的细节 3.1 Redis数据类型 3.2 内存结构 3.3 内存分配器 3.4 redisObject 3.4.1 type 3.4.2 encoding 3…

[论文阅读笔记26]Tracking Everything Everywhere All at Once

论文地址: 论文 代码地址: 代码 这是一篇效果极好的像素级跟踪的文章, 发表在ICCV2023, 可以非常好的应对遮挡等情形, 其根本的方法在于将2D点投影到一个伪3D(quasi-3D)空间, 然后再映射回去, 就可以在其他帧中得到稳定跟踪. 这篇文章的方法不是很好理解, 代码也刚开源, 做一…

设计模式--单例模式(Singleton Pattern)

一、什么是单例模式 单例模式是一种创建型设计模式&#xff0c;它旨在确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。换句话说&#xff0c;单例模式限制了类的实例化次数为一个&#xff0c;并提供一种在应用程序中共享一个实例的方式。这对于需要只有…

一个简单的web应用程序的创建

一个简单的web应用程序的创建 1、数据库设计与创建1.1、数据库系统1.2、Navicat Premium1.3、Power Designer2、使用maven创建SpringBoot项目2.1、配置maven2.2、安装idea2.3、使用idea创建maven项目2.4、根据需要配置pom.xml文件、配置项目启动相关的文件2.5、写SpringBoot项目…

React入门 组件学习笔记

项目页面以组件形式层层搭起来&#xff0c;组件提高复用性&#xff0c;可维护性 目录 一、函数组件 二、类组件 三、 组件的事件绑定 四、获取事件对象 五、事件绑定传递额外参数 六、组件状态 初始化状态 读取状态 修改状态 七、组件-状态修改counter案例 八、this问…

Jmeter 接口测试总结

背景介绍 对于 Android 项目来说&#xff0c;使用的是 Java 开发&#xff0c;网络请求接口的数量庞大且复杂&#xff0c;测试人员无法很直观的判断、得出网络请求是否存在问题。另一方面&#xff0c;为了验证请求接口是否能够在大负荷条件下&#xff0c;长时间、稳定、正常的运…

3d max插件CG MAGIC中的蜂窝材质功能可提升效率吗?

工作中能提升效率也都是大家所想的&#xff0c;对于设计师的一个设计过程中&#xff0c;可能想怎么样可以更快呀&#xff0c;是哪个步骤慢了呢&#xff1f; 这样的结果只能说会很多&#xff0c;但是建模这个步骤&#xff0c;肯定是有多无少的。 为了让模型更加逼真&#xff0c…

探索AIGC人工智能(Midjourney篇)(二)

文章目录 利用Midjourney进行LOGO设计 用ChatGPT和Midjourney的AI绘画&#xff0c;制作儿童绘本故事 探索Midjourney换脸艺术 添加InsightFaceSwap机器人 Midjourney打造专属动漫头像 ChatGPT Midjourney画一幅水墨画 Midjourney包装设计之美 Midjourney24节气海报插画…

【电能质量扰动】基于ML和DWT的电能质量扰动分类方法研究(Matlab实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

APP爬虫之-Protobuf协议逆向解析

在做APP抓取时&#xff0c;会发现有的APP Response回来的数据有“加密”。不知道返回的内容是什么。 如下&#xff1a; 如上&#xff0c;内容不是明文的&#xff0c;没办法解析数据。APP常见的对数据加密有三种情况&#xff1a;第一种是&#xff0c;用诸如AES这类加密算法对数…

HodlSoftware-免费在线PDF工具箱 加解密PDF 集成隐私保护功能

HodlSoftware是什么 HodlSoftware是一款免费在线PDF工具箱&#xff0c;集合编辑 PDF 的简单功能&#xff0c;可以对PDF进行加解密、优化压缩PDF、PDF 合并、PDF旋转、PDF页面移除和分割PDF等操作&#xff0c;而且工具集成隐私保护功能&#xff0c;文件只在浏览器本地完成&…

vs2019 ,c++的STD库全局函数 _Pocma 的思考

&#xff08;1&#xff09;在阅读vs2019上的 STL库的 map 源码时&#xff0c;遇到了这个函数&#xff0c;之前&#xff0c;在别的源码中也经常出现这个函数。那么这个函数起什么作用呢&#xff1f; 在1880行&#xff0c;有对该函数的调用。其定义如下图&#xff1a;&#xff0…

Jetbrains IDE新UI设置前进/后退导航键

背景 2023年6月&#xff0c;Jetbrains在新发布的IDE&#xff08;Idea、PyCharm等&#xff09;中开放了新UI选项&#xff0c;我们勾选后重启IDE&#xff0c;便可以使用这一魔性的UI界面了。 但是前进/后退这对常用的导航键却找不到了&#xff0c;以前的设置方式&#xff08;Vi…

Java10(异常处理)

0.复习面向对象 1.异常的体系结构 异常&#xff1a;在Java语言中&#xff0c;将程序执行中发生的不正常情况.(开发中的语法错误和逻辑错误不是异常) 异常事件分两类&#xff08;它们上一级为java.lang.Throwable&#xff09;&#xff1a; Error Java虚拟机无法解决的严重问…

使用kubeadm方式快速部署一个K8S集群

目录 一、环境准备 二、环境初始化 三、在所有主机上安装相关软件 1、安装docker 2、配置k8s的yum源 3、安装kubelet、kubeadm、kubectl 四、部署Kubernetes Master 五、加入Kubernets Node 六、部署CNI网络插件 七、测试k8s集群 一、环境准备 我的是CentOS7系统&am…

【Linux】socket 编程基础

文章目录 &#x1f4d5; 网络间的通信&#x1f4d5; socket 是什么1. socket 套接字2. 套接字描述符3. 基本的 socket 接口函数3.1 头文件3.2 socket() 函数3.3 bind() 函数struct sockaddr主机序列与网络序列 3.4 listen() 函数3.5 connect() 函数3.6 accept() 函数IP 地址风格…