一· 介绍

  • 目的: 做一个标准型的计算器。用于学习Qt基础学习。
  • 平台: Qt 5.12.0

二· 结构框架设计

2.1最终产品样式

界面的设计大体按照win系统自带的计算器做模仿。左边是win7 的界面。本次项目是为了熟悉Qt的界面设计,取消掉一些复杂的计算,和一些比较花样的布局。最终实现右边图样的界面。

2.2 架构规则

  • 弱耦合,强内聚,业务逻辑与界面逻辑一定要分离
  • 模块之间使用接口进行关联
  • 模块之间的关系必然是 单项依赖 ,最大力度避免循环依赖关系

2.3 框架结构

​ 计算器一定包含界面类和 后台计算类。按照上述规则,设计如下:

  • 一个界面类 IOCalculator;

  • 一个后台计算的类 QCalculatorDec;

  • 一个接口类,用来连接 界面类和后台业务计算类 QcalculatorUI;

  • 一个 完整的计算机类 Qcalculator;(实现上述三个类的融合,对外就是一个计算器类)

三· 接口类的设计

接口类主要是实现界面类与业务逻辑的通信。最主要的有两个内容

  1. 从界面类中得到 用户输入的计算公式。
  2. 由计算公式得到计算结果。

接口类只提供接口,UI 类使用该接口。业务逻辑类 负责具体的实现。

类的结构为:

class IOCalculator
{
public:
virtual bool expression(const QString& exp )=0;
virtual QString result()=0;
virtual ~IOCalculator() ;
};

四· 界面类的设计

4.1 界面布局设计

直接离别别人设计好的

从图中可以看到 界面类用到的元素

  • 一个窗口界面 (QWidget)
  • 一个显示栏(QLineEdit)
  • 20个按键(QPushButton)

4.2 代码设计

4.21 类的设计

UI类除了要实现界面,还用到接口。类的结构如下:

class QcalculatorUI : public QWidget
{
Q_OBJECT private:
QPushButton * m_buttons[20];
QLineEdit* m_ledit;
IOCalculator* m_cal; QcalculatorUI();
bool construct(); private slots:
void getEquationFoUser(); public:
static QcalculatorUI* NewInstance();
void show();
void importInterface(IOCalculator* cal );
~QcalculatorUI();
};

为了防止半成品的对象的生成,该类使用二阶构造方式

4.22 具体实现

QcalculatorUI::QcalculatorUI() :QWidget(nullptr, Qt :: WindowMinimizeButtonHint | Qt:: WindowCloseButtonHint )
{
m_cal =nullptr;
} void QcalculatorUI::getEquationFoUser()
{
QPushButton* binsig= dynamic_cast<QPushButton *>( sender()); if(binsig != nullptr)
{
QString gets=binsig->text();
if( "C"==gets )
{
m_ledit->setText("");
}
else if("<-"==gets )
{
QString ls = m_ledit->text();
if(ls.length()>0)
{
ls.remove(ls.length()-1,1);
}
m_ledit->setText(ls);
}
else if("="==gets)
{
if(m_cal != nullptr)
{
m_cal->expression(m_ledit->text());
m_ledit->setText(m_cal->result());
}
}
else
{
m_ledit->setText( m_ledit->text()+gets );
} }
} bool QcalculatorUI:: construct()
{
bool ret =true;
const char* btnText[20] =
{
"7", "8", "9", "+", "(",
"4", "5", "6", "-", ")",
"1", "2", "3", "*", "<-",
"0", ".", "=", "/", "C",
}; m_ledit = new QLineEdit( this);
if(m_ledit != nullptr)
{
m_ledit->move(10,10 );
m_ledit-> resize(240,30);
m_ledit->setReadOnly(true);
m_ledit->setAlignment(Qt::AlignRight); }
else
{
ret =false;
} for(int i=0;i< 5;i++)
for(int j =0;j<4;j++)
{
m_buttons[j*5 +i] = new QPushButton(this);
if(m_buttons[j*5+i] != nullptr)
{
m_buttons[j*5 +i]->setText (btnText[ j*5+i]);
m_buttons[j*5 +i]->move(10+i*50,50+j*50);
m_buttons[j*5+i]->resize(40,40);
connect(m_buttons[j*5+i] ,SIGNAL(clicked()),this ,SLOT(getEquationFoUser()));
}
else
{
ret = false;
continue;
}
} return ret;
} QcalculatorUI* QcalculatorUI:: NewInstance()
{
QcalculatorUI* ret = new QcalculatorUI() ;
if(ret == nullptr || ! ret->construct() )
{
delete ret;
ret =nullptr;
}
return ret;
} void QcalculatorUI :: show()
{
QWidget ::show();
setFixedSize(this->width(),this->height()); } void QcalculatorUI ::importInterface(IOCalculator* cal )
{
m_cal=cal;
}

4.23 主要代码解释

1 二结构造类

​ 为了防止半成品的对象生成,所以使用二阶构造模式。详细请参考。

2 接口函数的使用

​ m_cal->expression(m_ledit->text()); 和 m_ledit->setText(m_cal->result());使用 接口对象,实现界面类和后台运算对象的交互。

五· 后台计算类

后台类主要实现计算器的计算,实现接口。

5.1 计算器算法

​ 计算器的计算方式和我们人类的计算方式大不相同。计算机只会按照一条条指令来执行,人的那种整体性的去认知和分析在计算机世界里是很难完成。所以要按照计算机的思路设计算法。

5.11 元素分离

​ 该类从UI界面类得到的表达式,对于计算器(准确的说是 C++编译器)他只是一个字符串。想把该字符解释成(当做)一个表达式。则该表达式中的元素有 四则远算,数字,和正负号,还有括号符,每个元素根据前后的元素不同,就有不同的意义。例如 一个“+”元素,如果该元素前边是一个数字,后边是个数字,那该元素是加号,如果前边元素是个符号,后边是个数字元素,则是个正号。所以简单的把字符串拆成每个字符是毫无意义。需要根据字符的前后特性,识别出每个表达式的元素,才能有后边的计算的可能。所以先实现 元素分离。

​ 随便找个表达式:“1/2*(-2-1.123)+1”来分析。不看看出,如果以 符号为标志对每个字符分析,就能实现分离。

  • 数字元素识别:

    1. 由于数字元素是多个字符组成。则要用字符串来存储。
    2. 当前字符是数字字符或者小数点字符,则追加到到数字元素后边。
  • 符号元素的识别:

    1. “*”,“/”,“(”, “)” 这些元素很好识别。直接可以识别出来。

    2. 如果遇到 “+”和“-”,如果① 该元素前是个符号且后边为数字。②该元素位于当前表达式的首位。则该元素为 后边的数字元素的正负号,存到下一个数字字符串中即可。

    伪码为:

    // 用个对列分离出来的元素存起
    
    exp= 表达式字符串
    num 一个字符串的累加器 for(int i=0;i<exp.length();<i++)
    {
    if(exp[i] == 数字 || exp[i] == 小数点 )
    num +=exp[i]; //追加
    else
    {
    //到这里,前边的数字元素已经结束
    保存num;
    if( exp[i] == “*”,“/”,“(”, “)” )
    保存该元素;
    else if( exp[i] == “+” “-” )
    {
    if( ( i>0 && exp[i-1] == 字符 ) || (i==0))
    {
    存入数字字符串;
    }
    else
    {
    error;
    }
    }
    } else
    {
    error;
    } }

5.22 中缀表达式转后缀表达式

​ 已经有了可以定性(知道该元素在表达式里代表什么意义)的元素,接下来就可以根据元素来做计算了。分析当前处境所遇到的问题是:计算机只能顺序的执行指令,面对改变顺序执行的 优先级的运算规则无法处理。则需要

  • 四则运算符需要得到该运算符的优先级

  • 用后缀表达式代替中缀表达式来解决 括号带来的运算顺序的改变问题。

    1. 当前元素为 左括号 : 入栈

    2. 当前元素为 右括号 :弹出栈的元素到出现 左括号并把左括号也弹出

    3. 当前元素为数字:输出

    4. 当前元素为运算符:

      ​ 1) 与栈顶元素比较优先级

      ​ 2) 大于栈顶元素,入栈

      ​ 3) 小于等于则把栈顶元素弹出,一直到大于栈顶元素,入栈

    伪码为:

    // 中缀转后缀,把转好的元素按规则存到一个队列里
    //exp是个队列,存放已经做了分离的元素
    while(exp != 空)
    {
    QString s=exp.dequeue();
    if(s==数字 )
    {
    放入 out队列;
    }
    else if(s==运算符)
    {
    while(getPriority( s) <= getPriority( stack.top() ) )
    stack.pop() 放入到out队列;
    }
    else if(s== "(")
    入栈
    else if(s == ")")
    {
    while( "(" != stack.top() && ( stack.length()>0 ) )
    {
    stack.pop() 放入到out队列;
    }
    if(stack.length()>0)
    {
    //"("已经不需要了。丢到
    stack.pop();
    }
    else
    {
    // 说明栈里没有 "(",表达式有误
    error;
    } }
    else
    {
    error;
    }
    }

5.23 后缀表达式的计算

​ 经过前边两步的处理,用后缀表达式计算 该表达已经万事俱备了。后缀表达式的计算规则为:

  • 当前元素为 数字:入栈
  • 当前元素为 运算符:
    1. 从栈中弹出 右操作数
    2. 从栈中弹出 左操作数
    3. 根据运算符,进行计算
    4. 把计算结果压栈

5.2 后台计算类设计

​ 前边的5.1 章节做了计算器的算法处理,这只是该类的内部功能的实现。根据项目的整体结构设计。该类要是对接口的函数做具体的实现。则代码如下:(私有函数是在具体代码实现是才能确定,主要脉络是对外的函数)

class QCalculatorDec :public IOCalculator
{
private:
QString m_exp;
QString m_result; bool isNumberOrDec(QChar c );
bool isSymbol(QChar c);
bool isOperator(QString c);
bool isSign(QChar c);
bool isNumber(QString s);
int getPriority( QString& s);
QString getResult(const QString& var1,const QString& ver2,const QString& oper ); QQueue<QString> split(const QString& exp);
bool transverter(QQueue<QString>& exp , QQueue<QString>& output );
QString suffixCalculate(QQueue<QString>& exp); public:
QCalculatorDec();
bool expression(const QString& exp );
QString result();
~QCalculatorDec();
};

5.3 具体实现

QCalculatorDec::QCalculatorDec()
{ } bool QCalculatorDec:: isNumberOrDec(QChar c )
{
return ((c>='0'&& c<='9') || c=='.');
} bool QCalculatorDec:: isSymbol(QChar c)
{
return(isOperator(c) ||c=='(' || c==')');
}
bool QCalculatorDec ::isOperator(QString c)
{
return(c =="+" || c =="-" || c =="*" || c =="/");
} bool QCalculatorDec ::isSign(QChar c)
{
return(c =='+' || c =='-');
} int QCalculatorDec:: getPriority( QString& s)
{
int ret=0;
if(s=="+" || s=="-")
{
ret =1;
}
else if(s=="*" || s=="/")
{
ret =2;
} return ret;
} bool QCalculatorDec:: isNumber(QString s)
{
bool ret =false; s.toDouble( &ret); return ret;
} /* 元素 分离 */
QQueue<QString> QCalculatorDec::split(const QString& exp)
{
QQueue<QString> ret; QString num;
QString pre; for(int i=0; i<exp.length();i++)
{
if( isNumberOrDec(exp[i]))
{
num.append(exp[i]);
}
else if(isSymbol(exp[i]))
{
if(!num.isEmpty())
{
ret.enqueue(num);
num.clear();
}
if(isSign(exp[i]) &&( pre=="" || pre=="(" || isOperator( pre) ) )
{
num.append(exp[i]);
}
else //肯定是运算符
{
ret.enqueue(exp[i]);
} }
pre = exp[i];
} if(!num.isEmpty())
{
ret.enqueue(num);
num.clear();
} return ret;
} /*中缀变后缀*/
bool QCalculatorDec::transverter(QQueue<QString>& exp , QQueue<QString>& output )
{ bool ret= true;
QStack<QString> stack;
stack.clear(); // while(!exp.isEmpty())
for(int i=0;i<exp.length();i++)
{
if( isNumber(exp[i]))
{
output.enqueue(exp[i]);
}
else if(isOperator(exp[i]) )
{
while(!stack.isEmpty() && ( getPriority(exp[i])<= getPriority(stack.top() ) ) )
{
output.enqueue(stack.pop());
} stack.push(exp[i]);
}
else if(exp[i]== "(")
{
stack.push(exp[i]);
}
else if(exp[i]== ")")
{
while(!stack.isEmpty() && ( "(" != stack.top() ) )
{
output.enqueue(stack.pop());
}
if(!stack.isEmpty() &&( "(" == stack.top()) )
{
stack.pop();
}
else // if ( stack.isEmpty() || ("(" != stack.top() ))
{
ret=false;
} }
else
{
ret=false;
} } while(!stack.isEmpty())
{
if("(" != stack.top())
{
output.enqueue(stack.pop());
}
else
{
ret=false;
break;
}
} if(! ret)
{
output.clear();
} return ret;
} /*单纯的计算*/
QString QCalculatorDec::getResult( const QString& var1,const QString& var2,const QString& oper )
{
QString ret; if(isNumber(var1) && isNumber(var2) )
{ double lp = var1.toDouble();
double rp = var2.toDouble(); if("+" == oper)
{
ret.sprintf("%f", lp + rp);
}
else if("-" == oper)
{
ret.sprintf("%f", lp - rp);
}
else if("*" == oper)
{
ret.sprintf("%f", lp * rp);
}
else if("/" == oper)
{
if(rp<0.000000000001 && rp> -0.000000000001 )
{
ret="error";
}
else
{
ret.sprintf("%f", lp / rp);
}
}
else
{
ret="error";
}
}
else
{
ret="error";
} return ret;
}
/*后缀表达式的计算*/
QString QCalculatorDec::suffixCalculate(QQueue<QString>& exp)
{
QString ret;
QStack<QString> stack;
stack.clear(); for(int i= 0; i<exp.length();i++)
{
if(isNumber(exp[i]))
{
stack.push(exp[i]);
}
else if(isOperator(exp[i] ) )
{
if(!stack.isEmpty())
{
QString leftVar,rightVar; rightVar=stack.pop();
if(!stack.isEmpty())
{
leftVar=stack.pop();
ret = getResult(leftVar,rightVar,exp[i]);
if("error"!= ret)
{
stack.push(ret);
}
}
else
{
ret="error";
}
}
else
{
ret="error";
}
}
else
{
ret="error";
}
} if(1== stack.length())
{
ret =stack.pop();
}
else
{
ret ="error";
} return ret;
} /*计算结果*/
bool QCalculatorDec:: expression(const QString& exp )
{
bool ret=false;
m_exp= exp;
qDebug()<<"expression() is start " ;
QQueue<QString> tranin=split(m_exp) ;
QQueue<QString> tranOut; if( transverter( tranin, tranOut ) )
{
m_result = suffixCalculate(tranOut);
ret = (m_result != "Error");
}
else
{
m_result= "error";
} return ret;
} QString QCalculatorDec :: result()
{
return m_result;
}

5..4 主要代码解释

1 浮点数与0的比较

​ 在C/C++ 语言中,遇到 浮点数与0 的比较时,由于存储特性。不能直接做相等比较,要与 很小的数字区间比较。

六· 计算类的实现

​ 为了架构的整洁和使用的方便,把计算机的各个部分用一个类做个整体的封装。对外只有对象的生产和显示接口。

6.1 计算类的设计

class Qcalculator
{
private:
QcalculatorUI* m_ui;
QCalculatorDec m_dec; Qcalculator();
bool construct();
public:
static Qcalculator * NewInstance();
void show();
};

6.2 具体实现

Qcalculator::Qcalculator()
{ } bool Qcalculator::construct()
{
m_ui= QcalculatorUI::NewInstance();
if(m_ui != nullptr)
{
m_ui->importInterface(&m_dec);
}
return (m_ui != nullptr);
} Qcalculator * Qcalculator:: NewInstance()
{
Qcalculator* ret = new Qcalculator;
if(ret ==nullptr || ! ret->construct())
{
delete ret;
ret = nullptr;
} return ret;
} void Qcalculator :: show()
{
m_ui->show();
}

6.3 主要代码解释

1各个模块实现组合

七· 效果展示

最新文章

  1. Spring 4 集成Apache CXF开发JAX-RS Web Service
  2. [JSP]解决Maven创建项目失败
  3. Java-计划存储
  4. sruts2 自定义类型转换器
  5. Web API开发实例——对产品Product进行增删改查
  6. js保留n位小数
  7. node搜索codeforces 3A - Shortest path of the king
  8. PowerDesigner 如何生成数据库更新脚本
  9. NSUserDefaults(数据存储)
  10. windows下配置Java环境变量
  11. [机器学习Lesson4]多元线性回归
  12. [SCOI2015]国旗计划
  13. 如何使用微信小程序云函数发送短信验证码
  14. HashMap循环过程中删除元素发生ConcurrentModificationException的源码分析
  15. abap 常用TCODE
  16. Kubernetes集群搭建之Master配置篇
  17. Cannot find ./catalina.sh The file is absent or does not have execute permission This file is nee
  18. Java的赋值、浅克隆和深度克隆的区别
  19. Linux运维之每日小技巧-检测网站状态以及PV、UV等介绍
  20. 【原创】Dependency Walker

热门文章

  1. LeetCode 531. Lonely Pixel I
  2. 【BZOJ1095】【ZJOI2007】捉迷藏
  3. Numpy | 16 算术函数
  4. Linux 和 windows下查看运行命令的位置
  5. 反素数 Antiprime(信息学奥赛一本通 1625)(洛谷 1463)
  6. 深入ES6中的class类
  7. js操作表格、table、
  8. 刷题记录:[De1CTF 2019]Giftbox &amp;&amp; Comment
  9. .htaccess tricks总结
  10. idea2017打war包