一.概要
东南大学吴健雄学院22级大一暑校大作业
玉米肠,王同学完成了主要的软件框架,五花肉给予了我一些指导,向他们表示感谢,写于文前.
特别地,玉米肠完成了计算部分的主要内容,她对于该软件的编写展现了深厚的热情,也付出了大量的时间.
文件获取可以评论区留下邮箱,或是加该qq号:2969639923,备注mfc计算器.
二. 软件介绍
软件界面如图,函数绘图部分时间比较赶,稍稍有点粗糙.
三.主要算法思想
表达式的识别用到了中缀表达式转后缀表达式
网站上有很多资源,简要介绍一些:如下图:
计算流程:
1.创建一个Calculator对象;
2.获取用户输入的表达式;
3.将表达式转为string类型;
4.调用Calculator类里的calculate(string)函数计算结果;
5.获取错误信息和结果;
6.过滤结果:删除多余的零和小数点并将大数用科学计数法表示;
7.显示结果:错误信息为空则显示结果,否则显示错误信息;
8.更新历史记录:历史记录依次顺移,顶部更新为最新历史记录,底部舍弃。
四.部分代码
文件挺多,包括见面美化,计算,绘图,故不可能全部贴出,只留下部分主要核心代码.计算,绘图部分
#include "pch.h"
#include "Calculator.h"
#include "pch.h"
#include "framework.h"
#include "MFC_Calculator.h"
#include "MFC_CalculatorDlg.h"
#include "afxdialogex.h"
#include "Calculator.h"
#include <stack>
#include <vector>
#include <string>
#include <cmath>
//const int MAX_EXP_LEN = 100; //最大表达式长度
using namespace std;
//算术符号优先权等级
enum PRIO_LV {
PRIO_LV0 = 0,
PRIO_LV1 = 1,
PRIO_LV2 = 2,
PRIO_LV3 = 3,
PRIO_LV4 = 4,
};
Calculator::Calculator() { //构造函数,初始化成员变量
result = 0.0;
//cal_ErrorImfo = "";
}
//表达式自定义标准格式化
void Calculator::getFormat(string infix) {
stdInfix = infix;
size_t l = 0, ll = 0; //检查左右括号个数是否相等
size_t r = 0, rr = 0;
for (size_t i = 0; i < stdInfix.size(); i++) {
if (stdInfix[i] == '(') {
l++;
}
else if (stdInfix[i] == ')') {
r++;
}
}
if (l != r) {
cal_ErrorImfo = "括号输入有误(1)";
}
//实现正负数、sin、cos、tan、log、sqrt、PI
for (size_t i = 0; i < stdInfix.size(); i++) { //string.size()返回size_type类型,避免下标运算时的类型溢出
if (cal_ErrorImfo != "")
break;
if (stdInfix[i] == '.') {
if (i == 0) { //检查小数点前后是否有数字
cal_ErrorImfo = "表达式输入有误";
break;
}
else if (!(stdInfix[i - 1] >= '0' && stdInfix[i - 1] <= '9' && stdInfix[i + 1] >= '0' && stdInfix[i + 1] <= '9')) {
cal_ErrorImfo = "表达式输入有误";
break;
}
}
if (stdInfix[i] == '-' || stdInfix[i] == '+') { //-x转换为0-x,+x转化为0+x
if (i == 0) { //sin转为s、cos转为c、tan转为t、log转为l、sqrt转为q
stdInfix.insert(0, 1, '0'); //PI转为P
}
else if (stdInfix[i - 1] == '(') {
stdInfix.insert(i, 1, '0');
}
}
else if (stdInfix[i] == 'i' || stdInfix[i] == 'o' || stdInfix[i] == 'a') {
string t;
if (stdInfix[i] == 'i')
t = "s";
else if (stdInfix[i] == 'o')
t = "c";
else
t = "t";
size_t tmp = 0;
for (size_t j = i + 2; j < stdInfix.size(); j++) {
if (stdInfix[j] == '(') {
ll++;
}
else if (stdInfix[j] == ')') {
rr++;
}
if (ll == rr) {
tmp = j;
break;
}
}
if (ll != rr) {
cal_ErrorImfo = "括号输入有误(2)";
break;
}
else {
stdInfix.insert(tmp + 1, t);
stdInfix.replace(i - 1, 1, "k");
}
}
else if (stdInfix[i] == 'q') {
stdInfix.erase(i+1, 2);
stdInfix.replace(i - 1, 1, "2");
}
else if (stdInfix[i] == 'P') {
stdInfix.erase(i + 1 , 1);
}
else if (stdInfix[i] == 'l') {
stdInfix.erase(i + 1, 2);
}
}
for (size_t i = 0; i < stdInfix.size(); i++) {
if (stdInfix[i] == 'k') {
stdInfix.erase(i , 3);
}
}
}
//获取算术符号优先级
int Calculator::getPrior(char c) {
if (c == '+' || c == '-') {
return PRIO_LV1;
}
else if (c == '*' || c == '/') {
return PRIO_LV2;
}
else if (c == '%' || c == '^' || c == 'l') {
return PRIO_LV3;
}
else if (c == '!' || c == 's' || c == 'c' || c == 't' || c=='q') {
return PRIO_LV4;
}
else {
return PRIO_LV0;
}
//else { cout << c << 非法符号! << endl; }
}
//后缀表达式转换
void Calculator::getPostfix() {
string tmp;
if (cal_ErrorImfo != "")
goto EndLoop;
//for (int i = 0; i < stdInfix.length(); i++) {
for (size_t i = 0; i < stdInfix.size(); i++) { //string.size()返回size_type类型,避免下标运算时的类型溢出
tmp = "";
switch (stdInfix[i]) {
case '+':
case '-':
case '*':
case '/':
case '%':
case '^':
case 'l':
case 'q':
if (i == 0 ) {
cal_ErrorImfo = "表达式输入有误";
goto EndLoop;
}
if (!(((stdInfix[i - 1] <= '9' && stdInfix[i - 1] >= '0') || stdInfix[i - 1] == ')' || stdInfix[i - 1] == 's' || stdInfix[i - 1] == 'c' || stdInfix[i - 1] == 't' || stdInfix[i - 1] == '!' || stdInfix[i - 1] == 'e' || stdInfix[i - 1] == 'P') && ((stdInfix[i + 1] <= '9' && stdInfix[i + 1] >= '0') || stdInfix[i + 1] == '(' || stdInfix[i + 1] == 'e' || stdInfix[i + 1] == 'P'))) {
cal_ErrorImfo = "表达式输入有误";
goto EndLoop;
}
if (symStack.empty() || symStack.top() == '(') {
symStack.push(stdInfix[i]);
}
else {
while (!symStack.empty() && (getPrior(symStack.top()) >= getPrior(stdInfix[i]))) {
tmp += symStack.top();
postfix.push_back(tmp);
symStack.pop();
tmp = "";
}
symStack.push(stdInfix[i]);
}
break;
case '!':
case 's':
case 'c':
case 't':
if (i == 0) {
cal_ErrorImfo = "表达式输入有误";
goto EndLoop;
}
if (!(((stdInfix[i - 1] <= '9' && stdInfix[i - 1] >= '0') || stdInfix[i - 1] == ')' || stdInfix[i - 1] == 'e' || stdInfix[i - 1] == 'P') && !((stdInfix[i + 1] <= '9' && stdInfix[i + 1] >= '0') || stdInfix[i + 1] == '('))) {
cal_ErrorImfo = "表达式输入有误";
goto EndLoop;
}
if (symStack.empty() || symStack.top() == '(') {
symStack.push(stdInfix[i]);
}
else {
while (!symStack.empty() && (getPrior(symStack.top()) >= getPrior(stdInfix[i]))) {
tmp += symStack.top();
postfix.push_back(tmp);
symStack.pop();
tmp = "";
}
symStack.push(stdInfix[i]);
}
break;
case '(':
if (stdInfix[i + 1] == ')') {
cal_ErrorImfo = "括号输入有误(3)";
goto EndLoop;
}
else
symStack.push(stdInfix[i]);
break;
case ')':
if (symStack.empty()) {
cal_ErrorImfo = "括号输入有误(4)";
goto EndLoop;
}
while (!symStack.empty() && symStack.top() != '(') {
tmp += symStack.top();
postfix.push_back(tmp);
symStack.pop();
tmp = "";
}
if (!symStack.empty() && symStack.top() == '(') {
symStack.pop(); //将左括号出栈丢弃
}
else {
cal_ErrorImfo = "括号输入有误(5)";
goto EndLoop;
}
break;
default:
if ((stdInfix[i] >= '0' && stdInfix[i] <= '9')) {
tmp += stdInfix[i];
while (i + 1 < stdInfix.length() && (stdInfix[i + 1] >= '0' && stdInfix[i + 1] <= '9' || stdInfix[i + 1] == '.')) { //小数处理
tmp += stdInfix[i + 1]; //是连续的数字,则追加
i++;
}
if (tmp[tmp.length() - 1] == '.') {
tmp += '0'; //将x.做x.0处理
}
postfix.push_back(tmp);
}
else if (stdInfix[i] == 'P') {
tmp += 'P';
postfix.push_back(tmp);
}
else if (stdInfix[i] == 'e') {
tmp += 'e';
postfix.push_back(tmp);
}
break;
}//end switch
}//end for
EndLoop:
//if(!symStack.empty()) {
while (!symStack.empty() && cal_ErrorImfo == "") { //将栈中剩余符号加入后缀表达式
tmp = "";
tmp += symStack.top();
if (tmp == "(") {
cal_ErrorImfo = "括号输入有误(6)";
break;
}
postfix.push_back(tmp);
symStack.pop();
}
}
//获取运算结果
void Calculator::calResult() {
string tmp;
double number = 0;
double op1 = 0, op2 = 0;
for (size_t i = 0; i < postfix.size(); i++) {
if (cal_ErrorImfo != "")
break;
tmp = postfix[i];
if (tmp[0] >= '0' && tmp[0] <= '9') {
number = atof(tmp.c_str());
figStack.push(number);
}
else if (tmp[0] == 'P') {
number = 3.14159265358979323846; //将P数字化
figStack.push(number);
}
else if (tmp[0] == 'e') {
number = 2.71828182845904523536; //将e数字化
figStack.push(number);
}
else if (postfix[i] == "+") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
figStack.push(op1 + op2);
}
else if (postfix[i] == "-") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
figStack.push(op1 - op2);
}
else if (postfix[i] == "*") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
figStack.push(op1 * op2);
}
else if (postfix[i] == "/") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
if (op2 != 0) {
figStack.push(op1 / op2);
}
else {
cal_ErrorImfo = "除数不能为0";
break;
}
}
else if (postfix[i] == "%") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
if (op2 != 0) {
figStack.push(fmod(op1, op2)); //可进行小数求余
}
else {
cal_ErrorImfo = "除数不能为0";
break;
}
}
else if (postfix[i] == "^") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
figStack.push(pow(op1, op2));
}
else if (postfix[i] == "q") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
if (op2 >= 0) {
figStack.push(pow(op2, 1 / op1));
}
else {
cal_ErrorImfo = "底数不能小于0";
break;
}
}
else if (postfix[i] == "l") {
if (!figStack.empty()) {
op2 = figStack.top();
figStack.pop();
}
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
if (op2 > 0 && op1 > 0 && op1 != 1) {
figStack.push(log(op2) / log(op1));
}
else if (op2 <= 0){
cal_ErrorImfo = "真数必须大于0";
break;
}
else if (op1 <= 0 || op1==1) {
cal_ErrorImfo = "底数必须大于0且不能为1";
break;
}
}
else if (postfix[i] == "!") {
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
}
if (op1 > 0) {
//阶乘数应大于0;为小数时(转化为整数求阶)
double factorial = 1;
for (int i = 1; i <= op1; ++i)
{
factorial *= i;
}
op1 = factorial;
}
else if (op1 == 0)
op1 = 1;
figStack.push(op1);
}
else if (postfix[i] == "s") {
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
op1 = sin(op1);
}
figStack.push(op1);
}
else if (postfix[i] == "c") {
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
op1 = cos(op1);
}
figStack.push(op1);
}
else if (postfix[i] == "t") {
if (!figStack.empty()) {
op1 = figStack.top();
figStack.pop();
if (fmod((abs(op1) / 3.14159265358979323846 * 2), 2) == 1) { //tan(+-(pi/2的奇数倍))不可取
cal_ErrorImfo = "表达式输入有误";
break;
}
else
op1 = tan(op1);
}
figStack.push(op1);
}
}//end for
if (!figStack.empty() && cal_ErrorImfo == "") {
result = figStack.top();
}
if (std::isinf(result)) {
if (std::signbit(result)) {
cal_ErrorImfo = "超过计算下限";
}
else {
cal_ErrorImfo = "超过计算上限";
}
result = 0;
}
}
//计算方法
void Calculator::calculate(string infix) {
getFormat(infix); //表达式自定义标准格式化
getPostfix(); //后缀表达式转换
calResult(); //计算结果
}
//获取结果
double Calculator::getResult() {
return result;
}
//获取异常信息
string Calculator::getErrorImfo() {
return cal_ErrorImfo;
}
// GraphDlg.cpp: 实现文件
//
#include "pch.h"
#include "MFC_Calculator.h"
#include "afxdialogex.h"
#include "GraphDlg.h"
#include "PLOT.h"
#include "PlotCal.h"
// GraphDlg 对话框
IMPLEMENT_DYNAMIC(GraphDlg, CDialogEx)
GraphDlg::GraphDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_DIALOG3, pParent)
{
}
GraphDlg::~GraphDlg()
{
}
void GraphDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(GraphDlg, CDialogEx)
ON_WM_PAINT()
END_MESSAGE_MAP()
// GraphDlg 消息处理程序
void GraphDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CPaintDC dc(this);
CRect rect;//声明客户区矩形
GetClientRect(&rect);//获得客户区坐标
dc.SetMapMode(MM_ANISOTROPIC);//设置映射模式
dc.SetWindowExt(rect.Width(), rect.Height());//设置窗口
dc.SetViewportExt(rect.Width(), -rect.Height());//x轴水平向右,y轴垂直向上
dc.SetViewportOrg(rect.Width() / 2, rect.Height() / 2);//客户区中心为坐标系原点
rect.OffsetRect(-rect.Width() / 2, -rect.Height() / 2);//将rect移回到客户区内
//画x轴
dc.MoveTo(-rect.Width() / 2, 0);
dc.LineTo(rect.Width() / 2, 0);
//画x轴箭头
dc.MoveTo(rect.Width() / 2, 0);
dc.LineTo(rect.Width() / 2 - 20, 20);
dc. MoveTo(rect.Width() / 2, 0);
dc.LineTo(rect.Width() / 2 - 20, -20);
//画y轴
dc.MoveTo(0, -rect.Height() / 2);
dc.LineTo(0, rect.Height() / 2);
//画y轴箭头
dc.MoveTo(0, rect.Height() / 2);
dc.LineTo(-20, rect.Height() / 2 - 20);
dc.MoveTo(0, rect.Height() / 2);
dc.LineTo(20, rect.Height() / 2 - 20);
double detX = (rect.Width()) / (Xmax - Xmin);//图像修正
bool succ;
CPen pen(PS_SOLID, 5, RGB(0, 0, 0)); // 实线,线宽5,颜色黑色
// 选择画笔
CPen* pOldPen = dc.SelectObject(&pen);
for (int i = 0; i < Step - 1; i++)
{
double startX = (Xmin + i * (Xmax - Xmin) / (Step - 1)) * 10;
double startY = CalcEquation(Equation, succ, 'x', startX);
double endX = (Xmin + (i + 1) * (Xmax - Xmin) / (Step - 1)) * 10;
double endY = CalcEquation(Equation, succ, 'x', endX);
// 将double类型坐标转换为int类型坐标
int startXInt = static_cast<int>(startX * detX);
int startYInt = static_cast<int>(startY * detX);
int endXInt = static_cast<int>(endX * detX);
int endYInt = static_cast<int>(endY * detX);
// 绘制线
dc.MoveTo(startXInt, startYInt);
dc.LineTo(endXInt, endYInt);
// 恢复原始的画笔对象
dc.SelectObject(pOldPen);
}
}
}
五.总结
1.存在问题:不能限制非法输入
计算器的耦合程度很高,例如输入'+'号后必须输入数值而不能是另一个运算符,在电脑自带的计算器程序中,它实现了这个功能,输入一个运算符后非法的输入按钮将会变灰无法按下,我们的程序只能做到检测错误的输入并报错.
2.改进方向:用正则表达式判断
修改方法:我们认为在目前的代码下应该无法修改,因为我们对表达式的处理是在按下’=‘号之后.无法限制用户之前的输入,如果要修改必须及时的检测用户的每一次输入并对各个按钮进行大量的更改,故在目前的能力下无法解决该问题.
最后,感谢小组里每一位同学的参与和付出.