stl_list类(使用+实现)(C++)

list

  • 一、list-简单介绍
  • 二、list的常用接口
    • 1.常见构造
    • 2.iterator的使用
    • 3.Capacity和Element access
    • 4.Modifiers
    • 5.list的迭代器失效
  • 三、list实现
  • 四、vector 和 list 对比
  • 五、迭代器
    • 1.迭代器的实现
    • 2.迭代器的分类(按照功能分类)
    • 3.反向迭代器
      • (1)、包装逻辑
      • (2)、代码
    • 注意

一、list-简单介绍

list是一个可以在常熟范围内任意位置进行插入和删除的序列式容器。底层是带头双向循环链表(链接中有对带头双向循环链表的逻辑分析)。

二、list的常用接口

1.常见构造

(constructor)构造函数声明接口说明
list()无参构造
list(size_type n, const T& val = T()构造并初始化n个val
list(const list& x)拷贝构造
list(InputIterator first, InputIterator last)使用迭代器[first, last)区间中的元素初始化构造list

test:

void test_constructor()
{
	list<int> lt1;                       //无参构造
	list<int> lt2(4, 25);                //构造并初始化n个val
	list<int> lt3(l2.begin(), l2.end()); //用lt2的[first, last)区间构造
	list<int> lt4(l3);                   //拷贝构造
}

2.iterator的使用

注意:list的迭代器和vector string不同。vector和string的迭代器都是原生指针,而list的迭代器是一个封装起来的指针。

iterator的使用接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator(即end()位置),返回最后一个元素下一个位置的reverse_iterator(即begin()位置)

一个正向迭代器一个反向迭代器,注意使用规则,前者++迭代器向后移动,后者++迭代器向前移动。

test:

void test_iterator()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	list<int> lt(arr, arr + sz);
	
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//反向迭代器
	list<int>::reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

iterator示意图

3.Capacity和Element access

函数名称接口说明
size返回list中的有效节点个数
empty判断是否为空
函数名称接口说明
front返回list的第一个节点中,值的引用
back返回list的最后一个节点中,值的引用

test:

void test_capacity_elementAccsee()
{
	list<int> lt;
	lt.push_back(77);
	lt.push_back(22);

	//头节点的值-尾节点的值
	lt.front() -= lt.back();
	
	cout << lt.front() << endl;
	cout << "size:" << lt.size() << endl;
	cout << "empty:" << lt.empty() << endl;
}

4.Modifiers

函数名称接口说明
push_front头插
pop_front头删
push_back尾插
pop_back尾删
erase删除pos位置的数据
insert在pos之前插入val
swap交换两个list的元素
clear情况list的有效元素

test: 头插 头删 尾插 尾删

void test_Modifiers1()
{
	list<int> lt;
	//头插
	lt.push_front(1);
	lt.push_front(2);
	//尾插
	lt.push_back(10);
	lt.push_back(20);

	//范围for
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	//头删
	lt.pop_front();
	//尾删
	lt.pop_back();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

test: 插入 删除 交换 清理

void test_Modifiers2()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);    
	
	Print(lt);

	//获取链表第二个节点
	list<int>::iterator pos = lt.begin();
	cout << *(++pos) << endl;

	//在pos前插入值为100的元素
	lt.insert(pos, 100);
	Print(lt);

	//在pos前插入值5个5
	lt.insert(pos, 5, 5);
	Print(lt);

	//在pos前插入[v.begin(), v.end())区间的元素
	vector<int> v{ 6, 6, 6 ,6 };
	lt.insert(pos, v.begin(), v.end());
	Print(lt);

	//删除操作
	//删除pos位置上的元素     --   特别注意一下迭代器失效问题(下个知识点介绍)
	lt.erase(pos);
	Print(lt);

	// 删除list中[begin, end)区间中的元素,即删除list中的所有元素
	lt.erase(lt.begin(), lt.end());
	Print(lt);

	list<int> lt1{ 6, 6, 6 ,6 };
	lt1.swap(lt);
	cout << "lt::empty:" << lt.empty() << endl;
	cout << "lt1::empty:" << lt1.empty() << endl;

	lt.clear();
	cout << "new_lt::empty:" << lt.empty() << endl;
}

5.list的迭代器失效

在list中迭代器失效即迭代器指向的节点是无效的,即该节点被删除了。因为list的底层是带头双向循环列表,所以在插入元素时,不会导致liet迭代器失效,只有删除时指向删除节点的那个迭代器失效,其他的迭代器不受影响。

错误代码:

void test_iterator_invalid()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	list<int> lt(arr, arr + sz);
	
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		//erase()执行完之后,it所指向的节点已经被删除,因此it无效,下次使用必须重新赋值
		lt.erase(it);

		++it;   //err   迭代器失效
	}
}

改正:

void test_iterator_invalid()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	list<int> lt(arr, arr + sz);

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		lt.erase(it++);  //it = lt.erase(it);
	}
}

三、list实现

list类整体实现代码
注意:这里就不单列出来一部分成员函数进行介绍了,因为重要的在string类和vector类都进行了重点讲解。

反向迭代器在list类实现中不进行介绍,在最后单列一个知识点讲解

#include <assert.h>

namespace kpl
{
	// List的节点类
	template<class T>
	struct ListNode
	{
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _val;
		
		//初始化
		ListNode(const T& val = T())
			: _prev(nullptr)
			, _next(nullptr)
			, _val(val)
		{}
	};
	
	//List 的迭代器:将原生态指针进行封装
	template<class T, class Ref, class Ptr>
	class ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr> Self;

	public:
		// Ref 和 Ptr 类型重定义,在实现反向迭代器时便于使用。就不需要再模板传参时传Ref和Ptr
		typedef Ref Ref;
		typedef Ptr Ptr;

		// 构造
		ListIterator(Node* node = nullptr)
			: _node(node)
		{}

		// 在模板中多加一个参数Ref的原因是:区分const返回
		Ref operator*() 
		{ 
			return _node->_val;
		}

		//Ptr:区分const返回
		Ptr operator->() 
		{ 
			return &(operator*()); 
		}

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

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

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

		Self operator--(int)
		{
			Self temp(*this);
			_node = _node->_prev;
			return temp;
		}

		// 比较
		bool operator!=(const Self& l)const
		{ 
			return _node != l._node;
		}

		bool operator==(const Self& l)const
		{ 
			return _node != l._node;
		}

		Node* _node;
	};

	//反向迭代器借用正向迭代器实现
	template<class Iterator>
	class ReverseListIterator
	{
	public:
		typedef typename Iterator::Ref Ref;
		typedef typename Iterator::Ptr Ptr;
		typedef ReverseListIterator<Iterator> Self;

		// 构造
		ReverseListIterator(Iterator it)
			: _it(it)
		{}

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

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

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

		Self operator++(int)
		{
			Self temp(*this);
			--_it;
			return temp;
		}

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

		Self operator--(int)
		{
			Self temp(*this);
			++_it;
			return temp;
		}

		// 比较
		bool operator!=(const Self& l)const
		{
			return _it != l._it;
		}

		bool operator==(const Self& l)const
		{
			return _it != l._it;
		}

		Iterator _it;
	};

	
	//list类模板的实现
	template<class T>
	class list
	{
		typedef ListNode<T> Node;

	public:
		// 正向迭代器
		// 这里就也可以看出传三个模板参数的原因。不值得再去写一个const修饰的模板,普通的迭代器和const修饰的迭代器区别就在于部分成员函数的返回值,所以多传递两个参数即可
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T&> const_iterator;

		// 反向迭代器
		typedef ReverseListIterator<iterator> reverse_iterator;
		typedef ReverseListIterator<const_iterator> const_reverse_iterator;
	public:
		// List的构造
		list()
		{
			CreateHead();  //因为很多地方都会使用这部分代码,所以进行封装,方便调用
		}

		list(int n, const T& value = T())
		{
			CreateHead();
			for (int i = 0; i < n; ++i)
				push_back(value);
		}

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

		//拷贝构造
		list(const list<T>& l)
		{
			CreateHead();

			// 用l中的元素构造临时的temp,然后与当前对象交换。也可以一次赋值
			list<T> temp(l.begin(), l.end());
			swap(temp);
		}

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

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

		// List的迭代器
		iterator begin() 
		{ 
			//or return _head->_next;
			return iterator(_head->_next); 
		}

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

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

		const_iterator end()const
		{ 
			//or return _head;
			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());
		}

		// capacity相关
		size_t size()const
		{
			//在实现size时,也可以通过给list类增减一个size_t类型的成员变量,然后返回
			
			Node* cur = _head->_next;
			size_t count = 0;
			while (cur != _head)
			{
				count++;
				cur = cur->_next;
			}

			return count;
		}

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

		void resize(size_t newsize, const T& data = T())
		{
			size_t oldsize = size();
			if (newsize <= oldsize)
			{
				// 有效元素个数减少到newsize
				while (newsize < oldsize)
				{
					pop_back();
					oldsize--;
				}
			}
			else
			{
				while (oldsize < newsize)
				{
					push_back(data);
					oldsize++;
				}
			}
		}

		// List的元素访问操作
		// 注意:List不支持operator[]
		T& front()
		{
			return _head->_next->_val;
		}

		const T& front()const
		{
			return _head->_next->_val;
		}

		T& back()
		{
			return _head->_prev->_val;
		}

		const T& back()const
		{
			return _head->_prev->_val;
		}


		// List的插入和删除
		void push_back(const T& val) 
		{ 
			insert(end(), val); 
		}

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

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

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

		// 在pos位置前插入值为val的节点
		iterator insert(iterator pos, const T& val)
		{
			Node* pNewNode = new Node(val);
			Node* cur = pos._node;
			// 先将新节点插入
			pNewNode->_prev = cur->_prev;
			pNewNode->_next = cur;
			pNewNode->_prev->_next = pNewNode;
			cur->_prev = pNewNode;
			return iterator(pNewNode);
		}

		// 删除pos位置的节点,返回该节点的下一个位置
		iterator erase(iterator pos)
		{
			assert(pos != end());

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

			prev->_next = next;
			next->_prev = prev;
			delete cur;
			
			return next;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
			_head->_next = _head->_prev = _head;
		}
		

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

	private:
		void CreateHead()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}
	private:
		Node* _head;
	};
}

四、vector 和 list 对比

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

五、迭代器

1.迭代器的实现

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  1. 原生态指针,比如:vector
  2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
    • 指针可以解引用,迭代器的类中必须重载operator*()
    • 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
    • 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
    • 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()。至于operator–()/operator–(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list(单链表)就不需要重载–。
    • 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

2.迭代器的分类(按照功能分类)

迭代器的分类

  1. 单向迭代器的功能相对较少,只能进行逐个元素的遍历和访问操作。它只支持t运算符来移动到下一个元素,不支持–运算符来回退到前一个元素。因此,单向迭代器无法进行逆向遍历和随机访问元素的操作。
  2. 双向迭代器相比于单向迭代器功能更加强大,它支持双向即可以使用++运算符向前移动到下一个元素,也可以使用–运算符向后移动到前一个元素。因此,双向迭代器可以进行逆向遍历和前向遍历操作。
  3. 随机迭代器是迭代器的最高级别,功能最丰富。它除了支持双向迭代器的所有操作外,还可以进行随机访问,即可以使用]运算符来访问任意位置的元素。此外,随机迭代器还可以进行迭代器之间的算术运算,比如可以使用+、-运算符来计算迭代器之间的距离。

所以,单向迭代器功能最少,只能逐个访问元素;双向迭代器比单向迭代器功能更强大,可以双向移动;随机迭代器是最高级别的迭代器,功能最丰富,除了双向移动外还能进行随机访问和算术运算操作。

3.反向迭代器

(1)、包装逻辑

在这里插入图片描述

(2)、代码

template<class Iterator>
	class ReverseListIterator
	{
		// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量
		// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
		// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
		//typename和class的区别会在模板的博客中进行介绍
	public:
		typedef typename Iterator::Ref Ref;
		typedef typename Iterator::Ptr Ptr;
		typedef ReverseListIterator<Iterator> Self;
	public:

		// 构造
		ReverseListIterator(Iterator it)
			: _it(it)
		{}

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

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

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

		Self operator++(int)
		{
			Self temp(*this);
			--_it;
			return temp;
		}

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

		Self operator--(int)
		{
			Self temp(*this);
			++_it;
			return temp;
		}

		bool operator!=(const Self& l)const
		{
			return _it != l._it;
		}

		bool operator==(const Self& l)const
		{
			return _it != l._it;
		}

		Iterator _it;
	};

注意

	//迭代器对箭头进行了重载,返回的是一个指针
	Ptr operator->()
	{
		return &(operator*());
	}

虽然重载了->但是在使用的时候,会发现一个问题。
eg:

struct A
{
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{}

	int _a1;
	int _a2;
};

void test_iterator()
{
	list<A> lt;
	lt.push_back(A(1, 1));
	lt.push_back(A(2, 2));
	lt.push_back(A(3, 3));
	list<A>::iterator it = lt.begin();

	while (it != lt.end())
	{
		cout << (*it)._a1 << " " << (*it)._a2 << endl;
		cout << it->_a1 << " " << it->_a2 << endl;
		++it;
	}
	cout << endl;
}

在这里插入图片描述

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

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

相关文章

Rust中的高吞吐量流处理

本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库&#xff0c;还使用这些库实现了一个流处理程序。 最后&#xff0c;作者介绍了如何通过测量空闲和阻塞时间来优化流处理程序的性能&#xff0c;并将这些内容同步至…

Stephen Wolfram:嵌入的概念

The Concept of Embeddings 嵌入的概念 Neural nets—at least as they’re currently set up—are fundamentally based on numbers. So if we’re going to to use them to work on something like text we’ll need a way to represent our text with numbers. And certain…

快速实现一个div的水平垂直居中

效果 实现 给父盒子宽高和flex&#xff0c;子盒子margin&#xff1a;auto 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sc…

No112.精选前端面试题,享受每天的挑战和学习

文章目录 说一说JavaScript有几种方法判断变量的类型&#xff1f;说一说defer和async区别&#xff1f;HTTP&#xff08;超文本传输协议&#xff09;是什么&#xff1f;说一下浏览器输入URL发生了什么&#xff1f;一只青蛙一次可以跳上1级台阶&#xff0c;也可以跳上2级。求该青…

rest api client code generator

一、搜索&#xff1a;REST API Client Code Generator 二、 安装成功后 配置java环境和node环境

软件使用 | jupyter notebook 使用技巧大全

本文记录使用 jupyter notebook 的使用方法。 更新&#xff1a;2023 / 8 / 6 软件使用 | jupyter notebook 使用技巧大全 图片表格参考链接 图片 插入图片并控制大小和对其方式 1: <img src"https://www.domain.com/image_file_name.jpg" alt"ImageFile&…

MFC第二十八天 WM_SIZE应用,CFrameWnd类LoadFrame的方法,PreCreateWindow窗口预处理,Frame-view窗口视图

文章目录 WM_SIZE应用通过WM_SIZE消息实现窗口布局管理通过控件属性实现窗口布局管理 CFrameWnd类CFrameWnd类简介OnCreate和OnCreateClient的方法注册时的要素与窗口设置PreCreateWindow创建窗口的预处理函数 附录预处理函数的结构体CFrameWnd::LoadFrame与CreateFrame WM_SIZ…

程序环境和预处理(含C语言程序的编译+链接)--2

文章前言&#xff1a; 上章我们把 程序的翻译环境 程序的执行环境 C语言程序的编译链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符 #和##的介绍 的相关知识进行了梳理讲解&#xff0c;接下来被把剩余知识 命令定义 …

机器学习:训练集与测试集分割train_test_split

1 引言 在使用机器学习训练模型算法的过程中&#xff0c;为提高模型的泛化能力、防止过拟合等目的&#xff0c;需要将整体数据划分为训练集和测试集两部分&#xff0c;训练集用于模型训练&#xff0c;测试集用于模型的验证。此时&#xff0c;使用train_test_split函数可便捷高…

数字孪生的「三张皮」问题:数据隐私、安全与伦理挑战

引言 随着数字化时代的来临&#xff0c;数据成为了当今社会的宝贵资源。然而&#xff0c;数据的广泛使用也带来了一系列隐私、安全与伦理挑战。数字孪生作为一种虚拟的数字化实体&#xff0c;通过收集和分析大量数据&#xff0c;模拟和预测现实世界中的各种情境&#xff0c;为…

【Jenkins】Jenkins 安装

Jenkins 安装 文章目录 Jenkins 安装一、安装JDK二、安装jenkins三、访问 Jenkins 初始化页面 Jenkins官网地址&#xff1a;https://www.jenkins.io/zh/download/ JDK下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 清华源下载RPM包地址&#xff…

简单认识ELK日志分析系统

一. ELK日志分析系统概述 1.ELK 简介 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kiabana 三个开源工具配合使用&#xff0c; 完成更强大的用户对日志的查询、排序、统计需求。 好处&#xff1a; &#xff08;1&#xff09;提高安全…

【每天40分钟,我们一起用50天刷完 (剑指Offer)】第四十八天 48/50【字符串处理】【最低公共祖先】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

DQN原理和代码实现

参考&#xff1a;王树森《强化学习》书籍、课程、代码 1、基本概念 折扣回报&#xff1a; U t R t γ ⋅ R t 1 γ 2 ⋅ R t 2 ⋯ γ n − t ⋅ R n . U_tR_t\gamma\cdot R_{t1}\gamma^2\cdot R_{t2}\cdots\gamma^{n-t}\cdot R_n. Ut​Rt​γ⋅Rt1​γ2⋅Rt2​⋯γn−…

基于 APN 的 CXL 链路训练

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

Dockerfile构建mysql

使用dockerfile构建mysql详细教学加案例 Dockerfile 文件 # 使用官方5.6版本&#xff0c;latest为默认版本 FROM mysql:5.6 #复制my.cof至容器内 ADD my.cnf /etc/mysql/my.cof #设置环境变量 密码 ENV MYSQL_ROOT_PASSWORD123456my.cof 文件 [mysqld] character-set-server…

LNMP搭建

LNMP&#xff1a;目前成熟的企业网站的应用模式之一&#xff0c;指的是一套协同工作的系统和相关软件 能够提供静态页面服务&#xff0c;也可以提供动态web服务。 这是一个缩写 L linux系统&#xff0c;操作系统。 N nginx网站服务&#xff0c;也可也理解为前端&#xff0c…

企业计算机服务器中了locked勒索病毒怎么办,如何预防勒索病毒攻击

计算机服务器是企业的关键信息基础设备&#xff0c;随着计算机技术的不断发展&#xff0c;企业的计算机服务器也成为了众多勒索者的攻击目标&#xff0c;勒索病毒成为当下计算机服务器的主要攻击目标。近期&#xff0c;我们收到很多企业的求助&#xff0c;企业的服务器被locked…

uni-app、H5实现瀑布流效果封装,列可以自定义

文章目录 前言一、效果二、使用代码三、核心代码总结 前言 最近做项目需要实现uni-app、H5实现瀑布流效果封装&#xff0c;网上搜索有很多的例子&#xff0c;但是代码都是不够完整的&#xff0c;下面来封装一个uni-app、H5都能用的代码。在小程序中&#xff0c;一个个item渲染…

Godot 4 源码分析 - Path2D与PathFollow2D

学习演示项目dodge_the_creeps&#xff0c;发现里面多了一个Path2D与PathFollow2D 研究GDScript代码发现&#xff0c;它主要用于随机生成Mob var mob_spawn_location get_node(^"MobPath/MobSpawnLocation")mob_spawn_location.progress randi()# Set the mobs dir…