c++ 实现计算器(数据结构——栈)

数据结构课程设计——计算器

计算器 的图像结果

目录(可跳转)

1 项目介绍

1.1 背景分析

  计算器,广泛应用于商业交易中,是必备的办公用品之一,也被称作“第一代电子计算机”。与计算机相同,它的内部也由逻辑电路组成,其功能实现也离不开对各条语句的分析,从而解析复杂的表达式,按照计算规则得到正确的结果。

  我们平时最常接触的计算表达式就是“中缀表达式”,然而机器要解析中缀表达式并不容易,尤其是在有括号和各种不同优先级的运算符同时存在的时候,通过对中缀表达式进行转换,使其变成”后缀表达式“,则可以用栈来实现计算的过程。

​  本项目就是一个用栈来实现表达式计算的简易计算器。

1.2 功能分析

基础功能

  • 支持的运算有+,-,*,/,%,^
  • 支持括号运算
  • 支持单目运算符+和-的运算

项目附加功能(测试)

  • 判断计算式的括号是否匹配
  • 检测是否有非法字符出现
  • 自动删除表达式中的空格

2 类结构设计

  本项目拟用栈来实现计算过程,由于没有限制表达式的长度大小,故采用”链式栈“的储存方式,其中,”链式栈类(LinkedStack.h和LinkedStack.cpp)“均直接引用项目”勇闯迷宫“里的类的声明与定义。
  项目除了”链式栈类“之外,还有”计算器类(即Calculator)“,用来实现对计算器各种功能的封装,提高可复用性和可读性。

在这里插入图片描述

  

  1. Calculator类的声明

    class Calculator {
    private:
    	LinkedStack<char> s_input_;			//从键盘读入的,同时含有操作数和操作符的栈
    	LinkedStack<char> s_char_;			//处理过后,只含有操作符的栈
    	LinkedStack<double> s_double_;		//处理过后,只含有操作数的栈
    
    public:
    	Calculator();
    	~Calculator();
    	double getRes();					//中缀转后缀并返回答案
    	bool Input();						//从键盘读入一个中缀表达式,存入栈中
    	bool isLegal();						//判断中缀表达式是否合法的函数
    	bool Calculate(char);				//计算后缀表达式结果
    	int charToDouble(char);				//字符转整型函数,调用后接下来出现的数字都参与转换
    
    	int getIsp(char);					//返回一个操作符的栈内优先级,返回-1表示是非法字符
    	int getIcp(char);					//返回一个操作符的栈外
        优先级,返回-1表示是非法字符
    
    };
    

  

3 功能实现

3.1 输入功能

  

1.运行截图

解释:输入一个以’='结尾的算式,则输入成功

 

在这里插入图片描述
解释:程序不认为回车所控制的”转行“为输入终止条件,程序仅认=符号为输入完毕标志

 
在这里插入图片描述
解释:将表达式全部分行写也可以正常读入,只要以=结尾

 
 
2.代码展示(ps:略去了部分声明和系统提示)

bool Calculator::Input() {
	cout << "请输入您想要计算结果的算式(以“=”结尾)" << endl;
	char temp_char;
	s_double_.makeEmpty();
	s_char_.makeEmpty();
	s_input_.makeEmpty();

	do {
		cin >> temp_char;
		s_input_.Push(temp_char);
	} while (temp_char != '=');
	return true;
}

 
 

3.2 合法性检测

流程图

1.运行截图

在这里插入图片描述
解释:乘法运算是用*来进行的,而不是X,报非法字符的错误,程序不崩溃

 
在这里插入图片描述
解释:算式中有空格,程序自动删除,不报错,不崩溃

 
在这里插入图片描述
解释:算式中括号不匹配,程序输出错误提示,不崩溃





2.代码展示(ps:略去了部分声明和系统提示)

while (!s_input_.isEmpty()) {

		s_input_.Pop(cur_char);							//从输入字符串当中pop一个字符

		if (cur_char >= '0' && cur_char <= '9') {
			temp.Push(cur_char);						//是数字,进入辅助栈
		}		
		else if (cur_char == '+' || cur_char == '-' ||
			cur_char == '*' || cur_char == '/' ||
			cur_char == '%' || cur_char == '^') {
			temp.Push(cur_char);						//是合法字符,进入辅助栈
		}
		else if (cur_char == ')') {
			temp.Push(cur_char);						//是合法字符,进入辅助栈
			parentheses.Push(')');						//由于是逆序,所以是'('栈,')'出栈
		}
		else if (cur_char == '(') {
			temp.Push(cur_char);						//是合法字符,进入辅助栈
			parentheses.Pop(cur_char);					//与')'匹配,出栈
		}
		else if (cur_char == '=') {						//不用管,删除即可
			continue;
		}
		else if (cur_char == ' ') {
			continue;									//空格不算非法字符,删除即可
		}
		else {											//如果都不是,说明为非法字符,输出提示
			cerr << "输入的表达式【含非法字符"
				<<cur_char
				<<"】请重新输入:" << endl;
			return false;
		}
	}
	while (!temp.isEmpty()) {							//把辅助栈里的字符弹出,进入s_input_
		temp.Pop(cur_char);
		s_input_.Push(cur_char);
	}

	if (!parentheses.isEmpty()) {
		cerr << "输入的表达式【括号不匹配】请重新输入:" << endl;
		return false;
	}

 

 

3.3 中缀转后缀+计算

3.3.1 栈的运行图

ps:此处栈的情况更为清晰易懂,流程图反而复杂

s_input_( 左为栈顶 ): 30+2*(105-95) - 560/56 #

当前项进行比较文字说明操作数栈情况操作符栈情况进行的运算
30\数字进栈30#\
++.icp > #.isp符号进栈30#, +\
2\数字进栈30,2#, +\
******.icp > +.isp符号进栈30,2#, +, *\
(( .icp > *****.icp符号进栈30,2#, +, *\
105\数字进栈30,2,105#, +, *, (\
-- .icp > (.icp符号进栈30,2,105#, +, *, ( , -\
95\数字进栈30,2,105,95#, +, *, ( , -\
)) .icp < -.icp出栈,运算30,2,10#, +, *, (105-95=10
) .icp = (.icp出栈30,2,10#, +, *
-- .icp < *.icp出栈,运算30,20#, +2*10=20
- .icp < +.icp出栈,运算50#30+20=50
-.icp > #.isp符号进栈50#, -\
560\数字进栈50,560#, -\
// .icp > #.icp符号进栈50,560#, - , /\
56\数字进栈50,560,50#, -, /\
## .icp < /.icp出栈,运算50,10#, -560/56=10
# .icp < -.icp出栈,运算40#, -50-10=40
#.icp = #.isp循环终止40\\

运行截图


 

2.代码展示(ps:略去了部分声明和系统提示)

getRes()函数

double Calculator::getRes() {
	while (1) {
		if (cur_char >= '0' && cur_char <= '9') {	//如果是数字,则处理这串数字		
			int cur_num = charToDouble(cur_char);	//将相邻几个数字转为double
			s_double_.Push(cur_num);				//得到的数字存入s_double_中
			s_input_.Pop(cur_char);					//继续处理下一个字符
		}
		else {
			s_char_.getTop(top_char);
			if (getIsp(top_char) < getIcp(cur_char)) {		
                //当前操作符的优先级高
				Pop_flag = true;
				s_char_.Push(cur_char);				//储存在字符栈中
				s_input_.Pop(cur_char);				//继续处理下一个字符
			}
			else if (getIsp(top_char) > getIcp(cur_char)) {
				s_char_.Pop(op);					//从字符栈之中退出,进行运算
				Calculate(op);
				Pop_flag = false;
			}
			else {									//优先级一样
				Pop_flag = true;
				s_char_.Pop(op);
				if (op == '(') {
					s_input_.Pop(cur_char);			//继续处理下一个字符
				}
				if (op == '#') {
					break;							//全部计算完毕,退出循环
				}
			}
		}
	}
	s_double_.Pop(res);
	return res;										//返回最终答案
}

Calculate()函数

	bool Calculator::Calculate(char op) {
	//计算后缀表达式结果

	double left, right;								//左右两个操作数
	double res = 0;									//和计算后结果
	s_double_.Pop(right);							//右操作数的出栈
	s_double_.Pop(left);							//左操作数的出栈
	switch (op) {
	case'+':
		res = left + right;
		break;
	case'-':
		res = left - right;
		break;
	case'*':
		res = left * right;
		break;
	case'/':
		res = left / right;
		break;
	case'%':
		res = fmod(left, right);
		break;
	case'^':
		res = pow(left, right);
		break;
	}
	s_double_.Push(res);
	return true;
}

4 亮点和小结

4.1 代码亮点简述

以下简述一下本项目的一些亮点(仅代表个人观点):

  1. 计算器实现了单目运算符 ‘+’ 和 ’ - ’

  2. 合法性检测考虑了”非法字符“,”括号不匹配“,”输入算式中含有括号“三种情况,并对应有提示信息

  3. 项目文档中附加有 ”流程图“ 和 ”工作栈示意图“ ,使读者易懂,思路清晰

  4. 项目的代码风格良好,变量和函数命名统一,有规整的注释帮助理解代码

 

4.2 项目小结/心得

  本项目已经不是第一次使用 ”链式栈“ (勇闯迷宫也用了链式栈),因此在类结构方面没有遇到什么难题,也比较熟练。

  项目在对计算的整个思维结构要求较高,我认为难点有三:

  一是各个函数之间的调用关系,由于函数要求简洁性和专一性,因此其体量较小,因此在”大函数分解为小函数“的过程中会遇到些难题;

  二是单目运算符的处理,本项目采用将+A看作是0+A的运算,将-A看作是0-A的运算,避免了对特殊情况的考虑,极大地减少函数分支数,提高效率,识别单目运算符则采用”判断操作数与操作符数目“的方法进行,一旦操作符个数大于操作数个数,则说明碰到了单目运算符,对其做出特殊标记;

  三是”中缀转后缀“的分支处理,由于我们日常生活中采用的运算方式是中缀表达式,所以对于后缀的形式表现得极其生疏,不论是转化过程还是计算后缀表达式都是一个非常”别扭“的事情,但是随着项目的结束,对于后缀表达式的熟练度已经远超过刚开始做项目的时候,这也算是一个大收获。

  计算器是一个非常具有实际意义的项目,一方面,这是我们生活中经常接触到的东西,另一方面,它的功能的综合性就要求我们在写代码的时候既要有全局观,又要考虑很多细节,因此十分锻炼代码能力,同时,经过这次项目,再一次巩固了栈的知识,加深了对栈的特点的认识。

  二是单目运算符的处理,本项目采用将+A看作是0+A的运算,将-A看作是0-A的运算,避免了对特殊情况的考虑,极大地减少函数分支数,提高效率,识别单目运算符则采用”判断操作数与操作符数目“的方法进行,一旦操作符个数大于操作数个数,则说明碰到了单目运算符,对其做出特殊标记;

  三是”中缀转后缀“的分支处理,由于我们日常生活中采用的运算方式是中缀表达式,所以对于后缀的形式表现得极其生疏,不论是转化过程还是计算后缀表达式都是一个非常”别扭“的事情,但是随着项目的结束,对于后缀表达式的熟练度已经远超过刚开始做项目的时候,这也算是一个大收获。
 
  计算器是一个非常具有实际意义的项目,一方面,这是我们生活中经常接触到的东西,另一方面,它的功能的综合性就要求我们在写代码的时候既要有全局观,又要考虑很多细节,因此十分锻炼代码能力,同时,经过这次项目,再一次巩固了栈的知识,加深了对栈的特点的认识。

  • 14
    点赞
  • 189
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值