C++ list链表的使用和简单模拟实现

目录

前言

1. list的简介

2.list讲解和模拟实现

2.1 默认构造函数和push_back函数

2.2 迭代器实现

2.2.1 非const正向迭代器

2.2.2 const正向迭代器

2.2.3 反向迭代器

2.3 插入删除函数

2.3.1 insert和erase

2.3.2 push_back pop_back push_front pop_front

2.4 构造函数,赋值拷贝函数,析构函数

总结


前言

这篇文章讲述常用容器list的使用和一些重要部分的简单模拟实现,仅仅只是了解一些实现方法。内容丰富,干货多多。


1. list的简介

  1. list是序列容器,允许在序列内的任何位置进行常量时间的插入和删除操作,以及两个方向的迭代。
  2. 列表容器被实现为双链表;双链表中每个元素存储在互不相关的几点钟,在节点中通过指针指向其前一个元素和后一个元素
  3. 它们与forward_list非常相似:主要区别在于forward_list对象是单链表,因此它们只能向前迭代,让其更简单高效。
  4. 与其他基本标准序列容器(array、vector和deque)相比,列表在容器内的任何位置插入、提取和移动元素方面通常表现更好。
  5. 与其他序列容器相比,列表和forward_lists的主要缺点是它们无法通过位置直接访问元素;例如,要访问列表中的第八个元素,必须从已知位置(如开始或结束)迭代到该位置,这需要在两者之间的距离上花费线性时间。它们还消耗一些额外的内存来保存与每个元素相关联的链接信息(对于包含小元素的大型列表来说,这可能是一个重要因素)。

  •  下面是展示list类的内部成员变量结构,并且之后要简单模拟实现,所以加上一个命名空间,防止与库中list起冲突。
  • list即是双向链表结构,我们使用模版定义一个ListNode类,内部成员变量有两个指针,指向前一个元素和后一个元素,还有存储T类型的元素。在list类中,我们可以使用typedef简化LIstNode<T>成Node。Node类型的指针变量_head,代表第一个哨兵位结点,不存放元素。
  • 其中ListNode使用struct而不用class,因为struct默认权限是公有的,且ListNode类里面的所有内容list都会使用到,所以直接用struct定义这个类。
namespace User
{
    template<class T>
    struct ListNode
    {
        ListNode*<T> _next;
        ListNode*<T> _prev;
        T _data;
        //...
    };

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

2.list讲解和模拟实现

2.1 默认构造函数和push_back函数

默认构造函数构造一个只含有哨兵位结点的链表,并且哨兵位结点的的两个指针指向自身。我们暂时使用库里的list,先创建一个int类型的list容器,调用的是默认构造函数,之后尾插几个整数。遍历整个链表一般使用迭代器,还可以使用范围for。因为物理结构是不连续的,不支持使用下标方括号遍历链表。

list();//默认构造函数

void test_list1()
{
    std::list<int> lt1;
    lt1.push_back(1);
    lt1.push_back(2);
    lt1.push_back(3);
    lt1.push_back(4);

    list<int>::iterator it1 = lt1.begin();
    while (it1 != lt1.end())
    {
        cout << *it1 << " ";
        ++it1;
    }
    cout << endl;

    for (const auto& e : lt1)
    {
        cout << e << " ";
    }
    cout << endl;
}

运行结果如下:

  • 实现一个list的默认构造函数,使用new动态开辟一个Node类型元素的内存空间,传入一个T类型的值调用LIstNode类的构造函数。下面写的是T()表示如果是自定义类型,就调用该自定义类型的构造函数,如果是内置类型,会初始化一个该类型的值,整型一般初始化为0。
  • 既然list构造函数中调用了LIstNode类的构造函数,LIstNode的构造函数也需要实现。构造函数的参数是const修饰的T类型引用的data变量,顺便给上一个缺省值T(),跟上面的用法相同。
  • push_back函数先动态开辟一个新结点,传入x初始化。然后再改变结点指向的位置即可。
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 list
{
    typedef ListNode<T> Node; 
public:
    list()
    {
        _head = new Node(T());
        _head->_next = _head;
        _head->_prev = _head;
    }

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

		// head tail newnode
		tail->_next = newnode;
		newnode->_prev = tail;

		newnode->_next = _head;
		_head->_prev = newnode;
	}
    
private:
    Node* _head;
};

2.2 迭代器实现

2.2.1 非const正向迭代器

list因本身物理空间不连续,需要通过结点存储前后元素地址,进行访问。那么list的迭代器需要封装成一个类,重载前置++,后置--,!=,==,*等符号。

template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T> Self;
	Node* _node;
    //...
};
  • typedef可以帮我们简化类名称,方便之后使用,重命名ListNode<T>类为Node,还有将ListIterator<T>类重命名为Self。

    ListIterator(Node* node)
		:_node(node)
	{}
  • 构造函数实现一个有参的构造函数,使用初始化列表初始化_node。

template<class T>
class list
{
	typedef ListNode<T> Node;

public:
	typedef ListIterator<T> iterator;

	iterator begin()
	{
		//iterator it(_head->_next);
		//return it
        //下面是隐士类型转换调用
		return iterator(_head->_next);
	}

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

};
  • 在list中提供begin和end函数,对迭代器进行初始化,或者遍历时做判断。
  • begin函数中,可以先创建一个iterator对象,再返回该对象。也可以直接使用匿名对象,内部传结点参数,进行隐式类型转换。
  • end函数提供最后一个元素的下一个位置,所以用哨兵位结点进行构造。

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

	// it++ 后置
	Self operator++(int)
	{
		Self tmp(*this);
		_node = _node->_next;

		return tmp;
	}

    // --it 前置
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
  • 前置++,表示访问后一个元素,让_node赋值为_next指针即可。返回类型使用Self的引用,不用再次拷贝,更高效,记得要返回*this。
  • 后置++,返回当前的位置,但是迭代器自身指向下一个元素。创建一个Self类型的tmp变量对*this进行拷贝构造,然后自身赋值为下一个结点指针,返回tmp。
  • 前置--,表示访问前一个元素。做法跟前置++类似,让_node赋值为它的_prev指针即可。也是使用Self引用类型返回。

    T& operator*()
	{
		return _node->_data;
	}
  • *符号,表示要解引用到Node类型存储的元素。返回类型使用T类型的引用,直接返回data的别名,减少拷贝。

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

	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
  • !=和==可直接返回两个迭代器结点地址比较结果。

  • ->符号比较特别,表示当前结点元素数据的地址,可以像结构体->一样使用。
	T* operator->()
	{
		return &_node->_data;
	}

虽然operator->看起来比较奇特,但是也有它的应用场景。

  • 看下面的代码,我们定义一个Pos,Pos是表示坐标位置的类。我们创建一个数据类型为Pos的list容器,尾插三个Pos类对象数据。
  • 使用迭代遍历。如果在while循环使用*it,进行打印,程序会报错,这是因为Pos类没有匹配的cout流插入函数,在全局作用域下也没有重载cout函数。
  • 所以可以用(*it)._row这样的方式打印两个整型坐标,*符号返回的是结点的数据别名,在这里是Pos类变量的别名,我们可以用.符号进行打印,但是看起来太别扭了。
  • 重载的->就派上用场了,我们使用->就像结构体指针访问内部对象。不过正常来说应该是两个->符号,代码中有展开示例。为了可读性,只用写一个->符号就可以使用。
struct Pos
{
	int _row;
	int _col;

	Pos(int row = 0, int col = 0)
		:_row(row)
		, _col(col)
	{}
};

void test_list2()
{
	list<Pos> lt1;
	lt1.push_back(Pos(100, 100));
	lt1.push_back(Pos(200, 100));
	lt1.push_back(Pos(300, 100));

	//临时对象可以调用非静态成员函数,但是不能引用定义
	list<Pos>::iterator it = lt1.begin();
	while (it != lt1.end())
	{
        //cout<< *it <<endl;//error,这样写无法运行成功。

		//可以这样写但是太别扭了
		//cout << (*it)._row << ":" << (*it)._col << " ";

		//为了可读性,省略了一个->
		cout << it->_row << ":" << it->_col << " " << endl;

		//cout << it->->_row << ":" << it->->_col << " "; 展开就成了下面的样子
		cout << it.operator->()->_row << ":" << it.operator->()->_col << " "<< endl;
		++it;
	}
	cout << endl;
}

运行结果如下:

2.2.2 const正向迭代器

  • 非const对象的迭代器迭代器指向的元素可以修改,但是有时候容器内部元素加上const修饰,不可以修改。因此,还要提供const类型的迭代器。我们可以使用上面的代码,只要将operator*和operator->函数的返回类型加上const修饰就行。
template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T> Self;

	Node* _node;

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

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

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

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

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

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

template<class T>
class list
{
	typedef ListNode<T> Node;

public:
	typedef ListIterator<T> iterator;
	typedef ListIterator<T> const_iterator;

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

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

  • 但是这两类迭代器就只有operator*和operator->函数的返回类型不同而已,返回类型上有区别,其实我们可以借助模版解决。
  • 只需要在定义两个模版类型参数Ref和Ptr,分别表示引用和指针,在list中分别传不同的参数表示iterator和const_iterator。
	template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr> Self;

		Node* _node;

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

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

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

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

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

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

	template<class T>
	class list
	{
		typedef ListNode<T> Node;

	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;

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

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

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

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

2.2.3 反向迭代器

反向迭代器的模拟实现和正向迭代器类似,需要注意++和--跟正向迭代器的的方向完全反过来,还要注意迭代器begin和end函数给的结点位置。

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

		Reverse_ListIterator(Node* node)
			:_node(node)
		{}

		Self& operator++()//前置++,赋值为前面的结点的指针
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator++(int)//后置++
		{
			Self tmp(_node);
			_node = _node->_prev;

			return tmp;
		}

		Self& operator--()
		{
			_node = _node->_next;

			return *this;
		}

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

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

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

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


template<class T>
class list
{
	typedef ListNode<T> Node;

public:
	typedef Reverse_ListIterator<T, T&, T*> reverse_iterator;
	typedef Reverse_ListIterator<T, const T&, const T*> reverse_const_iterator;

	reverse_const_iterator rbegin() const
	{
		return reverse_const_iterator(_head->_prev);
	} 

	reverse_const_iterator rend() const
	{
		return reverse_const_iterator(_head);
	}

	reverse_iterator rbegin()
	{
		return reverse_iterator(_head->_prev);
	}

	reverse_iterator rend()
	{
		return reverse_iterator(_head);
	}
    //...
};

2.3 插入删除函数

2.3.1 insert和erase

  • insert函数是在指定结点位置前插入一个新的结点。需要改变各个结点的指向问题,可以创建几个变量表示结点。
  • 因为该函数传入的参数类型是迭代器,所以还要考虑迭代器失效的问题。解决方法是返回指向新结点位置的迭代器类型的变量。下面采用的是匿名对象。
iterator insert(iterator pos, const T& x)
{
	Node* cur = pos._node;
	Node* newnode = new Node(x);
	Node* prev = cur->_prev;

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

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

	return iterator(newnode);
}

  • erase函数是删除传入的迭代器变量指向的结点。也可以创建几个变量表示结点,改变各个结点的指向问题。
  • 返回类型也是迭代器,返回的是被删除结点的下一个结点迭代器类型变量。
//erase后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 iterator(next);
}

2.3.2 push_back pop_back push_front pop_front

完成了对insert和erase函数的实现,头部或者尾部插入和删除的函数都可以复用这两个函数。

  • push_back函数是在尾部插入一个结点,insert是在指向结点前插入新结点,因为end函数是哨兵位结点,在它前面插入新结点就是尾插,所以传end函数作为迭代器即可。
  • pop_back函数是删除最后一个结点,最后一个结点在哨兵位结点之前,即end函数所代表的迭代器,所以需要--。
  • push_front和pop_front同理。
void push_back(const T& x)
{
    insert(end(), x);
}

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

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

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

写一个测试函数

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

void test_list3()
{
	list<int> lt1;
	//按需实例化
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.push_back(5);
	Func(lt1);

	lt1.push_front(10);
	lt1.push_front(20);
	lt1.push_front(30);
	Func(lt1);

	lt1.pop_back();
	lt1.pop_back();
	lt1.pop_back();
	Func(lt1);

	lt1.pop_front();
	lt1.pop_front();
	lt1.pop_front();
	Func(lt1);
}

运行结果如下:

2.4 构造函数,赋值拷贝函数,析构函数

构造函数这块,有四种类型,默认构造,initializer初始化器构造,迭代器区间构造和拷贝构造。

  • 首先,封装一个创建哨兵位头结点的函数,因为后面的构造函数都需要用到。再写一个遍历打印整个链表的函数。
  • 默认构造函数直接调用empty_init函数,初始化一个头结点。
void Printlist(const list<int> lt)
{
    for (const auto& e: lt1)
    {
        cout << e << " ";    
    }
    cout << endl;
}

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

list()
{
	empty_init();
}

  • initializer_list函数是针对花括号内部有相同类型的构造函数
list(initializer_list<T> il)
{
	empty_init();
	for (const auto& e : il)
	{
		push_back(e);
	}

}

void test1()
{
    list<int> lt1 = { 1,2,3,4,5,6,7,8 };
    
    Printlist(lt1);
}

  • 需要注意判断条件只能是!=,不能是<之类的符号。
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

void test2()
{
    list<int> lt1 = { 1,2,3,4,5,6,7,8 };
    Printlist(lt2);
    
    list<int> lt2(lt1.begin(), lt2.end());
    Printlist(lt2);
}

  • 拷贝构造函数,可以使用范围for尾插元素实现。
//lt1(lt2)
list(const list<T>& lt)
{
    empty_init();
    for (const auto& e : lt)
    {
        push_back(e);
    }
}

void test3()
{
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
    Printlist(lt2);

    list<int> lt2(lt1);
    Printlist(lt2);
}

  • 赋值拷贝函数可以先删除原链表的结点,然后再一个个尾插结点。
  • 但是更好的方法就是用正常模版类型参数list接受被拷贝对象,此时的lt就是待拷贝对象的一份临时拷贝,然后使用交换函数交换头结点,因为lt是一份临时拷贝,函数结束后会自动调用析构函数销毁原链表的内容。
//lt1 = lt3
list<T>& operator=(list<T> lt)
{
    std::swap(_head, lt._head);
    return *this;
}

void test3()
{
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
    Printlist(lt2);

    list<int> lt2;
    lt2 = lt1;
    Printlist(lt2);
}

  • 析构函数需要先一个个释放结点,再释放头结点。
void clear()
{
    auto it = begin();
    while (it != end())
    {
        it = erase(it);
        ++it;
    }
}

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


总结

看完整篇文章,我相信你对list的使用和一些底层实现原理有进一步的了解,你也可以尝试自己手撕一个简单的list容器实现。树欲静而风不止,既然我们无法改变大势,但是我们可以改变自己!

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

ee192b61bd234c87be9d198fb540140e.png

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

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

相关文章

LLVM Cpu0 新后端3

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1V_tZkt9uvxo5bnUufhMQ_Q?…

173.二叉树:找树左下角的值(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr, right(nullptr) {}* Tree…

强!推荐一款开源接口自动化测试平台:AutoMeter-API !

在当今软件开发的快速迭代中&#xff0c;接口自动化测试已成为确保代码质量和服务稳定性的关键步骤。 随着微服务架构和分布式系统的广泛应用&#xff0c;对接口自动化测试平台的需求也日益增长。 今天&#xff0c;我将为大家推荐一款强大的开源接口自动化测试平台: AutoMete…

【中国开源生态再添一员】天工AI开源自家的Skywork

刚刚看到《AI高考作文出圈&#xff0c;网友票选天工AI居首》&#xff0c;没想到在Huggingface中发现了Skywork大模型。天工大模型由昆仑万维自研&#xff0c;是国内首个对标ChatGPT的双千亿级大语言模型&#xff0c;天工大模型通过自然语言与用户进行问答式交互&#xff0c;AI生…

用c语言实现通讯录

目录 静态简易通讯录 代码&#xff1a; 功能模块展示&#xff1a; 设计思路&#xff1a; 动态简易通讯录&#xff08;本质顺序表&#xff09; 代码&#xff1a; 扩容模块展示&#xff1a; 设计思路&#xff1a; 文件版本通讯录 代码&#xff1a; 文件模块展示&#x…

突破网络屏障:掌握FRP内网穿透技术

1.FRP介绍 1.frp是什么 frp 是一款高性能的反向代理应用&#xff0c;专注于内网穿透。它支持多种协议&#xff0c;包括 TCP、UDP、HTTP、HTTPS 等&#xff0c;并且具备 P2P 通信功能。使用 frp&#xff0c;您可以安全、便捷地将内网服务暴露到公网&#xff0c;通过拥有公网 I…

【C++ STL】模拟实现 string

标题&#xff1a;【C :: STL】手撕 STL _string 水墨不写bug &#xff08;图片来源于网络&#xff09; C标准模板库&#xff08;STL&#xff09;中的string是一个可变长的字符序列&#xff0c;它提供了一系列操作字符串的方法和功能。 本篇文章&#xff0c;我们将模拟实现STL的…

【机器学习】消息传递神经网络(MPNN)在分子预测领域的医学应用

1. 引言 1.1. 分子性质预测概述 分子性质预测是计算机辅助药物发现流程中至关重要的任务之一&#xff0c;它在许多下游应用如药物筛选和药物设计中发挥着核心作用&#xff1a; 1.1.1. 目的与重要性&#xff1a; 分子性质预测旨在通过分子内部信息&#xff08;如原子坐标、原…

汇总 |国内外医疗器械网络安全法规与标准

国内外关于医疗器械网络安全的法规和标准日益完善&#xff0c;旨在确保医疗器械在全生命周期内的网络安全&#xff0c;保障患者信息的安全和隐私&#xff0c;以及医疗器械的正常运行。不同国家和地区的法规和标准各有侧重&#xff0c;但都强调了医疗器械制造商、开发者、经营者…

contos7使用docker安装vulhub

contos7下使用docker安装vulhub 1. 安装docker 1. 更新yum &#xff08;1&#xff09;切换root用户 su root &#xff08;2&#xff09;更新yum yum update 2. 卸载旧版本的docker sudo yum remove docker sudo yum remove docker-client sudo yum remove docker-clien…

反AI浪潮中的新机遇:Cara艺术社区异军突起

近日,一个名为Cara的艺术社区在网络上迅速走红,其独特的反AI定位吸引了大量创意人士。在AI技术日益普及的今天,Cara社区反其道而行之,致力于打造一个无AI侵害的创作和交流环境。这一创新模式不仅赢得了艺术家的青睐,也为国内创业者提供了新的思考角度。 一、精准定位,守…

Linux shell编程基础

Shell 是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言。Shell 是指一种应用程序&#xff0c;这个应用程序提供了一个界面&#xff0c;用户通过这个界面访问 Linux 内核的服务。 Shell 脚本&#x…

Elasticsearch中各种query的适用场景

Elasticsearch 提供了丰富的 Query 类型&#xff0c;以满足各种搜索需求。以下列举一些常见的 Query 类型&#xff0c;并分析其区别和应用场景&#xff1a; 一、 几个常用的基本Query 1. Term Query 应用场景: 查找包含特定词语的文档&#xff0c;适合精确匹配单个词语的场景…

【C++第九课 - vector】vector介绍、vector使用,vector的底层实现、杨辉三角、全排列、只出现一次的数字

目录 一、vector的介绍二、vector的使用1、vector的构造函数2、vector的插入和三种遍历方式3、开空间4、insert5、find6、erase补充 三、vector的底层实现1、成员变量2、构造函数3、push_back4、访问方式5、pop_back6、insert - pos位置插入x7、resize8、拷贝构造9、赋值10、er…

【第13章】SpringBoot实战篇之项目部署

文章目录 前言一、准备1. 引入插件2. 打包3. 启动4. 后台启动 二、跳过测试模块三、外置配置文件1.引入插件2.忽略配置文件3. 外置配置文件 总结 前言 项目部署需要把项目部署到Linux服务器上&#xff0c;SpringBoot项目通过Maven打包即可快速生成可运行Jar包程序。 一、准备 …

每日一题——Python实现PAT乙级1042 字符统计(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 优点 缺点和改进建议 时间复杂度分析 空间复杂度分析 改进后的代码 我…

【Androi】安卓发展历程详解

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

轻NAS玩客云使用Docker部署小雅并挂载到AList详细流程分享

文章目录 前言1. 本地部署AList2. AList挂载网盘3. 部署小雅alist3.1 Token获取3.2 部署小雅3.3 挂载小雅alist到AList中 4. Cpolar内网穿透安装5. 创建公网地址6. 配置固定公网地址 前言 本文主要介绍如何在安装了CasaOS的玩客云主机中部署小雅AList&#xff0c;并在AList中挂…

YOLOv8_obb的训练、验证、预测及导出[旋转目标检测实践篇]

1.旋转目标检测数据集划分和配置 从上面得到的images和labels数据还不能够直接训练,需要按照一定的比例划分训练集和验证集,并按照下面的结构来存放数据,划分代码如下所示,该部分内容和YOLOv8的训练、验证、预测及导出[目标检测实践篇]_yolov8训练测试验证-CSDN博客是重复的…

LNMP与动静态网站介绍

Nginx发展 Nginx nginx http server Nginx是俄罗斯人 Igor Sysoev(伊戈尔.塞索耶夫)开发的一款高性能的HTTP和反向代理服务器。 Nginx以高效的epoll.kqueue,eventport作为网络IO模型&#xff0c;在高并发场景下&#xff0c;Nginx能够轻松支持5w并发连接数的响应&#xff0c;并…