3---C++之list(逻辑梳理、简单使用演示、部分源码实现)

news2024/12/24 2:30:35

一、先决知识点1——认识list:

  • list底层实现是双向链表,但是不是循环链表。
  • list是否使用哨兵节点,是细节问题,C++标准并未规定。
  • list是链表,他的优势在于对节点的操作会十分灵活,因此它在需要频繁插入和删除元素的情况下非常高效。
  • list是链表的原因,他的元素分布不再是连续的空间,所以使用‘[ ]’来随机访问会使得性能消耗过大,所以C++标准不支持使用'[ ]'实现访问数据。

二、先决知识2——迭代器的分类: 

  • 根据迭代器的访问能力,可以将迭代器分为三类:单向迭代器、双向迭代器、随机访问迭代器。
  • 单向迭代器:只支持++,例如单链表
  • 双向迭代器:支持++和--,例如双向链表,红黑树
  • 随机访问迭代器:支持++/--/+/- 等,例如vector,string
  • 随机访问迭代器支持所有单向/双向迭代器的功能,因此可以向支持随机访问迭代器作为参数的函数,传递单向/双向迭代器作为参数。反之则不行。

三、简单的使用演示(vector/string中使用方法不变的不再赘述):

3.1排序——sort:

  • list内部实现了sort方法,默认升序。

  • 由于list是链表,他的sort在底层是归并排序而非快排。因此效率并不高,当数据量很大时,归并和快排的效率差距很大。数据量大时,先转换为vector排序后再转化为list可行。

3.2去重——unique:

  • 要求list有序,可以先sort再unique。

3.3删除——remove:

  • 删除所有指定值。

3.4转移——splice:

  • 把一个list的节点摘下来插到另一个list
void splice (iterator position, list& x);
void splice (iterator position, list& x, iterator i);
void splice (iterator position, list& x, iterator first, iterator last);

 四、底层功能实现(第一版,部分功能不是很完善,适合先了解逻辑):

4.1节点类:

  • 节点类,每个链表节点包含三个成员,分别是节点数据、上一个节点地址、下一个节点地址。
  • 创建哨兵节点时,哨兵节点不存储节点数据,所以可以使用缺省值;在C++中,内置类型也有默认构造,T()可以初始化一个类型为T的对象,调用其默认构造函数
template<class T>//模板
struct list_node//节点类
{
	T _data;
	list_node<T>* _next;
	list_node<T>* _prev;//一个指向上一个节点,一个指向下一个节点,一个存储节点值

	list_node<T>(const T& x = T())//内置类型也有匿名对象
		:_data(x)
		, _prev(nullptr)
		, _next(nullptr)
	{}
};

4.2迭代器类:

按照需求,实现的逻辑顺序:

1.重命名,简化书写:

		typedef list_node<T> Node;//对节点对象重命名
		typedef __list_iterator<T> self;//对自己重命名

2.构造函数和成员:

  • _node是一个指针,通过构造函数初始化,指向传过来的节点的地址。
		Node* _node;//创建一个指针

		__list_iterator(Node* node)
			:_node(node)//指针指向传递的节点对象
		{}

 3.++/--的运算符重载:

  • list是链表,要实现节点之间的迭代,就需要‘封装+运算符重载’。
  • 后置++/--,加上一个参数int来和前置++/--构成函数重载;
  • 由于后置++/--会创建临时对象,所以资源消耗会大于前置++/--,推荐使用前置代替后置。
  • 后置++/--,返回的是临时对象,所以不能使用引用返回。
		self& operator++()//向后挪动一个节点
		{
			_node = _node->_next;
			return *this;
		}

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

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

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

4.*的运算符重载:

  •  返回节点处的数据,如果使用引用返回,还可以修改节点数据。
		T& operator*()//获取节点对象处值,引用返回
		{
			return _node->_data;
		}

5. ==和!=的运算符重载:

  • 比较两个节点的地址是否相同。
		bool operator!=(const self& s)//判断两节点地址是否相等
		{
			return _node != s._node;
		}
		bool operator==(const self& s)//判断两节点地址是否相等
		{
			return _node == s._node;
		}

 6.完整代码:

	template<class T>
	struct __list_iterator//迭代器对象
	{
		typedef list_node<T> Node;//对节点对象重命名
		typedef __list_iterator<T> self;//对自己重命名

		Node* _node;//创建一个指针

		__list_iterator(Node* node)
			:_node(node)//指针指向传递的节点对象
		{}

		self& operator++()//向后挪动一个节点
		{
			_node = _node->_next;
			return *this;
		}

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

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

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

		T& operator*()//获取节点对象处值,引用返回
		{
			return _node->_data;
		}

		bool operator!=(const self& s)//判断两节点地址是否相等
		{
			return _node != s._node;
		}
		bool operator==(const self& s)//判断两节点地址是否相等
		{
			return _node == s._node;
		}
	};

4.3链表类:

按照需求,实现的逻辑顺序:

1.私有成员:

  • _head(哨兵节点)  +  _size(链表长度)
  • 哨兵节点不存储数据。
  • 链表长度‘_size’,在库中实现的list中并没有,加上这个私有成员,主要是方便返回链表的长度,无需再遍历一遍链表来计数链表长度。
	private:
		Node* _head;//哨兵节点
		size_t _size;

 2.重命名:

		typedef list_node<T> Node;//重命名节点
		typedef __list_iterator<T> iterator;//重命名迭代器

3.无参构造函数:

  • list()  +  empty_init()
  • 库中的无参构造调用了一个empty_init()函数初始化哨兵节点。
  • 初始化哨兵节点,将他的next和prev指针都指向自己即可,然后将链表长度初始化为0。
		void empty_init()//无参构造初始化哨兵节点
		{
			_head = new Node;//开辟一个空节点
			_head->_next = _head;//头节点的next和prev都指向自己
			_head->_prev = _head;
			_size = 0;
		}

		list()
		{
			empty_init();
		}

4. 插入函数:

  • 通过传过来的迭代器,在迭代器前面位置插入一个节点
  • 成功插入节点后,链表长度加一
  • 返回插入节点的迭代器,防止迭代器失效问题
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

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

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

			++_size;
			return iterator(newnode);
		}

 5.删除节点:

  • 断开迭代器位置的节点,并将其前后两个节点相互连接。
  • 删除节点后要将链表长度减一。
		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			delete cur;
			prev->_next = next;
			next->_prev = prev;
			--_size;
			return iterator(next);
		}

 6.获取链表首尾迭代器:

  • 首节点,就是哨兵节点的下一个节点。
  • 尾节点,就是哨兵节点。
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}

7.头插,尾插:

  •  头插,就是在哨兵节点的后一个节点插入节点,也就是begin()函数得到的迭代器的前一个位置插入节点。
  • 尾插,就是在哨兵节点前插入节点。
  • 复用insert即可。
		void push_back(const T& x)//尾插,重点在于处理好头尾新节点的指针指向
		{
			//第一版,在没有insert的情况下实现的版本
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(x);

			//newnode->_data = x;
			//newnode->_next = _head;
			//_head->_prev = newnode;

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

			//第二版,复用insert
			insert(end(), x);
		}

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

8.头删,尾删:

  • 和头插,尾插操作的位置相同。
  • 复用erase函数即可。
		void pop_back()
		{
			erase(begin());
		}

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

9.清空list对象:

  • 清理除了哨兵节点以外的所有节点。
  • 创建一个迭代器指向第一个节点(哨兵节点后一个节点),当这个迭代器不和哨兵节点重合,就持续删除节点。
  • 动态更新迭代器,由于erase会返回被删除节点的下一个节点,所以让迭代器每次都等于erase的返回值即可。
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

10.析构函数:

  • 析构函数,是删除所有节点,包括哨兵节点。
  • 复用clear函数后,再删除哨兵节点即可。
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

11.list对象节点数量:

		size_t size()
		{
			return _size;
		}

12.拷贝构造:

  • 先用empty_init函数初始化哨兵节点。
  • 再将源对象的数据一个个插入目标对象即可。
		list(list<T>& l)
		{
			empty_init();
			for (auto a : l)
			{
				push_back(a);
			}
		}

13.=的运算符重载:

  • 两个版本,第一个版本先将目标对象的节点全部删除,然后将源对象每个节点的值插入目标对象即可。
  • 第二个版本,实现一个swap函数,swap函数参数创建一个匿名对象,该匿名对象拷贝构造源对象;通过两个swap交换目标对象的哨兵节点和链表大小。交换完成后匿名对象被销毁。
		list<T>& operator=(list<int> l)
		{
			//if (*this != &l)
			//{
			//	clear();
			//	for (auto a : l)
			//	{
			//		push_back(a);
			//	}
			//}
			//return *this;

			//版本二:调用swap函数
			swap(l);
			return *this;
		}

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

14.完整代码:

	template<class T>
	class list//链表类
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T> iterator;
	public:
		void empty_init()//无参构造初始化头节点
		{
			_head = new Node;//开辟一个空节点
			_head->_next = _head;//头节点的next和prev都指向自己
			_head->_prev = _head;
			_size = 0;
		}

		list()
		{
			empty_init();
		}

		list(list<T>& l)
		{
			empty_init();
			for (auto a : l)
			{
				push_back(a);
			}
		}

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

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

		list<T>& operator=(list<int> l)
		{
			//if (*this != &l)
			//{
			//	clear();
			//	for (auto a : l)
			//	{
			//		push_back(a);
			//	}
			//}
			//return *this;

			//版本二:调用swap函数
			swap(l);
			return *this;
		}

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


		void push_back(const T& x)//尾插,重点在于处理好头尾新节点的指针指向
		{
			//第一版,在没有insert的情况下实现的版本
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(x);

			//newnode->_data = x;
			//newnode->_next = _head;
			//_head->_prev = newnode;

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

			//第二版,复用insert
			insert(end(), x);
		}

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

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

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

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

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

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

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

			++_size;
			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			delete cur;
			prev->_next = next;
			next->_prev = prev;
			--_size;
			return iterator(next);
		}

		size_t size()
		{
			return _size;
		}

	private:
		Node* _head;//头节点
		size_t _size;
	};

五、const迭代器:

5.1const迭代器和普通迭代器区别:

  •  常规迭代器允许遍历容器并修改容器中的元素。它的使用和指针类似,你可以解引用它来访问和修改元素。
  • const迭代器又名常量迭代器,常量迭代器不允许修改容器中的元素。它只能用于读取元素。这种迭代器用于确保代码的安全性和可读性,防止意外修改元素。
  • 两者的主要区别在于是否允许通过迭代器修改容器中的元素。

5.2const迭代器实现:

  • 要适配const对象和非const对象,就需要写两个版本的迭代器分别对应const对象和非const对象。由于我们写的普通迭代器是一个模板,就需要再写一个const迭代器模板。但是实际上两个模板之间的许多是相同的。
  • 我们可以通过添加模板参数,实现简化代码。
template<class T, class Ref, class Ptr>

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
  • 通过ref和ptr就可以实现通过传过来的参数,实例化具体的模板种类。同一个类模板,会根据传过来的模板参数不同,实例化出不同的类。
  • 就比如以上的两种传模板参数的方式,由于部分模板参数不同,实例化出的就是两个不同的类。

5.3修改*和[]运算符的重载:

  • 由于我们不知道通过模板具体实例化出的是普通迭代器还是const版本的迭代器,所以我们通过模板参数来替代返回类型。
		Ref operator*()//ref传过来的是T&或const T&
		{
			return _node->_data;
		}

		Ptr operator->()//ptr传过来的是T*或const T*
		{
			return &_node->_data;
		}

5.4添加begin()和end()的const版本:

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

六、打印函数和他的模板:

  •  我们如果要实现打印任意类型的list,就需要使用模板实现print_list()函数。
  • 下面这样写,运行不通过。
	template<typename T>
	void print_list(const list<T>& l)
	{
		list<T>::const_iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << ' ';
			++it;
		}
		cout << endl;
	}
  • 原因是:在C++中,当你在模板中使用依赖于模板参数的嵌套类型时,例如list<T>::const_iterator,编译器不知道这是一个静态成员或者一个静态函数还是一个类型。

  • 因此需要使用 typename 关键字明确告诉编译器它是一个类型。

	template<typename T>
	void print_list(const list<T>& l)
	{
		typename list<T>::const_iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << ' ';
			++it;
		}
		cout << endl;
	}
  • 上面是只针对list的打印模板,下面我们升级以下,让这个打印模板可以打印任意类型。
	template<typename Container>
	void print_container(const Container& con)
	{
		typename Container::const_iterator it = con.begin();
		while (it != con.end())
		{
			cout << *it << ' ';
			++it;
		}
		cout << endl;
	}

七、第二版(添加了const迭代器),完整的头文件代码:

测试函数可以写在demo命名空间中,在测试文件的主函数调用,要注意在测试文件包含要调用到的库。

#pragma once

namespace demo
{
	template<class T>//模板
	struct list_node//节点对象
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;//一个指向上一个节点,一个指向下一个节点,一个存储节点值

		list_node<T>(const T& x = T())//内置类型也有匿名对象
			:_data(x)
			, _prev(nullptr)
			, _next(nullptr)
		{}
	};

	template<class T, class Ref, class Ptr>//多加两个模板参数对应&和*的模板
	//template<class T>
	struct __list_iterator//迭代器对象
	{
		typedef list_node<T> Node;//对节点对象重命名
		typedef __list_iterator<T, Ref, Ptr> self;
		//typedef __list_iterator<T> self;//对自己重命名

		Node* _node;//创建一个指针

		__list_iterator(Node* node)
			:_node(node)//指针指向传递的节点对象
		{}

		self& operator++()//向后挪动一个节点
		{
			_node = _node->_next;
			return *this;
		}

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

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

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

		Ref operator*()//ref传过来的是T&或const T&
		{
			return _node->_data;
		}

		Ptr operator->()//ptr传过来的是T*或const T*
		{
			return &_node->_data;
		}

		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;//const迭代器的类
		//typedef __list_iterator<T> iterator;
		void empty_init()//无参构造初始化头节点
		{
			_head = new Node;//开辟一个空节点
			_head->_next = _head;//头节点的next和prev都指向自己
			_head->_prev = _head;
			_size = 0;
		}

		list()
		{
			empty_init();
		}

		list(const list<T>& l)//需要先实现const迭代器后,才能使用
		{
			empty_init();
			for (auto a : l)
			{
				push_back(a);
			}
		}

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

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

		list<T>& operator=(list<int> l)
		{
			//if (*this != &l)
			//{
			//	clear();
			//	for (auto a : l)
			//	{
			//		push_back(a);
			//	}
			//}
			//return *this;

			//版本二:调用swap函数
			swap(l);
			return *this;
		}

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


		void push_back(const T& x)//尾插,重点在于处理好头尾新节点的指针指向
		{
			//第一版,在没有insert的情况下实现的版本
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(x);

			//newnode->_data = x;
			//newnode->_next = _head;
			//_head->_prev = newnode;

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

			//第二版,复用insert
			insert(end(), x);
		}

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

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

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

		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator begin() const//const迭代器
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}

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

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

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

			++_size;
			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			delete cur;
			prev->_next = next;
			next->_prev = prev;
			--_size;
			return iterator(next);
		}

		size_t size()
		{
			return _size;
		}

	private:
		Node* _head;//头节点
		size_t _size;
	};

	template<typename Container>
	void print_container(const Container& con)
	{
		typename Container::const_iterator it = con.begin();
		while (it != con.end())
		{
			cout << *it << ' ';
			++it;
		}
		cout << endl;
	}
}

八、list和vector的比较:

  • list使用双向链表实现,节点存储不连续;vector使用动态数组实现,元素在内存中是连续存储的。
  • vector支持随机访问,访问某个元素效率O(1);list不支持随机访问,访问某个元素
    效率O(n)。
  • vector底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 ;list底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低。
  • list不会导致迭代器失效,vector删除、插入数据都会导致迭代器失效。

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

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

相关文章

压力测试JMeter

压力测试JMeter 1 下载JMeter1.1 测试计划1.2 JMeter Address Already in use 错误解决1.3 java 内存模型1.4 jconsole与jvisualvm1.5 优化方向1.6 Nginx动静分离 1 下载JMeter 官网地址&#xff1a;https://jmeter.apache.org/download_jmeter.cgi 运行apache-jmeter-5.6.3\…

鸿蒙应用Stage模型【应用/组件级配置】

应用/组件级配置 在开发应用时&#xff0c;需要配置应用的一些标签&#xff0c;例如应用的包名、图标等标识特征的属性。本文描述了在开发应用需要配置的一些关键标签。 应用包名配置 应用需要在工程的AppScope目录下的[app.json5配置文件]中配置bundleName标签&#xff0c;…

万字长文详解QUIC协议,为什么有了TCP我们还需要QUIC?

本文目录 1.前言2. HTTP缺点缺点一&#xff1a;建立连接的握手延迟大缺点二&#xff1a;多路复用的队首阻塞缺点三&#xff1a;TCP协议的更新滞后 3.TCP缺点3.QUIC优点一&#xff1a;避免队首阻塞的多路复用优点二&#xff1a;支持连接迁移优点三&#xff1a;可插拔的拥塞控制优…

YAML快速编写示例

一、案例 1.1 自主式创建service关联上方的pod 资源名称my-nginx-kkk命名空间my-kkk容器镜像nginx:1.21容器端口80标签njzb:my-kkk 1.1.1 创建一个demo文件夹 1.1.2 创建并获取模版文件 1.1.3 查看服务并编写yaml文件 1.1.4 编写yaml文件并部署&#xff0c;查看服务是否运行成…

Kotlin和Swift的前世一定是兄弟

Swift介绍 Swift这门编程语言主要用于iOS和MacOS的开发&#xff0c;可以说是非常流行的一门编程语言&#xff0c;我只想说&#xff0c;如果你会Kotlin&#xff0c;那么你学习Swift会非常容易&#xff0c;反之亦然。下载XCode&#xff0c;然后你就可以创建Playground练习Swift语…

Qt图像处理技术十:得到QImage图像的高斯模糊

效果图 参数为5 参数为20 原理 高斯模糊使用正态分布来分配周围像素的权重。具体来说&#xff0c;距离中心点越近的像素对最终结果的影响越大&#xff0c;权重也越高&#xff1b;随着距离的增加&#xff0c;权重逐渐减小。 这种权重分配方式确保了图像在模糊处理时&#xff0…

Docker(Centos7+)

先确定是否 Centos 7 及以上的版本 查看是否 ping 通外网 linux centos7运行下面的代码&#xff0c;基本上都可以正常安装 # 删除之前的docker残留 yum -y remove docker*yum install -y yum-utilsyum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/…

中草药识别系统Python+深度学习人工智能+TensorFlow+卷积神经网络算法模型

一、介绍 中草药识别系统。本系统基于TensorFlow搭建卷积神经网络算法&#xff08;ResNet50算法&#xff09;通过对10中常见的中草药图片数据集&#xff08;‘丹参’, ‘五味子’, ‘山茱萸’, ‘柴胡’, ‘桔梗’, ‘牡丹皮’, ‘连翘’, ‘金银花’, ‘黄姜’, ‘黄芩’&…

我给线程池管理框架hippo4j找bug

1 虚拟机参数不生效 hippo4j的docker启动脚本位于 docker/docker-startup.sh 。从下图可以看到 JAVA_OPT放在了jar包名 hippo4j-server.jar之后&#xff0c;而只有项目参数才放在jar包名之后。 实际上这里JAVA_OPT中包含虚拟机参数&#xff0c;而虚拟机参数要放在jar包名之前…

windows配置dns访问git , 加快访问速度保姆级教程

设置 DNS 访问 Git 需要修改电脑的 DNS 配置。下面是具体的操作流程&#xff1a; 第一步&#xff1a;打开命令提示符或终端窗口 在 Windows 系统中&#xff0c;可以按下 Win R 组合键&#xff0c;然后输入 “cmd”&#xff0c;按下 Enter 键打开命令提示符窗口。在 macOS 或 …

【tomcat 源码分析总结】

文章目录 tomcat官网路径目录结构介绍&#xff1a;Tomcat 系统架构 和 原理剖析http 的请求的处理过程 Tomcat 请求处理大致过程 tomcat官网路径 目录结构介绍&#xff1a; confserver.xml 端口的指定tomcat-users.xml 角色web.xml : tomcat 全局的xmllogging.properties 日志…

深入剖析 Kubernetes 原生 Sidecar 容器

1 Sidecar 容器的概念 sidecar 容器的概念在 Kubernetes 早期就已经存在。一个明显的例子就是 2015 年的这篇 Kubernetes 博客文章&#xff0c;其中提到了 sidecar 模式。多年来&#xff0c;sidecar 模式在应用程序中变得越来越普遍&#xff0c;使用场景也变得更加多样化。 其…

华为官网的自助申诉

代码&#xff1a;如下 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Document</title> …

乐观锁 or 悲观锁 你怎么选?

你有没有听过这样一句话&#xff1a;悲观者正确&#xff0c;乐观者成功​。那么今天我来分享下什么是乐观锁​和悲观锁。 乐观锁和悲观锁有什么区别&#xff0c;它们什么场景会用 乐观锁 乐观锁基于这样的假设&#xff1a;多个事务在同一时间对同一数据对象进行操作的可能性很…

Qt图像处理技术九:得到QImage图像的灰度直方图

效果 原理 得到灰度化值&#xff0c;将灰度化的值带入0-255内&#xff0c;增加&#xff0c;得到可视化图形 源码 // 绘制直方图 QImage drawHistogram(const QImage &image) {QVector<int> histogram(256, 0);// 计算图像的灰度直方图for (int y 0; y < image…

【linux】在linux操作系统下快速熟悉开发环境并上手开发工具——体验不一样的开发之旅

个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 祝福语&#xff1a;愿你拥抱自由的风 目录 vim编辑器 Linux编译器&#xff1a;gcc/g使用 gcc和g的选项 编译过程 动静态库的链接 Linux项目的自动化构建 生成可执行程序 清理可执行程序 Linux调试器-gdb使用 git和git…

【嵌入式硬件】DRV8874电机驱动

目录 1 芯片介绍 1.1 特性简介 1.2 引脚配置 1.3 最佳运行条件 2 详细说明 2.1 PMODE配置控制模式 2.1.1 PH/EN 控制模式 2.1.2 PWM 控制模式 2.1.3 独立半桥控制模式 2.2 电流感测和调节 2.2.1 IPROPI电流感测 2.2.2 IMODE电流调节 3.应用 3.1设计要求 3.2 设计…

C# FTP/SFTP 详解及连接 FTP/SFTP 方式示例汇总

文章目录 1、FTP/SFTP基础知识FTPSFTP 2、FTP连接示例3、SFTP连接示例4、总结 在软件开发中&#xff0c;文件传输是一个常见的需求。尤其是在不同的服务器之间传输文件时&#xff0c;FTP&#xff08;文件传输协议&#xff09;和SFTP&#xff08;安全文件传输协议&#xff09;成…

Scheduling Game Event

在游戏中管理事件&#xff1a;动画更新、对象碰撞等&#xff0c;如果没有清晰的理解事件是如何被组织和执行的&#xff0c;那么这将是一项艰巨的任务。这篇精华将解释调度器如何为你的游戏框架提供组织性和灵活性。 随着电脑游戏的日益复杂&#xff0c;实时事件和模拟几乎在今…

接口测试之XML响应断言

目录 XPath 基本语法XML 响应结果解析XML 响应结果断言 XML 响应数据 如何提取 AddResult 中的值&#xff1f; <soap:Body><AddResponse xmlns"http://tempuri.org/"><AddResult>4</AddResult></AddResponse> </soap:Body> …