C++学习之旅【C++拓展学习之反向迭代器实现、计算器实现以及逆波兰表达式】

C++学习之旅【C++拓展学习之反向迭代器实现、计算器实现以及逆波兰表达式】

🔥承渊政道:个人主页
❄️个人专栏: 《C语言基础语法知识》《数据结构与算法》

《C++知识内容》《Linux系统知识》

✨逆境不吐心中苦,顺境不忘来时路!🎬 博主简介:

引言:前篇文章,小编已经介绍了关于C++Stack和Queue类的相关知识.接下来我将带领大家继续深入学习C++的相关内容!本篇文章着重介绍关于C++拓展学习之反向迭代器实现、计算器实现以及逆波兰表达式,那么这里面到底有哪些知识需要我们去学习的呢?废话不多说,带着这些疑问,下面跟着小编的节奏🎵一起学习吧!

目录

1. 反向迭代器思路(源码及框架分析)

反向迭代器是一种特殊的迭代器,它的核心作用是:从容器的末尾开始,逆向遍历容器中的元素(正常迭代器是从头部到尾部).可以把它理解成:
正常迭代器:像你从队伍第一个人走到最后一个人;
反向迭代器:像你从队伍最后一个人倒着走回第一个人.
SGI-STL30版本源代码,反向迭代器实现的核⼼源码在stl_iterator.h中,反向迭代器是⼀个适配器,各个容器中再适配出⾃⼰的反向迭代器.下⾯我们截出vector和list的的反向迭代器结构框架核⼼部分截取出来如下:
// stl_list.htemplate<classT,classAlloc= alloc>classlist{public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T,const T&,const T*> const_iterator;#ifdef__STL_CLASS_PARTIAL_SPECIALIZATIONtypedef reverse_iterator<const_iterator> const_reverse_iterator;typedef reverse_iterator<iterator> reverse_iterator;#else/* __STL_CLASS_PARTIAL_SPECIALIZATION */typedef reverse_bidirectional_iterator<const_iterator, value_type, const_reference, difference_type> const_reverse_iterator;typedef reverse_bidirectional_iterator<iterator, value_type, reference, difference_type> reverse_iterator;#endif/* __STL_CLASS_PARTIAL_SPECIALIZATION */ iterator begin(){return(link_type)((*node).next);} const_iterator begin()const{return(link_type)((*node).next);} iterator end(){return node;} const_iterator end()const{return node;} reverse_iterator rbegin(){returnreverse_iterator(end());} const_reverse_iterator rbegin()const{returnconst_reverse_iterator(end());} reverse_iterator rend(){returnreverse_iterator(begin());} const_reverse_iterator rend()const{returnconst_reverse_iterator(begin());}};// stl_vector.htemplate<classT,classAlloc= alloc>classvector{public:typedef T value_type;typedef value_type* iterator;#ifdef__STL_CLASS_PARTIAL_SPECIALIZATIONtypedef reverse_iterator<const_iterator> const_reverse_iterator;typedef reverse_iterator<iterator> reverse_iterator;#else/* __STL_CLASS_PARTIAL_SPECIALIZATION */typedef reverse_iterator<const_iterator, value_type, const_reference, difference_type> const_reverse_iterator;typedef reverse_iterator<iterator, value_type, reference, difference_type> reverse_iterator;#endif/* __STL_CLASS_PARTIAL_SPECIALIZATION */ iterator begin(){return start;} const_iterator begin()const{return start;} iterator end(){return finish;} const_iterator end()const{return finish;} reverse_iterator rbegin(){returnreverse_iterator(end());} const_reverse_iterator rbegin()const{returnconst_reverse_iterator(end());} reverse_iterator rend(){returnreverse_iterator(begin());} const_reverse_iterator rend()const{returnconst_reverse_iterator(begin());}};// stl_iterator.h#ifdef__STL_CLASS_PARTIAL_SPECIALIZATION// This is the new version of reverse_iterator, as defined in the// draft C++ standard. It relies on the iterator_traits template,// which in turn relies on partial specialization. The class// reverse_bidirectional_iterator is no longer part of the draft// standard, but it is retained for backward compatibility.template<classIterator>classreverse_iterator{protected: Iterator current;public:typedeftypenameiterator_traits<Iterator>::iterator_category iterator_category;typedeftypenameiterator_traits<Iterator>::value_type value_type;typedeftypenameiterator_traits<Iterator>::difference_type difference_type;typedeftypenameiterator_traits<Iterator>::pointer pointer;typedeftypenameiterator_traits<Iterator>::reference reference;typedef Iterator iterator_type;typedef reverse_iterator<Iterator> self;public:reverse_iterator(){}explicitreverse_iterator(iterator_type x):current(x){}reverse_iterator(const self& x):current(x.current){}#ifdef__STL_MEMBER_TEMPLATEStemplate<classIter>reverse_iterator(const reverse_iterator<Iter>& x):current(x.current){}#endif/* __STL_MEMBER_TEMPLATES */ iterator_type base()const{return current;} reference operator*()const{ Iterator tmp = current;return*--tmp;}#ifndef__SGI_STL_NO_ARROW_OPERATOR pointer operator->()const{return&(operator*());}#endif/* __SGI_STL_NO_ARROW_OPERATOR */ self&operator++(){--current;return*this;} self operator++(int){ self tmp =*this;--current;return tmp;} self&operator--(){++current;return*this;} self operator--(int){ self tmp =*this;++current;return tmp;} self operator+(difference_type n)const{returnself(current - n);} self&operator+=(difference_type n){ current -= n;return*this;} self operator-(difference_type n)const{returnself(current + n);} self&operator-=(difference_type n){ current += n;return*this;} reference operator[](difference_type n)const{return*(*this+ n);}};template<classIterator>inlinebooloperator==(const reverse_iterator<Iterator>& x,const reverse_iterator<Iterator>& y){return x.base()== y.base();}template<classIterator>inlinebooloperator<(const reverse_iterator<Iterator>& x,const reverse_iterator<Iterator>& y){return y.base()< x.base();}template<classIterator>inlinetypenamereverse_iterator<Iterator>::difference_type operator-(const reverse_iterator<Iterator>& x,const reverse_iterator<Iterator>& y){return y.base()- x.base();}template<classIterator>inline reverse_iterator<Iterator>operator+(reverse_iterator<Iterator>::difference_type n,const reverse_iterator<Iterator>& x){returnreverse_iterator<Iterator>(x.base()- n);}#else/* __STL_CLASS_PARTIAL_SPECIALIZATION */// This is the old version of reverse_iterator, as found in the original// HP STL. It does not use partial specialization.template<classBidirectionalIterator,classT,classReference= T&,classDistance= ptrdiff_t>classreverse_bidirectional_iterator{typedef reverse_bidirectional_iterator<BidirectionalIterator, T, Reference, Distance> self;protected: BidirectionalIterator current;public:typedef bidirectional_iterator_tag iterator_category;typedef T value_type;typedef Distance difference_type;typedef T* pointer;typedef Reference reference;reverse_bidirectional_iterator(){}explicitreverse_bidirectional_iterator(BidirectionalIterator x):current(x){} BidirectionalIterator base()const{return current;} Reference operator*()const{ BidirectionalIterator tmp = current;return*--tmp;}#ifndef__SGI_STL_NO_ARROW_OPERATOR pointer operator->()const{return&(operator*());}#endif/* __SGI_STL_NO_ARROW_OPERATOR */ self&operator++(){--current;return*this;} self operator++(int){ self tmp =*this;--current;return tmp;} self&operator--(){++current;return*this;} self operator--(int){ self tmp =*this;++current;return tmp;}};template<classRandomAccessIterator,classT,classReference= T&,classDistance= ptrdiff_t>classreverse_iterator{typedef reverse_iterator<RandomAccessIterator, T, Reference, Distance> self;protected: RandomAccessIterator current;public:typedef random_access_iterator_tag iterator_category;typedef T value_type;typedef Distance difference_type;typedef T* pointer;typedef Reference reference;reverse_iterator(){}explicitreverse_iterator(RandomAccessIterator x):current(x){} RandomAccessIterator base()const{return current;} Reference operator*()const{return*(current -1);}#ifndef__SGI_STL_NO_ARROW_OPERATOR pointer operator->()const{return&(operator*());}#endif/* __SGI_STL_NO_ARROW_OPERATOR */ self&operator++(){--current;return*this;} self operator++(int){ self tmp =*this;--current;return tmp;} self&operator--(){++current;return*this;} self operator--(int){ self tmp =*this;++current;return tmp;} self operator+(Distance n)const{returnself(current - n);} self&operator+=(Distance n){ current -= n;return*this;} self operator-(Distance n)const{returnself(current + n);} self&operator-=(Distance n){ current += n;return*this;} Reference operator[](Distance n)const{return*(*this+ n);}};#endif//__STL_CLASS_PARTIAL_SPECIALIZATION
1️⃣源码中我们可以看到reverse_iterator实现了两个版本,通过__STL_CLASS_PARTIAL_SPECIALIZATION 条件编译控制使⽤哪个版本,简单点说就是⽀持偏特化的迭代器萃取以后,反向迭代器使⽤的是这个版本template
class reverse_iterator; 之前使⽤的是
template <class BidirectionalIterator, class T, class Reference, class Distance>
class reverse_bidirectional_iterator;
template <class RandomAccessIterator, class T, class Reference class Distance>
class reverse_iterator;
2️⃣可以看到他们的差别主要是在模板参数是否传递迭代器指向的数据类型,⽀持偏特化的迭代器萃取以后就不需要给了,因为 reverse_iterator 内部可以通过迭代器萃取获取数据类型.迭代器萃取的本质是⼀个特化,这个还有些⼩复杂且不影响我们学习,这⾥我就不讲解了,有兴趣可以去看源码,这个我们主要使⽤模版参数传递数据类型的⽅式实现.
3️⃣反向迭代器本质是⼀个适配器,使⽤模版实现,传递哪个容器的迭代器就可以封装适配出对应的反向迭代器.因为反向迭代器的功能跟正向的迭代器功能⾼度相似,只是遍历的⽅向相反,类似operator++ 底层调⽤迭代器的 operator-- 等,所以封装⼀下就可以实现.
4️⃣⽐较奇怪的是operator*的实现,内部访问的是迭代器当前位置的前⼀个位置.这个要结合容器中rbegin和rend实现才能看懂,rbegin返回的是封装end位置的反向迭代器,rend返回的是封装begin位置迭代器的反向迭代器,这⾥是为了实现出⼀个对称,所以解引⽤访问的是当前位置的前⼀个位置.

2. 反向迭代器的实现

// Common.h - 基础头文件和节点定义#pragmaonce#include<iostream>#include<cstdlib>// 内存分配#include<algorithm>usingnamespace std;// List节点定义(list依赖)template<classT>structListNode{ T _data; ListNode<T>* _prev; ListNode<T>* _next;// 节点构造函数ListNode(const T& val =T()):_data(val),_prev(nullptr),_next(nullptr){}};// 迭代器基础(list的正向迭代器)template<classT,classRef,classPtr>structListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self; Node* _node;// 构造函数ListIterator(Node* node):_node(node){}// 运算符重载 Ref operator*()const{return _node->_data;} Ptr operator->()const{return&_node->_data;}// 前置++ 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;}// 后置-- Self operator--(int){ Self tmp(*this); _node = _node->_prev;return tmp;}// 比较运算符booloperator==(const Self& s)const{return _node == s._node;}booloperator!=(const Self& s)const{return _node != s._node;}};// ReverseIterator.h#pragmaonce#include"Common.h"namespace lcz {template<classIterator,classRef,classPtr>structReverseIterator{typedef ReverseIterator<Iterator, Ref, Ptr> Self;// 存储正向迭代器 Iterator _it;// 构造函数ReverseIterator(Iterator it):_it(it){}// 解引用:const版本+非const版本(保证const正确性) Ref operator*(){ Iterator tmp = _it;return*(--tmp);// 反向迭代器解引用 = 正向迭代器退一位} Ref operator*()const{ Iterator tmp = _it;return*(--tmp);}// 箭头运算符 Ptr operator->(){return&(operator*());} Ptr operator->()const{return&(operator*());}// 前置++:反向迭代器++ → 正向迭代器-- Self&operator++(){--_it;return*this;}// 后置++ Self operator++(int){ Self tmp(*this);--_it;return tmp;}// 前置--:反向迭代器-- → 正向迭代器++ Self&operator--(){++_it;return*this;}// 后置--(修复原代码错误:--_it → ++_it) Self operator--(int){ Self tmp(*this);++_it;// 反向迭代器后置-- = 正向迭代器后置++return tmp;}// 比较运算符booloperator!=(const Self& s)const{return _it != s._it;}booloperator==(const Self& s)const{return _it == s._it;}};}// vector.h#pragmaonce#include"ReverseIterator.h"namespace lcz {template<classT>classvector{public:// 正向迭代器typedef T* iterator;typedefconst T* const_iterator;// 反向迭代器typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator,const T&,const T*> const_reverse_iterator;// 构造函数vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}// 初始化列表构造(支持v = {1,2,3,4})vector(initializer_list<T> il){ size_t n = il.size(); _start =new T[n]; _finish = _start + n; _endofstorage = _finish;copy(il.begin(), il.end(), _start);}// 析构函数~vector(){if(_start){delete[] _start; _start = _finish = _endofstorage =nullptr;}}// 正向迭代器接口 iterator begin(){return _start;} iterator end(){return _finish;} const_iterator begin()const{return _start;} const_iterator end()const{return _finish;}// 反向迭代器接口 reverse_iterator rbegin(){returnreverse_iterator(end());} reverse_iterator rend(){returnreverse_iterator(begin());} const_reverse_iterator rbegin()const{returnconst_reverse_iterator(end());} const_reverse_iterator rend()const{returnconst_reverse_iterator(begin());}private: iterator _start =nullptr;// 数组起始 iterator _finish =nullptr;// 有效数据末尾 iterator _endofstorage =nullptr;// 容量末尾};}// list.h#pragmaonce#include"ReverseIterator.h"namespace lcz {template<classT>classlist{typedef ListNode<T> Node;public:// 正向迭代器typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T,const T&,const T*> const_iterator;// 反向迭代器typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator,const T&,const T*> const_reverse_iterator;// 构造函数:初始化头节点(双向循环链表)list(){ _head =new Node; _head->_prev = _head; _head->_next = _head; _size =0;}// 初始化列表构造(支持lt = {1,2,3,4})list(initializer_list<T> il):list(){for(constauto& val : il){push_back(val);}}// 析构函数~list(){clear();delete _head; _head =nullptr; _size =0;}// 尾插(初始化列表依赖)voidpush_back(const T& val){ Node* new_node =newNode(val); Node* tail = _head->_prev; tail->_next = new_node; new_node->_prev = tail; new_node->_next = _head; _head->_prev = new_node; _size++;}// 清空链表voidclear(){ iterator it =begin();while(it !=end()){ it =erase(it);}}// 删除节点 iterator erase(iterator pos){ Node* cur = pos._node; Node* prev = cur->_prev; Node* next = cur->_next; prev->_next = next; next->_prev = prev;delete cur; _size--;returniterator(next);}// 正向迭代器接口 iterator begin(){returniterator(_head->_next);} iterator end(){returniterator(_head);} const_iterator begin()const{returnconst_iterator(_head->_next);} const_iterator end()const{returnconst_iterator(_head);}// 反向迭代器接口 reverse_iterator rbegin(){returnreverse_iterator(end());} reverse_iterator rend(){returnreverse_iterator(begin());} const_reverse_iterator rbegin()const{returnconst_reverse_iterator(end());} const_reverse_iterator rend()const{returnconst_reverse_iterator(begin());}private: Node* _head =nullptr; size_t _size =0;};}//test.cpp#include"list.h"#include"vector.h"intmain(){// 测试list反向迭代器 cout <<"list反向遍历:"; lcz::list<int> lt ={1,2,3,4}; lcz::list<int>::reverse_iterator rit = lt.rbegin();while(rit != lt.rend()){ cout <<*rit <<" ";// 输出:4 3 2 1++rit;} cout << endl;// 测试vector反向迭代器 cout <<"vector反向遍历:"; lcz::vector<int> v ={1,2,3,4}; lcz::vector<int>::reverse_iterator vit = v.rbegin();while(vit != v.rend()){ cout <<*vit <<" ";// 输出:4 3 2 1++vit;} cout << endl;return0;}

3. 逆波兰表达式介绍(也叫后缀表达式)

我们⽇常写的计算表达式都是中缀表达式,也就是运算符在中间,运算数在两边,但是直接读取⽆法⻢上进⾏运算因为⼀个计算表达式还涉及运算符优先级问题.如:1-2*(3-4)+5 中遇到-和*都⽆法运算,因为后⾯还有括号优先级更⾼.所以其中⼀种实现思路是把中缀表达式转换为后缀表达式,也就是说分析计算表达式的优先级,将运算符放到前⾯,运算符放到运算数的后⾯,然后我们依次读取后缀表达式,遇到运算符就可以进⾏运算了.后缀表达式也就做逆波兰表达式(Reverse Polish Notation, RPN),这种表示法由波兰逻辑学家J·卢卡西维兹于1929年提出,后来被⼴泛应⽤于计算机科学中.

3.1后缀表达式进⾏运算

后缀表达式因为已经确定好优先级,运算符⽅式⾮常简单,就是遇到运算符时,取前⾯的两个运算符进⾏运算,因为经过中缀转后缀优先级已经确定好了.建⽴⼀个栈存储运算数,读取后缀表达式,遇到运算数⼊栈,遇到运算符,出栈顶的两个数据进⾏运算,运算后将结果作为⼀个运算数⼊栈继续参与下⼀次的运算.读取表达式结束后,最后栈⾥⾯的值就是运算结果.

3.2逆波兰表达式求值

//后缀表达式进⾏运算classSolution{public:intevalRPN(vector<string>& tokens){ stack<int> st;for(auto str : tokens){if(str =="+"|| str =="-"|| str =="*"|| str =="/"){// 运算符,运算,运算结果入栈int right = st.top(); st.pop();int left = st.top(); st.pop();switch(str[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;default:break;}}else{// 运算数入栈 st.push(stoi(str));}}return st.top();}};

3.3中缀表达式转后缀表达式(含代码实现)

1️⃣依次读取计算表达式中的值,遇到运算数直接输出.
2️⃣建⽴⼀个栈存储运算符,利⽤栈后进新出性质,遇到后⾯运算符,出栈⾥⾯存的前⾯运算符进⾏⽐较,确定优先级.
3️⃣遇到运算符,如果栈为空或者栈不为空且当前运算符⽐栈顶运算符优先级⾼,则当前运算符⼊栈.因为如果栈⾥⾯存储的是前⼀个运算符,当前运算符⽐前⼀个优先级⾼,说明前⼀个不能运算,当前运算符也不能运算,因为后⾯可能还有更⾼优先级的运算符.
4️⃣遇到运算符,如果栈不为为空且当前运算符⽐栈顶运算符优先级低或相等,说明栈顶的运算符可以运算了,则输出栈顶运算符,当前运算符继续⾛前⾯遇到运算符的逻辑.
5️⃣如果遇到(),则把括号的计算表达式当成⼀个⼦表达式,跟上思路类似,进⾏递归处理⼦表达式,处理后转换出的后缀表达式加在前⾯表达式的后⾯即可.
6️⃣计算表达式或者()中⼦表达式结束值,输出栈中所有运算符.
#include<iostream>#include<stack>#include<vector>#include<string>#include<cctype>#include<cassert>usingnamespace std;classSolution{public:// 获取运算符优先级intoperatorPrecedence(char ch){staticconst pair<char,int> opPD[]={{'+',1},{'-',1},{'*',2},{'/',2}};int n =sizeof(opPD)/sizeof(opPD[0]);for(int i =0; i < n;++i){if(opPD[i].first == ch){return opPD[i].second;}}assert(false&&"非法运算符");return-1;}// 中缀转逆波兰表达式(核心函数,栈作为参数传递,避免递归时重建)voidtoRPN(const string& s, size_t& i, vector<string>& rpn, stack<char>& opStack){while(i < s.size()){if(isdigit(s[i])){// 处理多位数(操作数直接加入结果) string num;while(i < s.size()&&isdigit(s[i])){ num += s[i];++i;} rpn.push_back(num);}else{if(s[i]=='('){// 左括号入栈,递归处理括号内子表达式 opStack.push(s[i]);++i;toRPN(s, i, rpn, opStack);}elseif(s[i]==')'){// 右括号:弹出运算符直到左括号(左括号弹出但不加入结果)++i;while(!opStack.empty()&& opStack.top()!='('){ rpn.push_back(string(1, opStack.top())); opStack.pop();}// 弹出左括号(不加入结果)if(!opStack.empty()){ opStack.pop();}// 结束当前递归层,回到外层处理return;}else{// 处理运算符:优先级判断// 栈非空 + 栈顶不是左括号 + 当前运算符优先级 ≤ 栈顶 → 弹出栈顶运算符while(!opStack.empty()&& opStack.top()!='('&&operatorPrecedence(s[i])<=operatorPrecedence(opStack.top())){ rpn.push_back(string(1, opStack.top())); opStack.pop();}// 当前运算符入栈,i递增(关键:避免死循环) opStack.push(s[i]);++i;}}}}// 封装接口:对外提供简洁的调用方式 vector<string>convertToRPN(const string& s){ vector<string> rpn; stack<char> opStack; size_t i =0;toRPN(s, i, rpn, opStack);// 遍历结束后,弹出栈中剩余的运算符while(!opStack.empty()){ rpn.push_back(string(1, opStack.top())); opStack.pop();}return rpn;}// 逆波兰表达式求值(可选:验证转换结果是否正确)intevalRPN(const vector<string>& rpn){ stack<int> st;for(const string& token : rpn){if(isdigit(token[0])||(token.size()>1&& token[0]=='-'&&isdigit(token[1]))){// 处理数字(含负数) st.push(stoi(token));}else{// 处理运算符int b = st.top(); st.pop();int a = st.top(); st.pop();int res =0;if(token =="+") res = a + b;elseif(token =="-") res = a - b;elseif(token =="*") res = a * b;elseif(token =="/") res = a / b;// 整数除法向零取整 st.push(res);}}return st.top();}};intmain(){ Solution sol;// 测试用例1:简单表达式 string str1 ="1+2-3"; vector<string> rpn1 = sol.convertToRPN(str1); cout <<"中缀表达式:"<< str1 << endl; cout <<"逆波兰表达式:";for(constauto& e : rpn1){ cout << e <<" ";// 输出:1 2 + 3 -} cout << endl; cout <<"计算结果:"<< sol.evalRPN(rpn1)<< endl;// 输出:0 cout <<"------------------------"<< endl;// 测试用例2:带括号的复杂表达式 string str2 ="1+2-(3*4+5)-7"; vector<string> rpn2 = sol.convertToRPN(str2); cout <<"中缀表达式:"<< str2 << endl; cout <<"逆波兰表达式:";for(constauto& e : rpn2){ cout << e <<" ";// 输出:1 2 + 3 4 * 5 + - 7 -} cout << endl; cout <<"计算结果:"<< sol.evalRPN(rpn2)<< endl;// 输出:1+2=3; 3*4=12+5=17; 3-17=-14; -14-7=-21return0;}

4.基本计算器的实现


有了上⾯两个部分计算器OJ的⼤部分问题就解决了,但是这⾥还有⼀些问题需要处理.因为OJ中给的中缀表达式是字符串,字符串中包含空格,需要去掉空格.其次就是负数和减号,要进⾏区分,将所有的负数-x转换为0-x.
classSolution{public:intoperatorPrecedence(char ch){structopPD{char _op;int _pd;};static opPD arr[]={{'+',1},{'-',1},{'*',2},{'/',2}};for(auto& e : arr){if(e._op == ch){return e._pd;}}assert(false);return-1;}voidtoRPN(const string& s, size_t& i, vector<string>& v){ stack<char> st;while(i < s.size()){if(isdigit(s[i])){// 运算数输出 string num;while(i < s.size()&&isdigit(s[i])){ num += s[i];++i;} v.push_back(num);}else{if(s[i]=='('){// 递归⽅式处理括号中的⼦表达式++i;toRPN(s, i, v);}elseif(s[i]==')'){++i;// 栈中的运算符全部输出while(!st.empty()){ v.push_back(string(1, st.top())); st.pop();}// 结束递归return;}else{// 运算符// 1、如果栈为空或者栈不为空且当前运算符⽐栈顶运算符优先级⾼,则当前运算符⼊栈// 2、如果栈不为空且⽐栈顶运算符优先级低或相等,说明栈顶的运算符可以运算了,输出栈顶运算符,当前运算符继续⾛前⾯遇到运算符的逻辑if(st.empty()||operatorPrecedence(s[i])>operatorPrecedence(st.top())){ st.push(s[i]);++i;}else{ v.push_back(string(1, st.top())); st.pop();}}}}// 栈中的运算符全部输出while(!st.empty()){ v.push_back(string(1, st.top())); st.pop();}}longlongevalRPN(const vector<string>& tokens){ stack<longlong> s;for(size_t i =0; i < tokens.size();++i){const string& str = tokens[i];// str为数字if(!("+"== str ||"-"== str ||"*"== str ||"/"== str)){ s.push(stoll(str));}else{// str为操作符longlong right = s.top(); s.pop();longlong left = s.top(); s.pop();switch(str[0]){case'+': s.push(left + right);break;case'-': s.push(left - right);break;case'*': s.push(left * right);break;case'/': s.push(left / right);break;}}}return s.top();}intcalculate(string s){// 1、去除所有空格,否则下⾯的⼀些逻辑没办法处理 std::string news; news.reserve(s.size());for(auto ch : s){if(ch !=' ') news += ch;} s.swap(news); news.clear();// 2、将所有的负数-x转换为0-xfor(size_t i =0; i < s.size();++i){if(s[i]=='-'&&(i ==0||(!isdigit(s[i -1])&& s[i -1]!=')'))) news +="0-";else news += s[i];}// 中缀表达式转成后缀表达式 size_t i =0; vector<string> v;toRPN(news, i, v);// 后缀表达式进⾏运算returnevalRPN(v);}};
解题思路
1️⃣表达式预处理
去除空格:首先去除表达式中的所有空格,简化后续处理逻辑.
负数特殊处理:对于出现在表达式开头或左括号后的负号,需要特殊处理.例如,将 "-x" 转换为 "0-x",但为了避免溢出,我们直接将整个负数作为一个完整的数字解析(如 "-2147483648" 作为一个整体).
2️⃣中缀表达式转后缀表达式(逆波兰表达式)
使用递归下降法:由于括号会改变运算顺序,我们使用递归的方式处理括号内的子表达式.具体地,遇到左括号时递归处理,直到遇到右括号返回.
运算符优先级:设置加减为1级,乘除为2级.在转换过程中,如果当前运算符优先级高于栈顶运算符,则入栈;否则将栈顶运算符弹出并加入后缀表达式,直到栈空或遇到更低优先级的运算符.
数字直接输出:遇到数字时,将连续的数字字符拼接成一个完整的数字字符串,直接加入后缀表达式.
3️⃣后缀表达式求值
使用栈模拟:遍历后缀表达式,遇到数字则压栈,遇到运算符则弹出栈顶两个数字进行运算,并将结果压栈.
处理大数:由于可能涉及边界值(如 -2147483648),使用 long long 类型进行计算,避免溢出.最后将结果转换为 int 返回.

敬请期待下一篇文章内容–>C++模板相关内容!


Read more

Ubuntu 22.04环境下libwebkit2gtk-4.1-0安装超详细版

Ubuntu 22.04 下编译安装 libwebkit2gtk-4.1-0 :从踩坑到实战的完整指南 你有没有遇到过这样的情况? 在 Ubuntu 22.04 上准备运行一个基于 GTK 的 WebView 应用,兴冲冲地敲下: sudo apt install libwebkit2gtk-4.1-0 结果终端冷冰冰地回你一句: E: Unable to locate package libwebkit2gtk-4.1-0 那一刻,是不是感觉空气都凝固了?明明文档写着支持,系统却说“没这玩意儿”。更离谱的是,连 apt search webkit 都只能搜出一堆 4.0 版本的包。 别急——这不是你的错。这是 Ubuntu 22.

By Ne0inhk

【WebApi】C#创建WebApi学习

1、创建WebApi项目 Prgram.cs代码保留如下 var builder = WebApplication.CreateBuilder(args); // Add services to the container. var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.Run(); 2、Minmal APIs最小API使用 Prgram.cs中进行最小API使用 var builder = WebApplication.CreateBuilder(args); // Add services to the container. var app = builder.Build(); // Configure the HTTP request

By Ne0inhk

深度解析KBQA常用数据集:WebQSP与CWQ

深度解析KBQA常用数据集:WebQSP与CWQ 一、引言 知识图谱问答(KBQA)是自然语言处理领域的关键任务,其核心挑战在于将自然语言问题转换为可执行的逻辑形式(如SPARQL查询)并从知识图谱中获取答案。WebQSP和CWQ是当前KBQA研究中最具代表性的两个数据集,分别覆盖了从多跳到复杂组合性问题的全场景。本文将从数据形式、标注特点、核心挑战等维度对两者进行深度解析,并对比其在KBQA研究中的定位与价值。 二、WebQSP数据集:多跳推理的基石 2.1 数据集概况 * 全称:WebQuestionsSP(扩展自WebQuestions) * 来源:基于Freebase知识图谱构建,由Berant等人于2013年提出,后经扩展支持多跳推理。 * 规模:训练集约4,700条,测试集约2,000条。 * 问题类型:多跳关系推理(最多4跳),需结合实体、关系和约束条件。 2.2 数据形式详解(基于WebQSP-train实例深度解析) WebQSP的每条数据以JSON格式组织,包含从原始问题到逻辑形式、推理路径、答案的完整标注。以下结合WebQTrn-0实例(关于

By Ne0inhk
Glide播放webp动画的一些坑

Glide播放webp动画的一些坑

问题现象 使用Glide图片加载框架加载webp的时候默认会将一个资源加载一份然后缓存起来,之后引用相同资源id会始终返回这同一个缓存。这本身是一个很常见的优化手段,但是遇到Android原生的AnimatedImageDrawable就会有问题。因为Glide内部如果不做自定义Module的话,默认加载的webp图片就是使用的AnimatedImageDrawable类。 1. 如果有多个view通过Glide显示同一个webp资源,会导致播放进度强制一致。 其实我是先开始start播放上面的ImageView,然后再将图片设置到了下面的ImageView。结果后start的开始时机被强制绑定到了和正在播放的一起。 2. 任何一个调用了停止其他的也会跟随停止。 这里我调用Glide.with(this).clear(imageView2);将第二个ImageView播放停止并清楚不显示,导致第一个ImageView也跟着停止了播放。 3. 同一个view需要隐藏后再重头播放会导致开始时候闪现一下停止时的那一帧的问题。 这里我将同一个资源在上面ImageView停止

By Ne0inhk