计算器——可支持小数的任意四则运算(中缀表达式转为后缀表达式算法)

news/2024/7/24 12:26:46 标签: 算法, c++, 表达式, 计算器

中缀表达式转为后缀表达式的原理过程主要包括以下步骤:

1. 初始化两个栈,一个用于存储操作数,一个用于存储运算符。
2. 从左到右扫描中缀表达式的每个字符。
3. 如果遇到数字,则直接将其压入操作数栈。
4. 如果遇到运算符,则分两种情况处理:如果运算符优先级大于等于栈顶运算符的优先级,则将栈顶运算符弹出并压入后缀表达式,直到栈为空或者栈顶运算符的优先级低于当前运算符为止,然后将当前运算符压入栈;如果运算符优先级小于栈顶运算符的优先级,则直接将当前运算符压入栈。
5. 表达式扫描完毕后,如果栈中仍有剩余的运算符,则将这些运算符依次弹出并压入后缀表达式

6. 最后,后缀表达式中剩余的元素即为转换后的结果。

        需要注意的是,在实际应用中,可能还需要进行一些额外的处理,比如补全缺失的括号,以确保表达式的正确性。

 (括号法)

完整代码(注释都在代码中):

#include <iostream>//用于输入输出操作
#include <stack>//用于实现栈数据结构
#include <string>//用于处理字符串
#include <sstream>//用于字符串流操作
#include <cctype>//用于字符处理函数
#include <stdexcept>// 用于异常处理

using namespace std;
//用于判断给定的字符是否为运算符。
//如果字符是加号、减号、乘号或除号,则返回 true,否则返回 false。
bool is_operator(char c) {
    return c == '+' || c == '-' || c == '*' || c == '/';
}

//用于确定运算符的优先级。
//对于加号和减号,优先级为 1;对于乘号和除号,优先级为 2。其他字符的优先级为 0。
int precedence(char op) {
    if (op == '+' || op == '-') {
        return 1;
    }
    else if (op == '*' || op == '/') {
        return 2;
    }
    else {
        return 0;
    }
}

//用于应用运算符并返回结果。
//根据传入的运算符,执行相应的加法、减法、乘法或除法操作,并返回结果。如果传入的运算符无效,则抛出运行时错误。
double apply_operator(double a, double b, char op) {
    switch (op) {
    case '+': return a + b;
    case '-': return a - b;
    case '*': return a * b;
    case '/': return a / b;
        //如果某个函数执行失败,可以抛出一个 std::runtime_error 异常对象,并提供相应的错误信息。
    default: throw runtime_error("Invalid operator");
    }
}

//用于计算给定的数学表达式。
//它使用两个栈来存储数字和运算符。
//数字栈用于存储操作数,运算符栈用于存储运算符。
double evaluate_expression(const string& expression) {
    stack<double> num_stack;
    stack<char> op_stack;

    //这个循环遍历整个表达式字符串。对于每个字符,根据其类型执行相应的操作。如果是空格,则跳过;
    //如果是数字或小数点,则解析出完整的数字并将其压入数字栈;
    //如果是运算符,则将其与运算符栈顶的运算符进行比较,并根据优先级决定是否立即应用运算符。
    //如果是左括号,则将其压入运算符栈;
    //如果是右括号,则将匹配的左括号弹出,并将括号内的表达式计算出来。
    //如果遇到无效字符,则抛出运行时错误。
    for (size_t i = 0; i < expression.length(); ++i) {
        //size_t 是一种无符号整数类型,使用 size_t 类型作为循环变量的类型是为了确保能够正确处理表达式的长度,并提高代码的可移植性。
        if (isspace(expression[i])) {
            //isspace(expression[i])用于处理输入的字符串,以便在对字符串进行处理之前先识别和处理其中的空白字符。
            // 执行了对字符串 expression 在索引 i 处的字符进行空白字符判断。
            //如果返回结果为 true,则表示该字符是空白字符;如果返回结果为 false,则表示该字符不是空白字符。
            continue;
        }

        /* 这段代码是一个条件判断和循环的代码块。它的作用是找到一个数字或小数点开始的连续字符序列。
        首先,使用 isdigit(expression[i]) || expression[i] == '.' 判断表达式 expression 在索引 i 处的字符是否为数字或小数点。如果是,则执行以下代码块。
        在代码块中,定义了一个新的变量 j 并将其初始化为 i。然后,使用一个循环来迭代从 j 开始的字符序列。
        在循环的每一次迭代中,首先检查 j 是否超出了字符串 expression 的长度,并且判断 expression[j] 是否是数字或小数点。如果是,就将 j 的值增加 1,继续下一次迭代。
        这个循环会一直持续,直到遇到一个不是数字或小数点的字符,或者到达了字符串 expression 的结尾。在循环结束后,变量 j 将指向字符序列的下一个位置。
        这段代码的目的是找到一个数字或小数点开始的连续字符序列,以便后续处理该数字或小数点。*/
        else if (isdigit(expression[i]) || expression[i] == '.') {
            size_t j = i;
            while (j < expression.length() && (isdigit(expression[j]) || expression[j] == '.')) {
                ++j;
            }

            /*这段代码的作用是将找到的连续数字或小数点字符序列转换为一个双精度浮点数,并将其压入一个名为 num_stack 的栈中。
            首先,通过 expression.substr(i, j - i) 获取从索引 i 到索引 j - 1 的子字符串,该子字符串包含了找到的连续数字或小数点字符序列。
            然后,创建一个 stringstream 对象 ss 并将该子字符串传递给它。stringstream 类提供了一种将字符串转换为其他类型的数据的方法。
            接下来,使用 ss >> number 将 ss 中的字符串转换为一个双精度浮点数,并将其存储在变量 number 中。
            最后,将变量 number 压入名为 num_stack 的栈中,以便后续处理。
            最后一行的 i = j - 1 的目的是将变量 i 更新为 j - 1 的值,以便在循环的下一次迭代中,跳过已经处理过的字符序列。
            总之,这段代码的作用是将找到的连续数字或小数点字符序列转换为双精度浮点数,并将其存储在一个栈中,以便后续处理。*/
            double number;
            stringstream ss(expression.substr(i, j - i));
            ss >> number;
            num_stack.push(number);
            i = j - 1;
        }


        /* 这段代码用于处理表达式中的操作符。
         首先,通过调用 is_operator(expression[i]) 来判断当前字符 expression[i] 是否为操作符。
         如果是操作符,则进入一个循环。循环的条件是操作符栈 op_stack 不为空,并且栈顶操作符的优先级大于或等于当前操作符 expression[i] 的优先级。
         在循环中,首先从操作数栈 num_stack 中弹出栈顶的两个双精度浮点数,分别存储在变量 b 和 a 中。这两个操作数分别代表了运算符左侧和右侧的操作数。
         然后,从操作符栈 op_stack 中弹出栈顶的操作符,并将其存储在变量 op 中。
         接下来,调用 apply_operator(a, b, op) 函数,对操作数 a 和 b 应用操作符 op 进行计算,并将结果压入操作数栈 num_stack 中。
         完成内层循环后,将当前操作符 expression[i] 压入操作符栈 op_stack 中。
         总之,这段代码的作用是处理表达式中的操作符。它会从操作数栈中弹出两个操作数和一个操作符,并进行相应的计算,然后将计算结果压入操作数栈中。
         这个过程会不断重复,直到所有的操作符都被处理完毕。*/
        else if (is_operator(expression[i])) {
            while (!op_stack.empty() && precedence(op_stack.top()) >= precedence(expression[i])) {
                double b = num_stack.top();
                num_stack.pop();
                double a = num_stack.top();
                num_stack.pop();
                char op = op_stack.top();
                op_stack.pop();
                num_stack.push(apply_operator(a, b, op));
            }
            op_stack.push(expression[i]);
        }



        //这段代码处理括号的情况。
        //首先,通过比较 expression[i] 是否等于左括号 '(' 来判断当前字符是否为左括号。
        //如果是左括号,则将其压入操作符栈 op_stack 中。
        //接下来,通过比较 expression[i] 是否等于右括号 ')' 来判断当前字符是否为右括号。
        //如果是右括号,则进入一个循环。循环的条件是操作符栈 op_stack 不为空,并且栈顶的操作符不是左括号 '('。
        //在循环中,首先从操作数栈 num_stack 中弹出栈顶的两个双精度浮点数,分别存储在变量 b 和 a 中。
        //然后,从操作符栈 op_stack 中弹出栈顶的操作符,并将其存储在变量 op 中。
        //接下来,调用 apply_operator(a, b, op) 函数,对操作数 a 和 b 应用操作符 op 进行计算,并将结果压入操作数栈 num_stack 中。
        //完成内层循环后,如果操作符栈 op_stack 为空,或者栈顶的操作符不是左括号 '(',则抛出运行时错误 "Mismatched parentheses",表示括号不匹配。
        //最后,如果操作符栈 op_stack 的栈顶操作符是左括号 '(',则将其弹出。
        //总之,这段代码的作用是处理括号。当遇到左括号时,将其压入操作符栈中;当遇到右括号时,将操作符栈中的操作符逐个弹出并进行计算,直到遇到左括号为止。
         //如果括号不匹配,则抛出运行时错误。如果所有的操作符都处理完毕后,操作符栈应该为空。如果不为空,则表示括号不匹配。最后,将左括号从操作符栈中弹出。
        else if (expression[i] == '(') {
            op_stack.push(expression[i]);
        }
        else if (expression[i] == ')') {
            while (!op_stack.empty() && op_stack.top() != '(') {
                double b = num_stack.top();
                num_stack.pop();
                double a = num_stack.top();
                num_stack.pop();
                char op = op_stack.top();
                op_stack.pop();
                num_stack.push(apply_operator(a, b, op));
            }
            if (op_stack.empty() || op_stack.top() != '(') {
                throw runtime_error("Mismatched parentheses");
            }
            op_stack.pop();
        }
        else {
            throw runtime_error("Invalid character");
        }
    }

    //这个循环处理剩余的运算符,直到运算符栈为空。对于每个运算符,从数字栈中弹出两个操作数,然后应用运算符并将结果压入数字栈。
    while (!op_stack.empty()) {
        double b = num_stack.top();
        num_stack.pop();
        double a = num_stack.top();
        num_stack.pop();
        char op = op_stack.top();
        op_stack.pop();
        num_stack.push(apply_operator(a, b, op));
    }

    //最后,检查数字栈中是否只剩下一个元素。如果不是,则说明表达式无效,抛出运行时错误。否则,返回数字栈中的唯一元素作为计算结果。
    if (num_stack.size() != 1) {
        throw runtime_error("Invalid expression");
    }

    return num_stack.top();
}

//在主函数中,首先提示用户输入一个表达式。然后调用evaluate_expression函数计算表达式的结果,并将结果输出。
//如果在计算过程中发生错误,则捕获并输出错误信息。最后返回0表示程序成功结束。
int main() {
    string expression;
    cout << "Enter an expression: ";
    //getline()函数是C++标准库中的一个字符串输入函数,用于从输入流中读取一行文本并存储到字符串对象中。
    //使用getline()函数可以方便地读取包含空格和其他特殊字符的文本行,它会一直读取输入流直到遇到换行符或文件结束符。
    getline(cin, expression);
    //程序会提示用户输入一行文本,然后使用getline()函数读取输入的文本并存储到expression字符串中,最后输出读取到的文本。

    try {
        double result = evaluate_expression(expression);
        cout << "Result: " << result << endl;
    }
    catch (const runtime_error& e) {
        // // 处理异常的代码块
        cerr << "Error: " << e.what() << endl;
    }

    return 0;
}

希望对你有帮助!加油各位!


http://www.niftyadmin.cn/n/5289652.html

相关文章

Unity3D 安装和下载指南及汉化

Unity3D是一款强大的游戏开发引擎&#xff0c;为开发者提供了丰富的工具和资源&#xff0c;使得游戏制作变得更加简单和高效。本文将介绍Unity3D的安装和下载步骤&#xff0c;以帮助初学者迅速入门。 步骤一&#xff1a;访问Unity官网 首先&#xff0c;打开浏览器&#xff0c…

C++ 之LeetCode刷题记录(三)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅&#xff0c;多学多练&#xff0c;尽力而为。 先易后难&#xff0c;先刷简单的。 13、罗马数字转整数 罗马数字包含以下七种字符: I&#xff0c…

【量化】蜘蛛网策略复现

文章目录 蜘蛛网策略研报概述持仓数据整理三大商品交易所的数据统一筛选共有会员清洗数据计算研报要求数据全部代码 策略结果分析无参数策略有参数策略正做反做 MSD技术指标化 蜘蛛网策略 策略来自《东方证券-股指期货趋势交易之蜘蛛网策略——从成交持仓表中捕捉知情投资者行为…

【Linux驱动】内核定时器控制 LED 闪烁

Linux 提供了定时器&#xff0c;当超过预定时间时&#xff0c;就会触发回调函数&#xff0c;此外&#xff0c;Linux内核还提供了短延时函数&#xff0c;比如微秒、纳秒、毫秒延时函数。 一、内核定时器API 1、节拍数 内核定时器是通过节拍数来计时的&#xff0c;节拍数与时间…

设计模式-注册模式

设计模式专栏 模式介绍模式特点应用场景注册模式和单例模式的区别代码示例Java实现注册模式Python实现注册模式 注册模式在spring中的应用 模式介绍 注册模式是一种设计模式&#xff0c;也称为注册树或注册器模式。这种模式将类的实例化和创建分离开来&#xff0c;避免在应用程…

AGV智能搬运机器人-替代人工工位让物流行业降本增效

在当今快速发展的世界中&#xff0c;物流业面临着巨大的挑战&#xff0c;包括提高效率、降低成本和优化工作流程。为了应对这些挑战&#xff0c;一种新型的自动化设备——智能搬运机器人正在崭露头角。本文将通过一个具体的案例来展示富唯智能转运机器人在实际应用中的价值。 案…

Checkpoint 执行机制原理解析

在介绍Checkpoint的执行机制前&#xff0c;我们需要了解一下state的存储&#xff0c;因为state是Checkpoint进行持久化备份的主要角色。Checkpoint作为Flink最基础也是最关键的容错机制&#xff0c;Checkpoint快照机制很好地保证了Flink应用从异常状态恢复后的数据准确性。同时…

解决xcode15下载模拟器慢以及没有断点续传

问题描述&#xff1a;Xcode15 为了最小化安装包大小&#xff0c;iOS17模拟器需要单独安装。然而下载模拟器的时候&#xff0c;经常出现Could not download iOS... 的下载失败提示。 以下为解决方案&#xff1a; 一、直接下载IOS17模拟器的包 以下两种方式都可以 方法一&…