C++《stack与queue》

在之前的章节我们学习了C++当中string、vector和list三种容器并且试着模拟实现这三种容器,那么接下来在本篇当中我们将STL当中的stack和queue,并且在学习stack和queue的使用之后和之前一样还会试着模拟实现stck和queue。由于stck和queue的模拟实现较为简单因此就不另在其他篇章中讲解。在此我们还要了解到stack和queue是一种容器适配器,那么在这之前就要了解容器适配器是什么。最后我们还要了解一种双端队列的容器,并且分析其与vector和list的区别,那么接下来就开始本篇的学习吧!!!


 1.stack使用介绍

stack - C++ Reference

通过文档就可以看出stack也是C++当中STL当中提供的一种容器,该容器其实就是之前我们在数据结构当中学习到的栈,在此在C++当中是将栈的结构和各种功能实现好,之后我们需要使用时就只需要调用模板库内的stack即可这样就不需要像之前C语言一样显示的自己实现,这样可以大大提升我们的效率

在stack当中提供了以下函数,在此这些函数和之前在数据结构就已经学习过,那么在此就不做讲解,如果你还对栈有疑惑可以数据结构之《栈》通过来查缺补漏

函数说明接口说明
stack()构造空的栈
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出

那么在了解了stack的相关知识点之后接下来就来通过几道算法题来试着使用stck

最小栈

首先实现该算法题的代码之前我们先来分析该如何实现该算法题

该算法题其实就是要我们实现一个类在这个类当中实现以上的成员函数,在此可以看出要在这个类当中除了要实现栈通常的操作还要实现一个函数能完成将栈当中最小的数据返回,接下来就要分析要实现这样的功能要运用什么方法呢?

其实在此要实现一个最小栈的结构我们就可以使用两个stack来实现,其中一个栈PushSt来存储入栈所有的元素,另一个栈MinSt来存储PushSt内的最小元素

那么MinSt怎么才能实现存储PushSt内的最小元素呢?接下来我们就通过一个示例来分析看看

 例如以上示例所示要将 4 7 3 5 2 2依次入栈,那么就需要进行以下的操作
 1. 每次数据入栈时都先将元素入PushSt栈

 2.完成1之后查看MinSt内的栈顶元素是否比新插入PushSt内的元素的值要大,若大或者相等就也将插入PushSt内的元素的值插入到MinSt。在此还会有一种特殊情况是当MinSt为空时,无论插入到PushSt内的元素是什么都要再将该元素插入到MinSt

完成了以上操作之后所有元素入栈后两个栈PushSt和MinSt内的情况如下所示:

那么接下来如果要进行最小栈出栈的操作时MinSt内部又应该如何变化呢?
继续来对以上的示例进行出栈的分析

1.当进行出栈操作时候,无论要进行出栈的元素的值是多大都要将PushSt内的栈顶元素出栈

2.之后再比较进行此次出栈时PushSt内的栈顶元素的值是否和PushSt内的栈顶元素值相等,相等就需要也将MinSt内的栈顶元素出栈

通过以上的最小栈需要出栈时的操作就可以保证出栈之后在MinSt内的栈顶元素是PushSt内的最小值
 

完成了算法实现的分析之后接下来就来实现该算法题的代码:

class MinStack 
{
public://构造函数MinStack() {}//入栈void push(int val) {if(MinSt.empty() || val<=MinSt.top()){MinSt.push(val);}PushSt.push(val);}//出栈void pop() {if(PushSt.top()==MinSt.top()){MinSt.pop();}PushSt.pop();}//取栈顶元素int top() {return PushSt.top();}//得到最小的元素值int getMin() {return MinSt.top();}private:std::stack<int> PushSt;std::stack<int> MinSt;
};/*** Your MinStack object will be instantiated and called as such:* MinStack* obj = new MinStack();* obj->push(val);* obj->pop();* int param_3 = obj->top();* int param_4 = obj->getMin();*/

注:在以上的代码当中由于MinStack的成员变量都是自定义类型的变量,那么就就不需要我们显示的写MinStack的构造函数,这是由于在MinStack对象定义时会自动调用stck的构造函数

栈的压入、弹出序列

接下来我们继续来看以下算法题

通过以上的题目描述就可以看出该算法题要我们实现一个函数能判断两个vector对象内的第一个vector内存储的为入栈序列时,第二个序列能为第一个序列的出栈序列,如果能满足条件函数就返回true,否则就返回flase

接下来在实现该算法题代码之前就先来分析这个题的解题思路是什么

在此我们来通过一个示例来分析
当我们要判断[1,2,3,4,5],[4,5,3,2,1]这两个序列第二个序列是否为第一个序列的一种出栈序列时就可以借助栈来判断,具体过程如下所示:

首先创建一个栈st,在分别创建一个变量pushi和popi分别指向第一个和第二个序列的首个元素,接下来进行重复进行以下操作

1.当栈st为空时或者是栈st的栈顶元素不和popi指向的元素相等时就将pushi指向的元素插入到栈st中,之后再pushi++,直到不符合以上为空或者相等的要求为止

2.之后当popi指向的元素和栈st的栈顶元素相等时就将st栈顶元素出栈,之后再将popi++,重复该操作直到不匹配为止,之后再进行以上的操作1

3.以上的操作的结束条件是当pushi走到第一个序列的末尾时就结束操作,这时看栈st是否为空;为空就说明第二个序列不是为第一个序列的一种出栈序列,否则则是

完成了以上操作之后以上示例使用以上方法的判断过程如下所示:

 先是直到pushi指向5为止都没未出现st栈顶的元素和popi指向的元素匹配,那么就要把1到4都入栈

之后st栈顶的元素4和popi指向的元素4匹配,就将st的栈顶元素出栈,之后再将popi++

之后st栈顶的元素和popi指向的元素不匹配就继续将pushi指向的元素入栈

之后st栈顶的元素4和popi指向的元素4匹配,popi++,之后一直符合要求就一直重复该操作

由以上栈st最后再pushi走到第一个序列的末尾时st为空就说明第二个序列是为第一个序列的一种出栈序列

完成了算法实现的分析之后接下来就来实现该算法题的代码:

class Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param pushV int整型vector * @param popV int整型vector * @return bool布尔型*/bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {std::stack<int> st;int pushi=0,popi=0;while(pushi<pushV.size()){st.push(pushV[pushi]);while(!st.empty() && st.top()==popV[popi]){st.pop();++popi;}++pushi;}return st.empty();}
};

逆波兰表达式求值

通过以上的题目描述就知道了该算法题要我们实现的是对一个逆波兰表达式进行运算,在此的逆波兰表达式其实就是中缀表达式,这时你可能就会有疑惑那么中缀表达式是什么呢?

来看以下示例:
在以下示例当中就是这两个中缀表达式的后缀表达式的形式

在此中缀表达式转后缀表达式的技巧是运算数的顺序不变,运算符按照对应的优先级插入到运算数之后,每个运算符对其前面的两个运算数进行运算,并且在使用后缀表达式计算时是按照从左到右对操作符前面的操作数进行运算的

在了解了中缀表达式以及后缀表达式之后接下来我们就来思考该使用什么方式来解答这道算法题呢?
其实在此还是要用到栈来解决,在此先创建一个栈,之后遇到操作数则入栈;遇到操作符则取出栈顶两个操作数进行计算,并将结果压入栈中

实现的代码如下所示:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(auto& x:tokens){//当是操作符时if(x=="+" || x=="-" || x=="*" ||x=="/"){int right=st.top();st.pop();int left=st.top();st.pop();switch(x[0]){case('+'):st.push(left+right);break;case('-'):st.push(left-right);break;case('*'):st.push(left*right);break;case('/'):st.push(left/right);break;         }}//当是操作数时else{st.push(stoi(x));}}return st.top();}
};

2.queue使用介绍

queue - C++ Reference

通过文档就可以看出queue也是C++当中STL当中提供的一种容器,该容器其实就是之前我们在数据结构当中学习到的队列,在此在C++当中是将队列的结构和各种功能实现好,之后我们需要使用时就只需要调用模板库内的queue即可这样就不需要像之前C语言一样显示的自己实现,这样可以大大提升我们的效率

在queue当中提供了以下函数,在此这些函数和之前在数据结构就已经学习过,那么在此就不做讲解,如果你还对栈有疑惑可以通过数据结构之《队列》来查缺补漏

函数说明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

 那么在了解了stack的相关知识点之后接下来就来通过几道算法题来试着使用queue

用队列实现栈

以上的算法题其实在数据结构就写过了,不过在之前我们是使用C语言实现的,那时要解决这道算法题需要我们先自己实现队列的结构,之后才能使用创建两个队列来解决。但在C++中就不用这么繁琐了,我们可以直接调用STL当中的queue来实现,因此我们就只需要实现Mystack内的代码即可,并且该类内的构造函数还不需要我们显示的写

代码如下所示:

class MyStack {
public:MyStack() {}void push(int x) {if(!q1.empty()){q1.push(x);}else{q2.push(x);}}int pop() {int tmp=0;if(!q1.empty()){int n=q1.size();while(--n){q2.push(q1.front());q1.pop();}tmp=q1.front();q1.pop();}else{int n=q2.size();while(--n){q1.push(q2.front());q2.pop();}tmp=q2.front();q2.pop();}return tmp;}int top() {if(!q1.empty()){return q1.back();}else{return q2.back();}}bool empty() {return q1.empty() && q2.empty();}private:queue<int> q1;queue<int> q2;
};/*** Your MyStack object will be instantiated and called as such:* MyStack* obj = new MyStack();* obj->push(x);* int param_2 = obj->pop();* int param_3 = obj->top();* bool param_4 = obj->empty();*/

3.stack和queue的模拟实现

在了解了STL内的stack和queue的使用之后接下来我们来试着模拟实现stack和queue,但在此之前我们要了解什么是容器适配器,这时由于STL内的stack和queue都是容器适配器

什么是适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设
计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口

例如电脑的充电器其实就是一种电源适配器

我们可以通过文档看到STL标准库中stack和queue的底层结构

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为
容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认
使用deque,比如:

 

 那么在此stack内使用到的deque是什么容器呢,接下来我们就来简单了解一下

1.deque的原理介绍:

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端
进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与
list比较,空间利用率比较高。

其实deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问
的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

 2.deque的缺陷

那么在了解了deque之后你可能就会有疑惑了,deque这个容器相比vector在中间和头部插入数据有优势,又相比list空间利用率较高,那么是不是用deque就可以替代vector和list了呢?

确实与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其
是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实
际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看
到的一个应用就是,STL用其作为stack和queue的底层数据结构。

3.为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性
结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据
结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如
list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进
行操作。

2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的
元素增长时,deque不仅效率高,而且内存使用率高。

在此可以看出选择deque作为stack和queue的底层默认容器结合了deque的优点,而完美的避开了其缺陷。

了解deque接下来我们就来模拟实现stack和queue

stack:

#include<iostream>
#include<deque>
using namespace std;namespace zhz
{template<class T,class container =deque<T>>class stack{public:size_t size()const{return _st.size();}bool empty()const{return _st.empty();}void push(const T& val){_st.push_back(val);}void pop(){_st.pop_back();}T& top(){return _st.back();}const T& top()const{return _st.back();}private:container _st;};
}

queue:

#include<iostream>
#include<deque>
using namespace std;namespace zhz
{template<class T, class container = deque<T>>class queue{public:size_t size()const{return _qe.size();}bool empty()const{return _qe.empty();}void push(const T& val){_qe.push_back(val);}void pop(){_qe.pop_front();}T& front(){return _qe.front();}T& back(){return _qe.back();}const T& front()const{return _qe.front();}const T& back()const{return _qe.back();}private:container _qe;};
}

4.priority_queue的介绍和使用

4.1 priority_queue的介绍

在queue的文档当中可以看到除了queue还有priority_queue

那么priority_queue是什么呢?

其实priority_queue是一种优先队列; 优先队列也是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。并且优先队列类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

并且优先队列和queue一样被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

4.2 priority_queue的使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中
元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用
priority_queue。
注意:默认情况下priority_queue是大堆。

函数说明接口说明
priority_queue()/priority_queue(first,
last)
构造一个空的优先级队列
empty( )检测优先级队列是否为空,是返回true,否
则返回false
top( )返回优先级队列中最大(最小元素),即堆顶元
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元

例如以下示例:

#include <vector>
#include <queue>
#include <functional>   // greater算法的头文件void TestPriorityQueue()
{// 默认情况下,创建的是大堆,其底层按照小于号比较vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };priority_queue<int> q1;for (auto& e : v)q1.push(e);cout << q1.top() << endl;// 如果要创建小堆,将第三个模板参数换成greater比较方式priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());cout << q2.top() << endl;
}

接下来来看以下的算法题:

数组中第K个大的元素

通过以上的题目描述可以看出该算法题要我们实现的是将数组当中的第k大的元素找出来,并且实现时间复杂度为 O(n) 的算法解决此问题 

其实在此使用priority_queue就能很轻松的解决该算法题,代码如下所示:
 

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int> pq;for(auto x:nums){pq.push(x);}while(--k){pq.pop();}return pq.top();}
};

4.3 priority_queue的模拟实现 

在了解了优先队列的使用方式之后接下来就来试着模拟实现priority_queue,并且我们知道优先队列类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)

那么优先队列的结构以及入队和出队的操作,在此就要用到之前数据结构当中堆相应的知识,代码如下所示

namespace zhz
{template<class T, class container = vector<T>>class priority_queue{public://向上调整法算法void AdjustUp(int child){//以上为大堆时的向上调整法算法int parent = (child - 1) / 2;while (child > 0){if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}//向上调整法算法void AdjustDown(int parent){//以上为大堆时的向下调整法算法int child = 2 * parent + 1;while (child < _con.size()){if (child + 1 < _con.size() && _con[child + 1] > _con[child]){child++;}if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);parent = child;child = 2 * parent + 1;}else{break;}}}//入队列void push(const T& x){_con.push_back(x);AdjustUp(_con.size() - 1);}//出队列void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}private://底层为vectorcontainer _con;};
}

注:以上使用到向上和向下调整算法以及堆元素的插入和堆顶元素的删除在之前的数据结构堆章节有详细了解过,在此就不再讲解其的原理

注:如果你还对以上的堆相关知识有疑惑看一看之前的数据结构之《二叉树》(中)

在以上的代码我们是实现了默认情况下priority_queue为大堆时的入队列和出队列,但是我们知道STL内实现的priority_queue还可以通过实现对小堆的操作,那么我们该如何修改我们实现的代码呢?
其实在此就要使用到仿函数来实现队列的底层结构是小堆还是大堆,在此实现两个类less和greater类分别实现大堆和小堆构建,并且在这两个类内都实现()的运算符重载函数

实现代码如下所示:
 

template<class T>
struct less
{bool operator()(const T& x1, const T& x2){return x1 < x2;}
};template<class T>
struct greater
{bool operator()(const T& x1, const T& x2){return x1 > x2;}
};

在此我们实现的priority_queue的类模板也要加上第三个参数,在此将该参数命名为compare,并且缺省参数为less<T>。实现了类模板的修改之后在函数内部向上调整和向下调整的函数也要作出修改,修改完的代码如下所示:
 

namespace zhz
{template<class T>struct less{bool operator()(const T& x1, const T& x2){return x1 < x2;}};template<class T>struct greater{bool operator()(const T& x1, const T& x2){return x1 > x2;}};template<class T, class container = vector<T>, class compare = less<T>>class priority_queue{public:void AdjustUp(int child){compare com;int parent = (child - 1) / 2;while (child > 0){//if (_con[child] > _con[parent])if (com( _con[parent],_con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void AdjustDown(int parent){compare com;int child = 2 * parent + 1;while (child < _con.size()){//if (child + 1 < _con.size() && _con[child + 1] > _con[child])if(child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}//if (_con[child] > _con[parent])if(com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = 2 * parent + 1;}else{break;}}}void push(const T& x){_con.push_back(x);AdjustUp(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}private:container _con;};
}

之后在对以上代码补充一些构造函数以及取队头元素的代码之后就实现了完整的优先队列模拟实现代码

完整代码 

#pragma once
#include<iostream>
#include<vector>
//#include<queue>
using namespace std;namespace zhz
{template<class T>struct less{bool operator()(const T& x1, const T& x2){return x1 < x2;}};template<class T>struct greater{bool operator()(const T& x1, const T& x2){return x1 > x2;}};template<class T, class container = vector<T>, class compare = less<T>>class priority_queue{public:priority_queue() = default;template<class InputIterator>priority_queue(InputIterator first, InputIterator last):_con(first, last){for (int i = _con.size() - 1 - 1 / 2; i <= 0; i--){AdjustDown(i);}}void AdjustUp(int child){compare com;int parent = (child - 1) / 2;while (child > 0){//if (_con[child] > _con[parent])if (com( _con[parent],_con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void AdjustDown(int parent){compare com;int child = 2 * parent + 1;while (child < _con.size()){//if (child + 1 < _con.size() && _con[child + 1] > _con[child])if(child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}//if (_con[child] > _con[parent])if(com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = 2 * parent + 1;}else{break;}}}void push(const T& x){_con.push_back(x);AdjustUp(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}size_t size(){return _con.size();}bool empty(){return _con.empty();}const T& top(){return _con.front();}private:container _con;};
}

对以上代码我们来实现以下代码来测试看看是否正确

#include"priority_queue.h"
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;}private:int _year;int _month;int _day;
};int main()
{zhz::priority_queue<Date, vector<Date>, zhz::greater<Date>> pq1;pq1.push(Date(2014,11,1));pq1.push(Date(2024,11,5));pq1.push(Date(2024,11,10));pq1.push(Date(2014,9,20));pq1.push(Date(2024,7,1));while (!pq1.empty()){cout << pq1.top() << " ";pq1.pop();}return 0;
}

 在此我们实现的是日期类对象小堆实现,不断取堆顶元素输出结果如下所示:

通过以上输出结果为升序的就说明我们实现的priority_queue的代码没问题

在此我们还有可能会使用以上的形式来创建日期类对象,那么这时是否能实现正确的排序呢?

int main()
{zhz::priority_queue<Date*, vector<Date*>, zhz::greater<Date*>> pq1;pq1.push(new Date{ 2014, 11, 1 });pq1.push(new Date{ 2024, 11, 5 });pq1.push(new Date{ 2024, 11, 10 });pq1.push(new Date{ 2014, 9, 20 });pq1.push(new Date{ 2024, 7, 1 });while (!pq1.empty()){cout << *pq1.top() << " ";pq1.pop();}return 0;
}

我们通过运行该程序就会发现每次输出结果都是不同的,这时为什么呢?

这其实是由于以上是按照地址比较的,但每次new的对象在堆区的存储位置都不一样这就使得会出现每次输出结果都是不同的,那么要怎么才能解决呢?

这就需要我们再显示的相应的类,如下所示:

struct less_dest
{bool operator()(const Date* x1, const Date* x2){return *x1 < *x2;}
};struct greater_dest
{bool operator()(const Date* x1, const Date* x2){return *x1 > *x2;}
};

这时main函数内的代码就变为以下形式:

int main()
{zhz::priority_queue<Date*, vector<Date*>,greater_dest> pq1;//zhz::priority_queue<Date*, vector<Date*>, zhz::greater<Date*>> pq1;pq1.push(new Date{ 2014, 11, 1 });pq1.push(new Date{ 2024, 11, 5 });pq1.push(new Date{ 2024, 11, 10 });pq1.push(new Date{ 2014, 9, 20 });pq1.push(new Date{ 2024, 7, 1 });while (!pq1.empty()){cout << *pq1.top() << " ";pq1.pop();}return 0;
}

这时输出结果就是正确的了:


 

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

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

相关文章

【Linux】常用命令(2.6万字汇总)

文章目录 Linux常用命令汇总1. 基础知识1.1. Linux系统命令行的含义1.2. 命令的组成 2. 基础知识2.1. 关闭系统2.2. 关闭重启2.3. 帮助命令&#xff08;help&#xff09;2.4. 命令说明书&#xff08;man&#xff09;2.5. 切换用户&#xff08;su&#xff09;2.6.历史指令 3.目录…

Selenium+Pytest自动化测试框架 ------ 禅道实战

前言 有人问我登录携带登录的测试框架该怎么处理&#xff0c;今天就对框架做一点小升级吧&#xff0c;加入登录的测试功能。 选用的测试网址为我电脑本地搭建的禅道 更改了以下的一些文件,框架为原文章框架主体 conftest.py更改 conftest.py #!/usr/bin/env python3 # -*…

java---认识异常(详解)

还有大家来到权权的博客~欢迎大家对我的博客提出意见哦&#xff0c;有错误会及时改进的~点击进入我的博客主页 目录 一、异常的概念及体系结构1.1 异常的概念1.2 异常的体系结构1.3异常的分类 二、异常的处理2.1防御式编程2.2 异常的抛出2.3 异常的捕获2.3.1异常声明throws2.3.…

鸿蒙多线程开发——并发模型对比(Actor与内存共享)

1、概 述 并发是指在同一时间段内&#xff0c;能够处理多个任务的能力。为了提升应用的响应速度与帧率&#xff0c;以及防止耗时任务对主线程的干扰&#xff0c;HarmonyOS系统提供了异步并发和多线程并发两种处理策略。 异步并发&#xff1a;指异步代码在执行到一定程度后会被…

Axure是什么软件?全方位解读助力设计入门

在产品设计和开发领域&#xff0c;Axure是一款大名鼎鼎且功能强大的软件&#xff0c;它为专业人士和团队提供了卓越的设计支持&#xff0c;帮助他们将创意转化为实际可操作的产品原型。 一、Axure 的基本介绍 Axure是一款专业的原型设计工具&#xff0c;主要用于创建交互式的…

客户手机号收集小程序有什么用

客户手机号收集小程序具有多方面的重要作用&#xff0c;主要体现在以下几个领域&#xff1a; 商业营销与客户关系管理 精准营销&#xff1a;通过收集客户手机号&#xff0c;企业能够依据客户的消费行为、偏好等信息&#xff0c;进行精准的个性化营销。例如&#xff0c;电商企业…

Spring Boot集成SQL Server快速入门Demo

1.什么是SQL Server&#xff1f; SQL Server是由Microsoft开发和推广的以客户/服务器&#xff08;c/s&#xff09;模式访问、使用Transact-SQL语言的关系数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;它最初是由Microsoft、Sybase和Ashton-Tate三家公司共同开发的&…

[CKS] Create/Read/Mount a Secret in K8S

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于读取、创建以及挂载secret的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[C…

深入理解Java虚拟机:你真的了解JVM吗?

Java虚拟机(JVM) 是 Java 技术的核心,它帮助 Java 实现了一次编译,到处运行的梦想。然而,你真的理解 JVM 的工作原理吗?今天,我们就从 JVM 的内部架构、垃圾回收机制、性能调优等角度,深入探讨这个“神秘黑盒”。 1. JVM 的基本架构:探索虚拟机内部 JVM 是运行 Java …

大模型就业收入高吗?大模型入门到精通,收藏这篇就够了

目前&#xff0c;已经可以说人工智能&#xff08;AI&#xff09;是推动社会进步和产业升级的重要力量。 其中&#xff0c;AI大模型作为人工智能领域的核心技术之一&#xff0c;正引领着新一轮的技术革命。 2024年&#xff0c;AI大模型开发工程师无疑成为了IT行业中最炙手可热…

el-table 纵向垂直表头处理

项目中表格展示会遇到需要纵向垂直表头情况&#xff0c;下面&#xff0c;我们基于el-table组件来实现这种表格。 以下是这次需要用到的数据表格&#xff0c;已知左侧违章名称是固定的&#xff0c;而月份是不固定的&#xff0c;在后端返回数据格式已确定的情况下&#xff0c;需…

leetcode day10 动态规划篇 64+139

64 最小路径和 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 m grid.lengthn grid[i].length1 < m, n < 2000 < grid[i][j]…

Flutter 小技巧之 Shader 实现酷炫的粒子动画

在之前的《不一样的思路实现炫酷 3D 翻页折叠动画》我们其实介绍过&#xff1a;如何使用 Shader 去实现一个 3D 的翻页效果&#xff0c;具体就是使用 Flutter 在 3.7 开始提供 Fragment Shader API &#xff0c;因为每个像素都会过 Fragment Shader &#xff0c;所以我们可以通…

SpringCloud框架学习(第二部分:Consul、LoadBalancer和openFeign)

目录 六、Consul服务注册和发现 1.基本介绍 2.下载运行 3.服务注册与发现 &#xff08;1&#xff09;支付服务provider8001注册进consul &#xff08;2&#xff09;修改订单服务cloud-consumer-order80 4.CAP &#xff08;1&#xff09;CAP理论 &#xff08;2&#x…

大数据学习12之HBase

1.基本概念 1.1简介 Apache HBase&#xff08;Hadoop DataBase&#xff09;是一个开源的、高可靠性、高性能、面向列&#xff08;这里指列族&#xff0c;非列式存储&#xff09;、可伸缩、实时读写的分布式数据库&#xff0c;其设计思想来源于 Google 的 BigTable 论文。利用 …

el-input 正则表达式校验输入框不能输入汉字

<el-form :model"data1" :rules"rules" ref"ruleForm" label-width"210px" class"demo-ruleForm"><el-form-item label"锯路&#xff1a;" prop"sawKref"><el-input class"inptWid…

linux rocky 9.4部署和管理docker harbor私有源

文章目录 Harbor简介安装Harbor技术细节1.安装系统(略),设置主机名和IP2.安装docker3.安装docker-compose4.安装Harbor私有源仓库5 测试登录1.本机登录2.客户端登录Harbor服务器配置docker源1. 下载镜像2.把镜像上传到Harbor私有仓库源3.客户端下载镜像,并且启动容器linux …

03WIFI与蓝牙1——基于全志V3S的Linux开发板教程笔记

1. Kernel支持 1&#xff09;配置 终端输入&#xff1a; make menuconfig使能如下部分&#xff1a; 2&#xff09;编译 保存并退出后编译内核&#xff1a; make licheepi_zero_defconfig make menuconfig #配置内核&#xff0c;有需要的话配置 make -j16 make -j16 modu…

iOS 18.2 重磅更新:6个大动作

根据外媒报道&#xff0c;iOS 18.2迎来重磅更新&#xff0c;将带来6个大动作&#xff0c;这是一次非常实用的更新。不过要注意的是&#xff0c;其中涉及到AI的功能&#xff0c;国行iPhone 暂时还不可用&#xff0c;只能等审核通过了。 1&#xff0c;Safari下载进度 过去通过S…

【单例模式】饿汉式与懒汉式以及线程安全

1. 单例模式介绍 饿汉式单例模式&#xff1a;还没有获取实例对象&#xff0c;实例对象就已经产生了。一定是线程安全的。 懒汉式单例模式&#xff1a;需要用的时候再构造实例。 应用场景&#xff1a;比如日志模块&#xff0c;数据库模块&#xff0c;开发的解析器模块。 2.饿…