C++第七讲:STL--list的使用及模拟实现

C++第七讲:STL--list的使用及模拟实现

  • 1.list的使用
    • 1.1list是什么
    • 1.2构造、析构、赋值运算符重载
    • 1.3迭代器
    • 1.4empty、size、max_size
    • 1.5front、back
    • 1.6assign -- 代替
    • 1.7push_back和emplace_back
    • 1.8emplace
    • 1.9insert、erase、swap、resize、clear
    • 1.10find
    • 1.11splice
    • 1.12remove、remove_if
    • 1.13unique
    • 1.14merge
    • 1.15库中sort的不同使用
    • 1.16reverse
  • 2.list的模拟实现
    • 2.1list模拟实现思路
    • 2.2迭代器的实现
    • 2.3->问题
    • 2.4迭代器封装问题
    • 2.5const_iterator实现问题
    • 2.6多个swap问题
  • 3.list的模拟实现代码

1.list的使用

list的使用和vector、string中的接口大概相同,我们只会详细讲述list的不同之处

1.1list是什么

list的底层其实是一个带头双向循环链表:
在这里插入图片描述

下面我们来看list的使用

1.2构造、析构、赋值运算符重载

在这里插入图片描述

int main()
{//构造list<int> ls1(3);//就相当于list<int> ls1(3, 0),表示构造一个容器,其中有三个结点,每一个结点都是0的副本//赋值运算符重载(内容改变 + 数据改变)list<int> ls2(4, 2);cout << ls1.size() << endl;//3cout << ls2.size() << endl;//4ls2 = ls1;cout << ls1.size() << endl;//3cout << ls2.size() << endl;//3return 0;
}

1.3迭代器

在这里插入图片描述

list实现的迭代器中,begin和end所指向的位置为:
在这里插入图片描述

int main()
{list<int> ls1(3, 2);list<int>::iterator it = ls1.begin();while (it != ls1.end()){cout << *it << " ";it++;}cout << endl;//C++11是支持initilizer_list进行构造的list<int> ls2({ 1, 2, 3, 4, 5 });list<int>::iterator it1 = ls2.begin();while (it1 != ls2.end()){cout << *it1 << " ";it1++;}cout << endl;return 0;
}

1.4empty、size、max_size

在这里插入图片描述

1.5front、back

在这里插入图片描述

1.6assign – 代替

int main()
{list<int> ls1(3, 2);list<int>::iterator it = ls1.begin();while (it != ls1.end()){cout << *it << " ";it++;}cout << endl;//assign的使用(用新的数据、空间代替原来的list)ls1.assign(2, 1);//迭代器区间、n个val、初始化列表ls1.assign({ 1, 2, 3, 4 });it = ls1.begin();while (it != ls1.end()){cout << *it << " ";it++;}cout << endl;return 0;
}

1.7push_back和emplace_back

我们在刷题的时候可能会看到emplace_back的使用,所以我们在这一先简单了解一下,之后我们会详细讲述该函数的功能

我们先看一下两个函数在使用时的差异:

class Pos
{
public://表示坐标int _row;int _col;//我们写一个构造和拷贝构造,如果调用构造或拷贝构造就显示出被调用Pos(int row, int col):_row(row), _col(col){cout << "Pos(int row, int col)" << endl;}Pos(const Pos& p):_row(p._row), _col(p._col){cout << "	Pos(const Pos& p)" << endl;}
};
int main()
{//创建一个Pos类型的对象list<Pos> ls1;Pos p1(1, 2);ls1.push_back(p1);ls1.push_back(Pos(1, 2));ls1.push_back({2, 3});Pos p2(1, 2);ls1.emplace_back(p2);ls1.emplace_back(Pos(1, 2));ls1.emplace_back(2, 3);return 0;
}

在这里插入图片描述
我们再看一下两个函数的性能差异:
我们都知道,当形参传入实参时,会创建一个临时对象,然后临时对象再传入实参,比如double b = 1.0, int& a = b,这时会发生报错,因为传入给a的实际上时b的一个临时对象,所以要加上const修饰
在这里插入图片描述
所以说,对于最后一种情况,使用emplace_back的性能会好点,但是其它情况两者性能没什么区别
在这里插入图片描述
上面这几个函数都一样,不再讲

1.8emplace

在这里插入图片描述
该函数的作用就是构造 + 插入:

int main()
{list<int> ls1({1, 2, 3, 4, 5});ls1.emplace(ls1.begin(), 2);for (auto e : ls1){cout << e << " ";}cout << endl;// 2 1 2 3 4 5return 0;
}

1.9insert、erase、swap、resize、clear

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.10find

在list的库中,并没有实现find,所以我们还是要使用标准库中的find函数:

int main()
{list<int> ls1({ 1, 2, 3, 4 });for (auto e : ls1){cout << e << " ";}cout << endl;//找到并删除元素int x;cin >> x;auto it = find(ls1.begin(), ls1.end(), x);if (it != ls1.end()){ls1.erase(it);}for (auto e : ls1){cout << e << " ";}cout << endl;return 0;
}

1.11splice

在这里插入图片描述
该函数的作用为转移,将list x转移到迭代器指向的position位置、将迭代器指向的i元素转移到list x中的position位置、转移迭代器,要注意的是,因为是转移,如果是将x2中的数据转移到x1中,那么x2就是一个空的list

该函数在一般情况下不太常用,但是对于LRU(近期最少使用,如果最近使用过,就将使用的那一个程序向前排)情况很好用:

int main()
{//LRUlist<int> ls1({ 1, 2, 3, 4, 5 });for (auto e : ls1){cout << e << " ";}cout << endl;//splice函数不仅可以实现两个list之间的转移,还可以做到在自己的list中进行转移int x;while (cin >> x){auto pos = find(ls1.begin(), ls1.end(), x);if(pos != ls1.end())	{ls1.splice(ls1.begin(), ls1, pos);}for (auto e : ls1){cout << e << " ";}cout << endl;}return 0;
}

当出现一直循环的情况时,我们可以按ctrl+z进行截断,也可以使用ctrl+c,VS中ctrl+z和ctrl+c都是将流中的标识符设置为结束,这样就不能再提取东西了,而有的编译器下,ctrl+c的作用是直接将程序强制结束,会比较暴力
在这里插入图片描述

1.12remove、remove_if

在这里插入图片描述
在list中,remove的特别之处在于,remove删除时不用再传入迭代器,而是传入值进行删除:

int main()
{list<int> ls1({ 1, 2, 3, 4 });ls1.remove(1);for (auto e : ls1){cout << e << " ";}cout << endl;return 0;
}

在这里插入图片描述
remove_if以后再提

1.13unique

在这里插入图片描述
该函数可以对list进行去重操作:

int main()
{list<int> ls1({ 1, 1, 2, 2, 3, 4, 5 });for (auto e : ls1){cout << e << " ";}cout << endl;ls1.unique();for (auto e : ls1){cout << e << " ";}cout << endl;//1 2 3 4 5return 0;
}

1.14merge

在这里插入图片描述
merge必须针对于排序好了的list进行使用,然后对这两个list进行合并,合并方法为挑小的放进其中一个list中,这会使另一个list中没有数据:

int main()
{list<double> first, second;first.push_back(3.1);first.push_back(2.2);first.push_back(2.9);second.push_back(3.7);second.push_back(7.1);second.push_back(1.4);first.sort();second.sort();first.merge(second);//将second中的数据合并到first中for (auto e : first){cout << e << " ";}cout << endl;for (auto e : second)//second中没有数据{cout << e << " ";}cout << endl;return 0;
}

如果想要从大到小进行排序,这样使用:

int main()
{list<double> first, second;first.push_back(3.1);first.push_back(2.2);first.push_back(2.9);second.push_back(3.7);second.push_back(7.1);second.push_back(1.4);//less和greater是两个结构体,可以传入sort中判断按从小到大排还是从大到小进行排序//first.sort(less<double>());//second.sort(less<double>());first.sort(greater<double>());second.sort(greater<double>());first.merge(second, greater<double>());//将second中的数据合并到first中for (auto e : first){cout << e << " ";}cout << endl;for (auto e : second)//second中没有数据{cout << e << " ";}cout << endl;return 0;
}

1.15库中sort的不同使用

在这里插入图片描述

我们可以发现,报错了,这是因为:迭代器其实也分为好几种,不同的迭代器有着不同的使用:
在这里插入图片描述
其实sort函数的形参我们就可以看出来迭代器的不同:
在这里插入图片描述

1.16reverse

在这里插入图片描述
逆置操作

2.list的模拟实现

2.1list模拟实现思路

在这里插入图片描述

2.2迭代器的实现

list的迭代器实现起来有点不同,因为list中的迭代器不能只是一个指针,如果只是一个指针的话,那么迭代器++,会找不到下一个结点,所以在这里,我们要将迭代器整合成一个类,在类中完成解引用、++的重载,因为迭代器中的数据会经常使用,按照惯例,我们还使用结构体封装

在这里插入图片描述

2.3->问题

我们看下面的代码:

class Pos
{
public://表示坐标int _row;int _col;Pos(int row = 1, int col = 1):_row(row), _col(col){cout << "Pos(int row, int col)" << endl;}Pos(const Pos& p):_row(p._row), _col(p._col){cout << "	Pos(const Pos& p)" << endl;}
};int main()
{Mine::list<Pos> lt2;Pos p1(1, 1);lt2.push_back(p1);lt2.push_back(Pos(2, 2));lt2.push_back({ 3,3 });Mine::list<Pos>::iterator it2 = lt2.begin();while (it2 != lt2.end()){//*重载时返回的是Pos data,所以*it访问的是Pos的对象,这样我们就可以对它的数据进行访问了cout << (*it2)._row << ":" << (*it2)._col << endl;//这样是可以执行的++it2;}cout << endl;return 0;
}

那么可以使用->来访问吗?当然可以,只是我们需要重载一下:
在这里插入图片描述
这个重载的实现非常奇怪,而使用起来是这样:

T* operator->()
{return &_node->_data;
}int main()
{Mine::list<Pos> lt2;Pos p1(1, 1);lt2.push_back(p1);lt2.push_back(Pos(2, 2));lt2.push_back({ 3,3 });Mine::list<Pos>::iterator it2 = lt2.begin();while (it2 != lt2.end()){cout << (*it2)._row << ":" << (*it2)._col << endl;//cout << it2->_row << ":" << it2->_col << endl;//为了可读性,特殊处理,省略了一个->//第一个->的含义是调用它的重载,第二个->的含义为访问内容//cout << it2->->_row << ":" << it2->->_col << endl;//err:语法错误cout << it2->_row << ":" << it2->_col << endl;cout << it2.operator->()->_row << ":" << it2.operator->()->_col << endl;++it2;}cout << endl;return 0;
}

2.4迭代器封装问题

对于容器,它们的底层有:数组、链表、树和哈希等等,但是我们可以发现:它们被访问的方式都有迭代器,这些迭代器的底层实现可能不同,但是对于我们使用者而言,却没什么区别,这就是C++中的封装概念:

在这里插入图片描述

2.5const_iterator实现问题

我们能不能直接这样写:
在这里插入图片描述
肯定不能,因为这里的const迭代器是一个类,不是一个指针的别名,如果直接这样实现的话,我们看下边:
在这里插入图片描述
所以我们只能够再实现一个const_iterator:

template<class T>
struct list_const_iterator
{typedef list_node<T> Node;typedef list_const_iterator Self;Node* _node;list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_data;}Self& operator++()//前置++{_node = _node->_next;return *this;}Self& operator--()//前置--{_node = _node->_prev;return *this;}Self operator++(int)//后置++{Self ret(*this);_node = _node->_next;return ret;}Self operator--(int)//后置--{Self ret(*this);_node = _node->_prev;return ret;}bool operator!=(const Self& s){return _node != s._node;}const T* operator->(){return &_node->_data;}
};typedef list_const_iterator<T> const_iterator;
const_iterator begin() const
{return const_iterator(_head->_next);
}
const_iterator end() const
{return	const_iterator(_head);
}
//n个val的构造
list(int n, const T& val = T())
{empty_init();for (size_t i = 0; i < n; i++){push_back(val);}
}int main()
{//const对象不能够push_back,因为const对象只有在定义时才有一次初始化的机会const Mine::list<int> ls1(10, 1);//ls1.push_back(1); errMine::list<int>::const_iterator it = ls1.begin();while (it != ls1.end()){cout << *it << " ";it++;}cout << endl;return 0;
}

但是,我们会发现:const迭代器的实现和普通的迭代器只有在*和->两个运算符重载时才有所不同,而不同只有它们的返回值,因为这两个都是访问操作符,const的作用就是防止访问的数据进行改变,那么我们可不可以将他们进行合并呢,我们看库是怎么实现的:
在这里插入图片描述
我们根据库中的实现自己实现一下:
在这里插入图片描述

2.6多个swap问题

这个问题我们之前谈过,就是为什么list中实现了两个swap函数:
在这里插入图片描述

3.list的模拟实现代码

#pragma once
#include <assert.h>namespace Mine
{//使用struct和class的唯一区别是,struct中的对象默认是public,而class中的默认是private//这里有一个惯例:如果一个对象中的成员全是全局对象,那么就将它们封装在一个struct中template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;//默认构造函数,因为后边new出结点时,可能会传参进行初始化对象list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}};//迭代器的实现//typedef list_iterator<T, T&, T*> iterator;//typedef list_iterator<T, const T&, const T*> const_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* node):_node(node){}Ref operator*(){return _node->_data;}Self& operator++()//前置++{_node = _node->_next;return *this;}Self& operator--()//前置--{_node = _node->_prev;return *this;}Self operator++(int)//后置++{Self ret(*this);_node = _node->_next;return ret;}Self operator--(int)//后置--{Self ret(*this);_node = _node->_prev;return ret;}bool operator!=(const Self& s){return _node != s._node;}Ptr operator->(){return &_node->_data;}};//template<class T>//struct list_const_iterator//{//	typedef list_node<T> Node;//	typedef list_const_iterator Self;//	Node* _node;//	list_const_iterator(Node* node)//		:_node(node)//	{}//	const T& operator*()//	{//		return _node->_data;//	}//	Self& operator++()//前置++//	{//		_node = _node->_next;//		return *this;//	}//	Self& operator--()//前置--//	{//		_node = _node->_prev;//		return *this;//	}//	Self operator++(int)//后置++//	{//		Self ret(*this);//		_node = _node->_next;//		return ret;//	}//	Self operator--(int)//后置--//	{//		Self ret(*this);//		_node = _node->_prev;//		return ret;//	}//	bool operator!=(const Self& s)//	{//		return _node != s._node;//	}//	const T* operator->()//	{//		return &_node->_data;//	}//};反向迭代器的实现//template<class T>//struct list_reserve_iterator//{//	//反向迭代器中仍然也是一个Node//	typedef list_node<T> Node;//	typedef list_reserve_iterator Self;//	Node* _node;//	list_reserve_iterator(Node* node)//		:_node(node)//	{}//	T& operator*()//	{//		return _node->_data;//	}//	Self& operator++()//	{//		_node = _node->_prev;//		return *this;//	}//	Self& operator--()//	{//		_node = _node->_next;//		return *this;//	}//	Self operator++(int)//	{//		Self tmp(*this);//		_node = _node->_prev;//		return tmp;//	}//	Self operator--(int)//	{//		Self tmp(*this);//		_node = _node->_next;//		return tmp;//	}//	bool operator!=(const Self& i1)//	{//		return _node != i1._node;//	}//};///重点:template<class T>class list{typedef list_node<T> Node;private:Node* _head;public:typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;//typedef list_reserve_iterator<T> reserve_iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return	iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return	const_iterator(_head);}//reserve_iterator rbegin()//{//	return reserve_iterator(_head->_prev);;//}//reserve_iterator rend()//{//	return reserve_iterator(_head);//}void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;}list(){empty_init();}//析构函数iterator erase(iterator pos){assert(pos != end());//需要保存pos位置的上一个结点,以及下一个结点Node* del = pos._node;Node* prev = del->_prev;Node* next = del->_next;prev->_next = next;next->_prev = prev;delete del;return iterator(next);}void clear(){auto it = begin();while (it != end()){it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}//尾插void push_back(const T& x){/*Node* newnode = new Node(x);Node* ptail = _head->_prev;newnode->_prev = ptail;newnode->_next = _head;ptail->_next = newnode;_head->_prev = newnode;*/insert(end(), x);}//头插void push_front(const T& x){insert(begin(), x);}//n个val的构造list(int n, const T& val = T()){empty_init();for (size_t i = 0; i < n; i++){push_back(val);}}//解决浅拷贝问题list(const list<T>& ls){//直接尾插即可empty_init();for (auto e : ls){push_back(e);}}//赋值运算符重载list<T>& operator=(list<T> ls){swap(ls);return *this;}//头删void pop_front(){erase(begin());}//尾删void pop_back(){erase(--end());}//在pos位置进行插入iterator insert(iterator pos, const T& val = T()){Node* newnode = new Node(val);Node* pcur = pos._node;Node* prev = pcur->_prev;newnode->_prev = prev;newnode->_next = pcur;prev->_next = newnode;pcur->_prev = newnode;return iterator(newnode);}//list类中实现的交换void swap(list<T>& ls){std::swap(_head, ls._head);}};//库中的swaptemplate <class T>void swap(T& a, T& b){T c(a); a = b; b = c;}//自己定义的全局swaptemplate <class T>void swap(list<T>& ls1, list<T>& ls2){ls1.swap(ls2);}
}

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

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

相关文章

CSDN Markdown 编辑器语法大全

Markdown 是一种轻量级标记语言&#xff0c;它以简洁、易读易写的特点&#xff0c;被广泛应用于技术文档、博客文章、笔记等领域。CSDN 的 Markdown 编辑器为用户提供了丰富的功能&#xff0c;让用户能够轻松地创建格式规范、内容丰富的文档。以下是一份详细的 CSDN Markdown 编…

大数据治理--法规遵从与隐私保护

目录 ​编辑一、国际及地方数据保护法规 1.1 国际数据保护法规 1.1.1 欧盟《通用数据保护条例》&#xff08;GDPR&#xff09; 1.1.2 美国《加州消费者隐私法案》&#xff08;CCPA&#xff09; 1.1.3 中国《网络安全法》及《个人信息保护法》 1.2 地方数据保护法规 二、…

深入解析缓存技术

文章目录 1. 缓存基本原理2. 缓存更新机制2.1 Cache Aside模式2.2 Read/Write Through2.3 Write Behind Caching2.4 对比总结 3. 缓存数据过期策略3.1 最近最少使用&#xff08;Least Recently Used, LRU&#xff09;算法3.2 先进先出&#xff08;First-In-First-Out, FIFO&…

OpenCV高级图形用户界面(10)创建一个新的窗口函数namedWindow()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 创建一个窗口。 函数 namedWindow 创建一个可以作为图像和跟踪条占位符的窗口。创建的窗口通过它们的名字来引用。 如果已经存在同名的窗口&am…

linux线程 | 全面理解同步与互斥 | 同步

前言&#xff1a;本节内容主要讲解linux下的同步问题。 同步问题是保证数据安全的情况下&#xff0c;让我们的线程访问具有一定的顺序性。 线程安全就规定了它必须是在加锁的场景下的&#xff01;&#xff01;那么&#xff0c; 具体什么是同步问题&#xff0c; 我们加下来看看吧…

lesson01 Backtrader是什么

[Backtrader]专题连载 Backtrader是什么&#xff1f; Backtrader 是 2015 年开源的 Python 量化回测框架&#xff08;支持实盘交易&#xff09;。专注于为量化交易策略提供回测和实盘交易功能。它允许用户集中精力编写可复用的交易策略、指标和分析工具&#xff0c;而无需花费…

衡石分析平台系统分析人员手册-可视化报表仪表盘

仪表盘​ 仪表盘是数据分析最终展现形式&#xff0c;是数据分析的终极展现。 应用由一个或多个仪表盘展示&#xff0c;多个仪表盘之间有业务关联。 仪表盘编辑​ 图表列表​ 打开仪表盘后&#xff0c;就会看到该仪表盘中所有的图表。 调整图表布局​ 将鼠标移动到图表上拖动…

能源领域新政策,我们应该关注什么?

近日&#xff0c;国家发展改革委和国家能源局联合发布了《能源重点领域大规模设备更新实施方案》的通知。该方案指出&#xff0c;能源科技领域是大规模设备更新和消费品以旧换新行动实施的关键领域。 《方案》设定了到2027年&#xff0c;能源重点领域设备投资规模较2023年增长2…

CentOS 8 Stream环境下通过yum安装Mysql

1.在Mysql下载页面MySQL :: Download MySQL Community Server页尾 点击 “MD5 checksums and GnuPG signatures” 进入下一页面 2.打开下载yum repo文件页面 (MySQL :: Download MySQL Yum Repository) 3.点击"DownLoad"按钮&#xff0c;打开下载页面&#xff0c; 4.…

2012年国赛高教杯数学建模C题脑卒中发病环境因素分析及干预解题全过程文档及程序

2012年国赛高教杯数学建模 C题 脑卒中发病环境因素分析及干预 脑卒中&#xff08;俗称脑中风&#xff09;是目前威胁人类生命的严重疾病之一&#xff0c;它的发生是一个漫长的过程&#xff0c;一旦得病就很难逆转。这种疾病的诱发已经被证实与环境因素&#xff0c;包括气温和湿…

如何利用kafka实现高效数据同步?

在我们之前的文章有详细介绍过Kafka的结构、特点和处理方式。具备告诉处理能力的kafka被利用在数据同步和数据传输上&#xff0c;今天来说下kafka是怎么实现高效的数据同步和传输。 一、可靠的数据传输 1. 持久性保证&#xff1a;Kafka 将数据持久化到磁盘上&#xff0c;即使在…

深度学习实战94-基于图卷积神经网络GCN模型的搭建以及在金融领域的场景

大家好,我是微学AI,今天给大家介绍一下深度学习实战94-基于图卷积神经网络GCN模型的搭建以及在金融领域的场景。文章首先介绍了GCN模型的原理及模型结构,随后提供了数据样例,并详细展示了实战代码。通过本文,读者可以深入了解GCN模型在金融场景下的应用,同时掌握代码的具…

wifi、热点密码破解 - python

乐子脚本&#xff0c;有点小慢&#xff0c;试过多线程&#xff0c;系统 wifi 连接太慢了&#xff0c;需要时间确认&#xff0c;多线程的话系统根本反应不过来。 也就可以试试破解别人的热点&#xff0c;一般都是 123456 这样的傻鸟口令 # coding:utf-8 import pywifi from pyw…

BF 算法

目录 BF算法 算法思路 完整代码 时间复杂度 查找所有起始位置 BF算法 BF算法&#xff1a;即暴力(Brute Force)算法&#xff0c;是一种模式匹配算法&#xff0c;将目标串 S 的第一个字符与模式串 T 的第一个字符进行匹配&#xff0c;若相等&#xff0c;则继续比较 S 的第二…

【最新华为OD机试E卷-支持在线评测】TLV解码(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

【git】如何快速准确的回退(reverse)已经合并(merge)主分支(master)的新提交代码

文章目录 前言一、merge模式二、回滚步骤总结 前言 我们在做一些需求&#xff0c;正常流程经过开发&#xff0c;测试到最后和代码上线。但是有时候就会发生一些小插曲&#xff0c;比如产品说老板说某某某你的代码要延后上线&#xff01;&#xff01;或者你写的不合格预发环境出…

在Openshift(K8S)上通过EMQX Operator部署Emqx集群

EMQX Operator 简介 EMQX Broker/Enterprise 是一个云原生的 MQTT 消息中间件。 我们提供了 EMQX Kubernetes Operator 来帮助您在 Kubernetes 的环境上快速创建和管理 EMQX Broker/Enterprise 集群。 它可以大大简化部署和管理 EMQX 集群的流程&#xff0c;对于管理和配置的知…

ubuntu 安装keepalived+haproxy

一、安装keepalived sudo apt update sudo apt install keepalived sudo systemctl start keepalived sudo systemctl enable keepalived sudo systemctl status keepalived#配置Keepalived sudo cp /etc/keepalived/keepalived.conf.sample /etc/keepalived/keepalived.conf …

Java面试宝典-并发编程学习02

目录 21、并行与并发有什么区别&#xff1f; 22、多线程中的上下文切换指的是什么&#xff1f; 23、Java 中用到的线程调度算法是什么&#xff1f; 24、Java中线程调度器和时间分片指的是什么&#xff1f; 25、什么是原子操作&#xff1f;Java中有哪些原子类&#xff1f; 26、w…

Python案例小练习——小计算器

文章目录 前言一、代码展示二、运行展示 前言 这是用python实现一个简单的计器。 一、代码展示 def calculate(num1, op, num2):if op "":return float(num1) float(num2)elif op "-":return float(num1) - float(num2)elif op "*":return…