简易计算器(详解用栈实现算术表达式求值)

[问题描述]

一个算术表达式是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的。假设操作数是正实数,运算符只含加减乘除等四种运算符,界限符只含左右括号如:6+15*(21-8/4)。编程利用“运算符优先法”求算术表达式的值。

[基本要求]

(1)读入一个合法的算术表达式,输出正确的结果。

(2)考虑算法的健壮性,当表达式错误时,要给出错误原因的提示

(4)实现非整数的处理

实现一个简易计算器,我们要做的事情主要有两个。第一个是判断输入的表达式是否正确;第二个是若表达式正确,则求出运算结果。

1、判断算术表达式是否正确

判断 算术表达式是否正确要充分考虑各种可能的错误情况,这里 我列举出了几种错误情况:
(1) 括号不匹配(遗漏了左括号或右括号);
(2)出现非法字符,如 !/?& 等;
(3)运算符重复,例如1+3**2 中乘号重复;
(4)缺少用于运算的数,例如 (/3+2)+1 、*3+4、2+4/ 。
除了第一种情况外,其他情况都可以用 if 语句很容易地判断出来;但要判断括号是否匹配 就要运用到。这里算法思想是:依次读取表达式中的字符,若是非括号字符,则忽略。若是左括号,则压栈。若是右括号,则与栈顶元素进行匹配,若栈为空,则说明右括号多余(即遗漏了左括号),报错返回;若匹配成功,则栈顶元素弹栈(即一个左括号和一个右括号完成了配对),继续读取下一个字符。直到表达式遍历完毕,若此时栈不为空,则说明有左括号还没有匹配(即遗漏了右括号),报错返回;若栈为空,则说明括号全部配对成功。

2、求出运算结果

我们平常写出的算式对于我们来说是比较直观和容易计算的,这种形式的表达式被称为中缀表达式,但对于计算机而言,中缀表达式是无法直接进行计算的。因为在计算过程中,我们不是读到一个运算符就立即计算,而是要与后面的运算符进行优先级比较,以决定先算哪个,而计算机却做不到这点,但我们可以栈来解决这个问题。具体实现方法如下:

首先,我们创建两个栈 Num 和 Sign ,Num 保存运算数,Sign 保存运算符号。然后,我们依次读取表达式中的字符,若为运算数,则转化为对应数值压入 Num栈中;若为运算符,则和 Sign栈的栈顶运算符进行优先级比较:若栈顶运算符优先级低于当前运算符,则将当前运算符压入Sign栈中;若栈顶运算符优先级高于当前运算符,则 Sign栈弹出运算符、Num栈弹出两个运算数,进行相应计算,并将计算结果压入 Num栈中;但若为 ‘)’ ,则需要将对应左括号从 Sign栈中弹出,这就需要完成两个括号内的所有运算,计算方式同上。直到表达式遍历完毕,Num栈的栈顶元素即为最终结果。

算法思想如此,实现起来却并不简单。例如,我们要将运算数转化为对应数值,由于包括浮点数的情况,这个实现起来就比较复杂。以下是我的程序代码:

# include <iostream>
# include <algorithm>
# include <string>
# include <stack>
using namespace std;
bool CheckOperator(char c);             //判断是否为运算符
bool CheckMatch(string str);            //判断算式是否正确
int Prior(char c);                      //求符号的优先级
bool CheckPrior(char c1, char c2);      //通过优先级判断是否入栈
float CalNum(float a, float b, char c); //求单项运算的结果
float CalResult(string str);            //计算最终结果
int main()
{
	string str;
	cout << "请输入计算式:";
	cin >> str;
	str.append("#");           //作为算式的终止符
	bool t = CheckMatch(str);  //判断算式是否正确
	if (t) {
		float r = CalResult(str);  //计算最终结果
		cout << "计算结果为:" << r << endl;
	}
	return 0;
}

bool CheckOperator(char c)   //判断是否为运算符
{
	if (c == '+' || c == '-' || c == '*' || c == '/') {
		return true;
	}
	else {
		return false;
	}
}

bool CheckMatch(string str)  //判断算式是否正确
{
	if (CheckOperator(str[0]) || CheckOperator(str[str.length() - 2])) {
		//算式首尾位置出现运算符
		cout << "算术表达式错误!\n" << "原因:缺少用于运算的数" << endl;
		return false;
	}
	//判断括号及运算符输入是否正确
	stack<char>S;   //用于存放括号的栈
	for (int i = 0; i < str.length() - 1; i++) {
		if (!CheckOperator(str[i]) && (str[i] < 48 || str[i]>57) && str[i] != '(' && str[i] != ')' && str[i] != '.') {
			//出现非法字符
			cout << "算术表达式错误!\n" << "原因:出现非法字符" << str[i] << endl;
			return false;
		}
		if (CheckOperator(str[i]) && CheckOperator(str[i + 1])) {
			//运算符重复
			cout << "算术表达式错误!\n" << "原因:运算符" << str[i] << "重复" << endl;
			return false;
		}
		if (str[i] == '(')
		{
			S.push(str[i]);
			if (CheckOperator(str[i + 1])) {
				//'('后缺少用于运算的数
				cout << "算术表达式错误!\n" << "原因:缺少用于运算的数" << endl;
				return false;
			}
		}
		if (str[i] == ')')
		{
			if (S.empty()) {
				//若栈已为空,说明遗漏左括号
				cout << "算术表达式错误!\n" << "原因:遗漏左括号" << endl;
				return false;
			}
			S.pop();
		}
	}
	if (!S.empty()) {
		//若此时栈还不为空,说明遗漏右括号
		cout << "算术表达式错误!\n" << "原因:遗漏右括号" << endl;
		return false;
	}
	return true;
}

int Prior(char c)    //返回符号的优先级
{
	switch (c)
	{
	case '#': return 0;
	case '+':
	case '-': return 1;
	case '*':
	case '/': return 2;
	case ')': return 3;
	case '(': return 4;
	}
}

bool CheckPrior(char c1, char c2) //通过优先级判断是否入栈(c1当前,c2栈中)
{
	if (c2 == '(') {
		return true;  //同意进栈
	}
	int p1 = Prior(c1);
	int p2 = Prior(c2);
	if (p1 > p2) {   //当前运算符的优先级较高
		return true; //同意进栈
	}
	else {
		return false; //不同意进栈
	}
}

float CalNum(float a, float b, char c) //求单项运算的结果
{
	switch (c)
	{
	case '+': return a + b;
	case '-': return a - b;
	case '*': return a * b;
	case '/': return a / b;
	}
}

float CalResult(string str) //计算最终结果
{
	stack<float>Num;  //保存运算数的栈
	stack<char>Sign;  //保存运算符的栈
	Sign.push('#');
	float a, b;
	for (int i = 0; i < str.length() && !Sign.empty(); i++) {
		if (str[i] >= 48 && str[i] <= 57) {
			//若为数字
			a = float(str[i]) - 48;           //a保存由字符转化为数字后的值
			int j;
			//将字符转化为数字
			for (j = i + 1; (str[j] == '.') || (j < str.length() && str[j] >= 48 && str[j] <= 57); j++) {
				if (str[j] == '.') {
					//若为小数
					int k, l;
					for (k = j + 1, l = 1; k < str.length() && str[k] >= 48 && str[k] <= 57; k++, l++) {
						b = float(str[k]) - 48;
						a = a + b * pow(10, -l);
					}
					j = k;
					break;
				}
				else {
					b = float(str[j]) - 48;
					a = a * 10 + b;
				}
			}
			i = j - 1;
			Num.push(a);  //运算数进栈
		}
		else {
			//若为运算符
			char c1 = str[i];  //c1当前运算符
			while (1) {
				char c2 = Sign.top();             //c2栈顶运算符
				if (c2 == '#' && str[i] == '#') { //若遇到起始符,并且str也到达末尾
					Sign.pop();
					break;
				}
				bool t = CheckPrior(c1, c2);     //通过优先级判断是否入栈(c1当前,c2栈中)
				if (t) {
					//同意运算符进栈
					int s = Prior(str[i]);
					if (s == 3) {
						//出现')',优先进行括号内的运算
						while ((c2 = Sign.top()) != '(') {
							a = Num.top();  //取出第一个数
							Num.pop();
							b = Num.top();  //取出第二个数
							Num.pop();
							float r = CalNum(b, a, c2);  //求单项运算的结果
							Sign.pop();
							Num.push(r);    //运算得到的新数进栈
						}
						Sign.pop();
					}
					else {
						Sign.push(c1); //运算符进栈
					}
					break;
				}
				else {
					//不同意运算符进栈,执行运算操作
					a = Num.top();
					Num.pop();
					b = Num.top();
					Num.pop();
					float r = CalNum(b, a, c2); //求单项运算的结果
					Sign.pop();
					Num.push(r);
				}
			}
		}
	}
	return Num.top();
}

 运行结果:

请输入计算式:((1+2*3)-1)/2+1.5*3
计算结果为:7.5
请输入计算式:(1+3)*4)-2
算术表达式错误!
原因:遗漏左括号

总结:实现简易计算器主要运用了栈的数据结构。其中主要的操作是通过遍历算数表达式实现的,所以程序总的时间复杂度约为O(n),n是指算数表达式的长度。对于正在练习栈的小伙伴们来说,简易计算器是很不错的练习项目。

以上是我个人的学习成果,很高兴能与大家分享。

  • 20
    点赞
  • 108
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
在C语言中,使用栈(Stack)实现计算器的主要目的是演示递归调用和表达式求值的基本算法,特别是后缀表达式(也称为逆波兰表示法,RPN)。这种方法不需要复杂的符号表或优先级规则,非常适合教学和理解递归。 以下是一个简单的栈实现后缀表达式计算器的例子: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义数据结构 typedef struct { double num; } Number; typedef struct { char operator; Number left, right; } Expression; // 函数声明 void push(Number stack[], int &top); double pop(Number stack[], int &top); char pop_operator(Number stack[], int &top); // 后缀表达式处理函数 double evaluate_postfix(char* exp); int main() { char expression[100]; printf("Enter an expression in postfix notation: "); fgets(expression, sizeof(expression), stdin); expression[strcspn(expression, "\n")] = '\0'; // 去除末尾的换行符 double result = evaluate_postfix(expression); printf("Result: %.2lf\n", result); return 0; } // 栈操作 void push(Number stack[], int &top) { Number n = {atof(stack[top].num)}; top++; stack[top] = n; } double pop(Number stack[], int &top) { top--; return stack[top].num; } char pop_operator(Number stack[], int &top) { top--; return stack[top].operator; } // 表达式求值 double evaluate_postfix(char* exp) { int top = 0; Number stack[100]; // 栈大小可调整 stack = {'0'}; // 初始化栈顶为0 for (int i = 0; exp[i] != '\0'; i++) { if (isdigit(exp[i])) { push(stack, top); // 如果是数字,压入栈 } else { double right = pop(stack, top); double left = pop(stack, top); switch (exp[i]) { case '+': stack[top++] = {left + right}; break; case '- right}; break; case '*': stack[top++] = {left * right}; break; case '/': stack[top++] = {left / right}; break; // 添加其他运算符处理 } } } return pop(stack, top); // 返回结果 } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值