使用C++手写栈 - Stack

本文详细介绍如何使用C++从零开始实现一个栈数据结构,包括接口定义、基于动态数组的实现,以及栈的常用操作如push、pop、top和empty。此外,还提供了测试代码和一个使用栈进行中缀表达式求值的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用C++手写栈

在(一)中,对动态数组进行了简单的封装,实现了简易Vector, 这次不妨就来实际用一下我们的Vector。
无论是否学过数据结构,你大概都会听说过"栈",栈最主要也是最重要的特性就是FILO – 先进后出,或者说,满足先进后出的就是栈(Stack),所以Stack与我们说的vector、list、map等不同,Stack只定义了一组操作,只要实现了这些操作就是所谓栈,底层的实现显得不那么重要。
  • 所以,我们先来定义一个接口IStack,然后再用其他的数据结构实现。
    一个栈所需的操作主要有,
    1. push, 将元素推入栈
    2. pop, 将元素弹出栈
    3. top, 取栈顶的元素
    4. 为了方便我们还提供一个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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值