使用C++手写栈
在(一)中,对动态数组进行了简单的封装,实现了简易Vector, 这次不妨就来实际用一下我们的Vector。
无论是否学过数据结构,你大概都会听说过"栈",栈最主要也是最重要的特性就是FILO – 先进后出,或者说,满足先进后出的就是栈(Stack),所以Stack与我们说的vector、list、map等不同,Stack只定义了一组操作,只要实现了这些操作就是所谓栈,底层的实现显得不那么重要。
- 所以,我们先来定义一个接口IStack,然后再用其他的数据结构实现。
一个栈所需的操作主要有,- push, 将元素推入栈
- pop, 将元素弹出栈
- top, 取栈顶的元素
- 为了方便我们还提供一个empty函数, 返回是否为空
在C++中, 所谓接口即虚基类
// in IStack.h
// 省略了必要的头文件保护
template <typename _Tp>
class IStack {
public:
virtual void push(const _Tp& val) = 0; // 将_Tp类型的元素推入栈
virtual _Tp pop() = 0; // 弹出栈顶的元素, 为了方便一些操作, 返回被删元素的拷贝
virtual const _Tp& top() const = 0; // 栈顶的元素, 只读
virtual bool empty() const = 0; // 辅助函数, 返回栈是否为空
};
- 使用我们的Vector实现, 复用类的操作可以通过继承或组合, 显然, 这里组合更适合.
// in Stack.h , 省略了必要的头文件保护
#include "IStack.h"
#include "Vector.h"
template <typename _Tp>
class Stack : public IStack<_Tp> {
private:
Vector<_Tp> _data;
public:
void push(const _Tp& val) { // 将_Tp类型的元素推入栈
_data.push_back(val); // Vector的尾即Stack的顶
}
_Tp pop() { // 弹出栈顶的元素, 为了方便一些操作, 返回被删元素的拷贝
_Tp val = _data.at(_data.size() - 1); // 备份将要被删除的元素
_data.removeAt(_data.size() - 1); // 删除"尾"元素
return val;
}
const _Tp& top() const { // 栈顶的元素, 只读
return _data.at(_data.size() - 1);
}
bool empty() const { // 辅助函数, 返回栈是否为空
return _data.size() == 0;
}
};
- 为了方便测试功能, 提供print函数对Stack的元素进行打印
// in Stack.h, print已声明为Stack的friend
// 将ostream声明成模板类型而不是std::ostream的原因避免不必要的编译
// iostream之类的头文件内含大量模板, 编译负担较大, 很可能根本用不上print
// 也要编译iostream头文件, 造成负担, 也增加了耦合
template <typename _Tp, typename _OS>
_OS & print(_OS & os, const Stack<_Tp> & stack) {
if(stack.empty()) { return os << "<Empty Stack>\n"; }
for(int i = stack._data.size() - 1; i > -1; --i)
os << " | " << stack._data.at(i) << " |\n";
return os << " bottom \n";
}
- test.cpp
// in main-function
Stack<int> s;
print(std::cout, s) << "\n"
<< "isEmpty : " << s.empty() << "\n";
// 测试push
for (int i = 0; i < 5; ++i) {
s.push(i);
}
std::cout << "isEmpty : " << s.empty() << "\n";
print(std::cout, s) << "\n--------------------------------------\n";
// 测试pop
std::cout << s.pop() << "\n";
print(std::cout, s) << "\n--------------------------------------\n";
std::cout << s.pop() << "\n";
print(std::cout, s) << "\n--------------------------------------\n";
// 测试top
std::cout << "top : " << s.top() << "\n--------------------------------------\n";
- 输出
PS ~~~~> g++ test.cpp -o test; ./test
<Empty Stack>
isEmpty : 1
isEmpty : 0
| 4 |
| 3 |
| 2 |
| 1 |
| 0 |
bottom
--------------------------------------
4
| 3 |
| 2 |
| 1 |
| 0 |
bottom
--------------------------------------
3
| 2 |
| 1 |
| 0 |
bottom
--------------------------------------
top : 2
--------------------------------------
栈的用处有很多, 比如中缀表达式的求值, 括号问题, 改写递归为迭代… 有机会发一篇关于中缀表达式求值的文章
下面附上以前写的中缀表达式的程序, 有兴趣的朋友自取, 里面用的Stack是好久以前写的, 接口有所不同, 在最下面附有中缀求值所用Stack的源码
//
// Created by PGZXB on 2020/3/20.
//
// 建议参考学堂在线邓俊辉老师数据结构课程的栈应用部分
#include<iostream>
#include<ctype.h>
#include<math.h>
#include<string>
#include"stack.h"
using std::cout;
using std::string;
double getNum(char *exp, int& helper);
char orderBetween(char ch1, char ch2);
double calcu(char op, double num);
double calcu(double num1, char op, double num2);
int fact(int num);
double proRPN(char* RPN);
void toRPN(char* exp, char* & RPN, int size);
double proInfixExp1(char* exp);
double proInfixExp2(char* exp);
int main(){
string e;
cout <<"If you want to exit, please type \"exit\"." << std::endl;
while(e != "exit"){
std::getline(std::cin, e); if (e == "exit") break; //user can exit in the way
cout << "->: " << proInfixExp2(&e[0]) << std::endl;
}
cout <<"mycalcu2 exiting" << std::endl;
return 0;
}
double proInfixExp1(char* exp){
Stack<double> potd = Stack<double>();
Stack<char> potr = Stack<char>();
int helper = 0;
potr.push('\0');
char po;
while(!potr.isEmpty()){
// potd.out();
if(isdigit(*exp)){
// potr.out();
potd.push(getNum(exp, helper));
exp += helper;
}else if(*exp==' '){
++exp;
}else{
switch(orderBetween(potr.top(), *exp)){
case '<':
potr.push(*exp);
++exp;
break;
case '=':
potr.pop();
++exp;
break;
case '>':
po = potr.pop();
if(po == '!'){
potd.push(calcu(po, potd.pop()));
}else{
double num2 = potd.pop();
double num1 = potd.pop();
potd.push(calcu(num1, po, num2));
}
break;
default: break;
}
}
}
// potd.out();
// cout<<potd.top()<<"*****";
return potd.top();
}
double proInfixExp2(char* exp){
int size = 0;
for(int i=0; *(exp+i)!='\0'; ++i) ++size;
char *RPN = new char[size*3];
toRPN(exp, RPN, size);
// cout<<"\n"<<RPN<<"\n"; ///
double res = proRPN(RPN);
delete [] RPN;
return res;
}
double getNum(char* exp, int& helper){
double res = 0;
double base = 10;
helper = 0;
while(true){
if(isdigit(*exp) && base==10){
res = res * base + (*exp - '0');
}else if(isdigit(*exp) && base!=10){
base /= 10;
res = res + (*exp - '0') * base;
}else if(*exp == '.'){
base = 1.0;
}else return res;
++exp;
++helper;
}
return res;
}
char orderBetween(char ch1, char ch2){
#define OPTR_N 9
const char pri[OPTR_N][OPTR_N] = { // 优先级表
/* + */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/* - */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/* * */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/* / */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/* ^ */ '>', '>', '>', '>', '>', '<', '<', '>', '>',
/* ! */ '>', '>', '>', '>', '>', '>', '<', '>', '>',
/* ( */ '<', '<', '<', '<', '<', '<', '<', '=', ' ',
/* ) */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
/* \0*/ '<', '<', '<', '<', '<', '<', '<', ' ', '=',
};
int c1(0), c2(0);
char ops[OPTR_N] = {'+', '-', '*', '/', '^', '!', '(', ')', '\0'};
for(int i=0; i<OPTR_N; ++i){
if(ch1 == ops[i]) c1 = i;
if(ch2 == ops[i]) c2 = i;
}
#undef POTR_N
return pri[c1][c2];
}
double calcu(char op, double num){
switch(op){
case '!':
return fact(num);
}
return 0;
}
double calcu(double num1, char op, double num2){
switch(op){
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
return num1 / num2;
case '^':
return pow(num1, num2);
default:
return 0;
}
return 0;
}
int fact(int n){
long long res = 1;
for(int i=1;i<=n;++i){
res *= i;
}
return res;
}
void toRPN(char* exp, char* & RPN, int size){
Stack<char> potr = Stack<char>();
int indexOfRPN = -1;
potr.push('\0');
char po;
while(!potr.isEmpty()){
if(isdigit(*exp) || *exp=='.'){
RPN[++indexOfRPN] = *exp;
++exp;
}else if(*exp==' '){
++exp;
}else{
RPN[++indexOfRPN] = ' ';
switch(orderBetween(potr.top(), *exp)){
case '<':
potr.push(*exp);
++exp;
break;
case '=':
potr.pop();
++exp;
break;
case '>':
RPN[++indexOfRPN] = potr.pop();
break;
default: break;
}
}
}
RPN[++indexOfRPN] = '\0';
}
double proRPN(char* RPN){
Stack<double> op = Stack<double>();
int indexOfRPN = 0;
int helper = 0;
while(RPN[indexOfRPN]!='\0'){
if(isdigit(RPN[indexOfRPN])){
op.push(getNum(&RPN[indexOfRPN], helper));
indexOfRPN += helper;
}else if(RPN[indexOfRPN]==' '){
++indexOfRPN;
}else{
char op_ = RPN[indexOfRPN];
if(op_=='!') op.push(calcu(op_, op.pop()));
else{
double num2 = op.pop();
double num1 = op.pop();
op.push(calcu(num1, op_, num2));
}
++indexOfRPN;
}
}
return op.pop();
}
中缀求值所用Stack的源码
// stack.h
// Created by PGZXB on 2020/3/20.
//
#ifndef INFIXEXPRESSION_PROCESSING_STACK_H
#define INFIXEXPRESSION_PROCESSING_STACK_H
#include<iostream>
#define DEFAULT_SIZE 10
template<typename T>
class Stack
{
private:
int _data_size = DEFAULT_SIZE;
int _size = 0;
T *data;
public:
Stack(int size=DEFAULT_SIZE){ data = new T[size]; _data_size = size; }
~Stack(){ delete [] data; }
void clear(){ _size = 0; }
int size(){ return _size; }
void push(T val){ extend(); data[_size] = val; ++_size; }
T pop(){
if(_size==0) return T(0);
--_size; return data[_size];
}
T top(){ return data[_size-1]; }
bool isEmpty(){ return _size == 0; }
void extend(){
if(_size == _data_size){
T *data_ = new T[_data_size << 2];
for(int i=0;i<_data_size;++i) data_[i] = data[i];
delete [] data;
data = data_;
_data_size = _data_size << 2;
}
}
};
#endif //INFIXEXPRESSION_PROCESSING_STACK_H