QT开发(三十)——计算器实例开发
一、计算器界面制作
计算器界面需要QWidget组件作为顶层窗口,QLineEdit组件作为输入框,QPsuhButton作为按钮。
界面规划设计如下:
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *widget = new QWidget(NULL, Qt::WindowCloseButtonHint);
//构建输入框,设置属性
QLineEdit *edit = new QLineEdit(widget);
edit->move(10, 10);
edit->resize(240, 30);
edit->setReadOnly(true);
//构造按钮,设置属性
QPushButton *button[20] = {0};
const char *buttontext[20] =
{
"7", "8", "9", "+", "(",
"4", "5", "6", "-", ")",
"1", "2", "3", "*", "<-",
"0", ".", "=", "/", "C"
};
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 5; j++)
{
button[5*i + j] = new QPushButton(widget);
button[5*i + j]->move(10 + (10 + 40)*j, 50 + (10 + 40)*i);
button[5*i + j]->resize(40, 40);
button[5*i + j]->setText(buttontext[5*i + j]);
}
}
//设置窗口
int ret = 0;
widget->show();
widget->setFixedSize(widget->width(), widget->height());
ret = a.exec();
delete widget;
return ret;
}
二、项目代码重构
重构是以改善代码质量为目的的代码重写,使得软件的设计和架构更加合理,提高了软件的扩展性和维护性。
代码重构与代码实现的区别:
A、代码实现是按照设计编程实现,核心在于功能实现,不考虑架构的优劣
B、代码重构是以提高代码质量为目的的软件架构优化,核心在于优化架构,不考虑对已实现功能的修改。
代码重构在软件开发过程中的阶段:
代码重构的适用场合:
A、项目中重复代码越来越多
B、项目中代码功能越来越不清晰
C、项目中代码实现离设计越来越远
计算器界面代码重构:
由于需要申请堆空间资源,使用二阶构造模式。
CalculatorUI.h文件:
#ifndef CALCULATORUI_H
#define CALCULATORUI_H
#include
#include
#include
class CalculatorUI : public QWidget
{
Q_OBJECT
public:
static CalculatorUI* newInstance();
~CalculatorUI();
void show();
private:
CalculatorUI();
bool Construct();
private:
QLineEdit *m_edit;
QPushButton *m_buttons[20];
};
#endif // CALCULATORUI_H
CalculatorUI.cpp文件:
#include "CalculatorUI.h"
CalculatorUI::CalculatorUI() : QWidget(NULL, Qt::WindowCloseButtonHint)
{
}
CalculatorUI* CalculatorUI::newInstance()
{
CalculatorUI *ret = new CalculatorUI();
if(NULL == ret || !ret->Construct())
{
delete ret;
ret = NULL;
}
return ret;
}
CalculatorUI::~CalculatorUI()
{
}
void CalculatorUI::show()
{
QWidget::show();
setFixedSize(width(), height());
}
bool CalculatorUI::Construct()
{
bool ret = true;
m_edit = new QLineEdit(this);
if(m_edit != NULL)
{
m_edit->move(10, 10);
m_edit->resize(240, 30);
m_edit->setReadOnly(true);
}
else
{
ret = false;
}
const char *buttontext[20] =
{
"7", "8", "9", "+", "(",
"4", "5", "6", "-", ")",
"1", "2", "3", "*", "<-",
"0", ".", "=", "/", "C"
};
for(int i = 0; (i < 4) && ret; i++)
{
for(int j = 0; (j < 5) && ret; j++)
{
m_buttons[5*i + j] = new QPushButton(this);
if(m_buttons[5*i + j] != NULL)
{
m_buttons[5*i + j]->move(10 + (10 + 40)*j, 50 + (10 + 40)*i);
m_buttons[5*i + j]->resize(40, 40);
m_buttons[5*i + j]->setText(buttontext[5*i + j]);
}
else
{
ret = false;
}
}
}
return ret;
}
Main.cpp文件:
#include
#include
#include
#include
#include "CalculatorUI.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CalculatorUI *widget = CalculatorUI::newInstance();
int ret = 0;
if(widget != NULL)
{
widget->show();
ret = a.exec();
delete widget;
}
return ret;
}
三、计算器算法引擎
人类思维和阅读习惯的运算表达式为中缀表达式,但计算机的运算表达式为后缀表达式,因此需要将中缀表达式转换为后缀表达式。
算法引擎的解决方案如下:
A、将中缀表达式的数字和运算符分离
B、将中缀表达式转换为后缀表达式
C、使用后缀表达式计算运算表达式的结果
1、计算表达式的分离
中缀表达式通常包含如下元素:
A、数字与小数点[0-9 .]
B、符号位[+ -]
C、运算符[+ - * /]
D、括号[( )]
分离算法设计分析:
以符号作为标识对表达式中的字符进行逐个访问
A、定义累计变量
B、当前字符为数字或小数点
累计,num += exp[i]
C、当前字符exp[i]为为符号
num为运算数,分离并保存
如果exp[i]为正负号
累计符号位+、-:num += exp[i]
如果exp[i]为运算符
分离并保存
分离算法源码:
QQueue CalculatorDec::split(const QString& exp)
{
QQueue ret;
QString pre = "";
QString num = "";
for(int i=0; i
2、表达式的转换
中缀表达式转换为后缀表达式的注意事项:
A、运算表达式中的括号必须匹配
B、必须根据运算优先级进行转换
C、转换后的后缀表达式中不能有括号
D、转换后的后缀表达式可以计算出正确结果
在运算表达式中,括号匹配成对出现,左括号必须限于右括号出现。
匹配算法如下:
bool CalculatorDec::match(QQueue& exp)
{
bool ret = true;
int len = exp.length();
QStack stack;
for(int i=0; i
转换过程:
A、当前元素e为数字,直接输出
B、当前元素e为运算符:
与栈顶运算符进行优先级比较,如果小于等于,将栈顶元素输出,转1;如果大于,将当前元素入栈。
C、当前元素e为左括号:入栈
D、当前元素e为右括号:
弹出栈顶元素并输出,直至栈顶元素为左括号,将栈顶的左括号从栈中弹出
转换算法如下:
bool CalculatorDec::transform(QQueue& exp, QQueue& output)
{
bool ret = match(exp);
QStack stack;
output.clear();
while( ret && !exp.isEmpty() )
{
QString e = exp.dequeue();
if( isNumber(e) )
{
output.enqueue(e);
}
else if( isOperator(e) )
{
while( !stack.isEmpty() && (priority(e) <= priority(stack.top())) )
{
output.enqueue(stack.pop());
}
stack.push(e);
}
else if( isLeft(e) )
{
stack.push(e);
}
else if( isRight(e) )
{
while( !stack.isEmpty() && !isLeft(stack.top()) )
{
output.enqueue(stack.pop());
}
if( !stack.isEmpty() )
{
stack.pop();
}
}
else
{
ret = false;
}
}
while( !stack.isEmpty() )
{
output.enqueue(stack.pop());
}
if( !ret )
{
output.clear();
}
return ret;
}
3、结果计算
使用后缀表达式计算结果需要遍历后缀表达式中的数字和运算符。
A、当前元素为数字:进栈
B、当前元素为运算符:
从栈中弹出右操作数
从栈中弹出左操作数
根据符号进行运算
将运算结果压入栈中
遍历结束后,栈中的数字即为结果
QString CalculatorDec::calculate(QString l, QString op, QString r)
{
QString ret = "Error";
if( isNumber(l) && isNumber(r) )
{
double lp = l.toDouble();
double rp = r.toDouble();
if( op == "+" )
{
ret.sprintf("%f", lp + rp);
}
else if( op == "-" )
{
ret.sprintf("%f", lp - rp);
}
else if( op == "*" )
{
ret.sprintf("%f", lp * rp);
}
else if( op == "/" )
{
const double P = 0.000000000000001;
if( (-P < rp) && (rp < P) )
{
ret = "Error";
}
else
{
ret.sprintf("%f", lp / rp);
}
}
else
{
ret = "Error";
}
}
return ret;
}
QString CalculatorDec::calculate(QQueue& exp)
{
QString ret = "Error";
QStack stack;
while( !exp.isEmpty() )
{
QString e = exp.dequeue();
if( isNumber(e) )
{
stack.push(e);
}
else if( isOperator(e) )
{
QString rp = !stack.isEmpty() ? stack.pop() : "";
QString lp = !stack.isEmpty() ? stack.pop() : "";
QString result = calculate(lp, e, rp);
if( result != "Error" )
{
stack.push(result);
}
else
{
break;
}
}
else
{
break;
}
}
if( exp.isEmpty() && (stack.size() == 1) && isNumber(stack.top()) )
{
ret = stack.pop();
}
return ret;
}
四、用户界面与业务逻辑的分离
1、计算器架构设计
软件架构一般包括:
A、用户界面模块,接收用户输入,呈现数据
B、业务逻辑模块,根据用户需求处理数据
软件的各个模块之间必须遵循高内聚、低耦合的原则,每个模块应该只实现单一的功能,模块内部的子模块只作为整体的单一功能而存在,模块间通过约定好的接口进行交互。
模块间仅通过接口进行关联,因此必然存在使用接口的模块和实现接口的模块,模块间的关系必须是单向依赖的。
计算器应用程序的架构如下:
QCalculatorUI类实现了程序界面,QCalculatorDec类实现了计算器的核心算法。QCalculatorUI类通过引入ICalculator指针类型成员变量,增加了ICalculator属性;QCalculatorDec类继承自ICalculator,实现了ICalculator接口的计算功能。QCalculatorUI类与QCalculatorDec类间没有关系。
2、ICalculator类
#ifndef ICALCULATOR_H
#define ICALCULATOR_H
#include
class ICalculator
{
public:
virtual bool expression(const QString & exp) = 0;
virtual QString result() = 0;
};
#endif // ICALCULATOR_H
3、QCalculatorDec类
QCalculatorDec类继承自ICalculator,实现ICalculator类的计算功能
QCalculatorDec.h文件:
#ifndef CALCULATORDEC_H
#define CALCULATORDEC_H
#include
#include
#include
#include "ICalculator.h"
class CalculatorDec : public ICalculator
{
public:
CalculatorDec();
~CalculatorDec();
bool expression(const QString & exp);
QString expression();
QString result();
private:
bool isDigitOrDot(QChar c);
bool isSymbol(QChar c);
bool isSign(QChar c);
bool isNumber(QString s);
bool isOperator(QString s);
bool isLeft(QString s);
bool isRight(QString s);
int priority(QString s);
QQueue split(const QString& exp);
bool match(QQueue& exp);
bool transform(QQueue& exp, QQueue& output);
QString calculate(QQueue& exp);
QString calculate(QString l, QString op, QString r);
private:
QString m_exp;
QString m_result;
};
#endif // CALCULATORDEC_H
QCalculatorDec.cpp文件:
#include "CalculatorDec.h"
CalculatorDec::CalculatorDec()
{
m_exp = "";
m_result = "";
}
CalculatorDec::~CalculatorDec()
{
}
bool CalculatorDec::expression(const QString & exp)
{
bool ret = false;
QQueue spExp = split(exp);
QQueue postExp;
m_exp = exp;
if( transform(spExp, postExp) )
{
m_result = calculate(postExp);
ret = (m_result != "Error");
}
else
{
m_result = "Error";
}
return ret;
}
QString CalculatorDec::expression()
{
QString ret;
return ret;
}
QString CalculatorDec::result()
{
return m_result;
}
bool CalculatorDec::isDigitOrDot(QChar c)
{
return ('0' <= c) && (c <= '9') || (c == '.');
}
bool CalculatorDec::isSymbol(QChar c)
{
return isOperator(c) || ('(' == c) || (')' == c);
}
bool CalculatorDec::isSign(QChar c)
{
return ('+' == c) || ('-' == c);
}
bool CalculatorDec::isNumber(QString s)
{
bool ret = false;
s.toDouble(&ret);
return ret;
}
bool CalculatorDec::isOperator(QString s)
{
return ("+" == s) || ("-" == s) || ("*" == s) || ("/" == s);
}
bool CalculatorDec::isLeft(QString s)
{
return ("(" == s);
}
bool CalculatorDec::isRight(QString s)
{
return (")" == s);
}
int CalculatorDec::priority(QString s)
{
int ret = -1;
if(s == "+" || s == "-")
{
ret = 1;
}
if(s == "*" || s == "/")
{
ret = 2;
}
return ret;
}
QQueue CalculatorDec::split(const QString& exp)
{
QQueue ret;
QString pre = "";
QString num = "";
for(int i=0; i& exp)
{
bool ret = true;
int len = exp.length();
QStack stack;
for(int i=0; i& exp, QQueue& output)
{
bool ret = match(exp);
QStack stack;
output.clear();
while( ret && !exp.isEmpty() )
{
QString e = exp.dequeue();
if( isNumber(e) )
{
output.enqueue(e);
}
else if( isOperator(e) )
{
while( !stack.isEmpty() && (priority(e) <= priority(stack.top())) )
{
output.enqueue(stack.pop());
}
stack.push(e);
}
else if( isLeft(e) )
{
stack.push(e);
}
else if( isRight(e) )
{
while( !stack.isEmpty() && !isLeft(stack.top()) )
{
output.enqueue(stack.pop());
}
if( !stack.isEmpty() )
{
stack.pop();
}
}
else
{
ret = false;
}
}
while( !stack.isEmpty() )
{
output.enqueue(stack.pop());
}
if( !ret )
{
output.clear();
}
return ret;
}
QString CalculatorDec::calculate(QString l, QString op, QString r)
{
QString ret = "Error";
if( isNumber(l) && isNumber(r) )
{
double lp = l.toDouble();
double rp = r.toDouble();
if( op == "+" )
{
ret.sprintf("%f", lp + rp);
}
else if( op == "-" )
{
ret.sprintf("%f", lp - rp);
}
else if( op == "*" )
{
ret.sprintf("%f", lp * rp);
}
else if( op == "/" )
{
const double P = 0.000000000000001;
if( (-P < rp) && (rp < P) )
{
ret = "Error";
}
else
{
ret.sprintf("%f", lp / rp);
}
}
else
{
ret = "Error";
}
}
return ret;
}
QString CalculatorDec::calculate(QQueue& exp)
{
QString ret = "Error";
QStack stack;
while( !exp.isEmpty() )
{
QString e = exp.dequeue();
if( isNumber(e) )
{
stack.push(e);
}
else if( isOperator(e) )
{
QString rp = !stack.isEmpty() ? stack.pop() : "";
QString lp = !stack.isEmpty() ? stack.pop() : "";
QString result = calculate(lp, e, rp);
if( result != "Error" )
{
stack.push(result);
}
else
{
break;
}
}
else
{
break;
}
}
if( exp.isEmpty() && (stack.size() == 1) && isNumber(stack.top()) )
{
ret = stack.pop();
}
return ret;
}
4、CalculatorUI类
CalculatorUI类使用ICalculator接口的计算功能。通过在CalculatorUI类中引入ICalculator指针成员变量,增加了ICalculator属性。将所有按钮发送的信号与onCalculate()槽函数连接。
CalculatorUI.h文件:
#ifndef CALCULATORUI_H
#define CALCULATORUI_H
#include
#include
#include
#include "icalculator.h"
class CalculatorUI : public QWidget
{
Q_OBJECT
public:
static CalculatorUI* newInstance();
~CalculatorUI();
void show();
void setCalculator(ICalculator* cal);
ICalculator* getCalculator();
private:
CalculatorUI();
bool Construct();
private slots:
void onCalculate();
private:
QLineEdit *m_edit;
QPushButton *m_buttons[20];
ICalculator *m_cal;
};
#endif // CALCULATORUI_H
CalculatorUI.cpp文件:
#include "CalculatorUI.h"
CalculatorUI::CalculatorUI() : QWidget(NULL, Qt::WindowCloseButtonHint)
{
m_cal = NULL;
}
CalculatorUI* CalculatorUI::newInstance()
{
CalculatorUI *ret = new CalculatorUI();
if(NULL == ret || !ret->Construct())
{
delete ret;
ret = NULL;
}
return ret;
}
CalculatorUI::~CalculatorUI()
{
}
void CalculatorUI::show()
{
QWidget::show();
setFixedSize(width(), height());
}
bool CalculatorUI::Construct()
{
bool ret = true;
m_edit = new QLineEdit(this);
if(m_edit != NULL)
{
m_edit->move(10, 10);
m_edit->resize(240, 30);
m_edit->setReadOnly(true);
}
else
{
ret = false;
}
const char *buttontext[20] =
{
"7", "8", "9", "+", "(",
"4", "5", "6", "-", ")",
"1", "2", "3", "*", "<-",
"0", ".", "=", "/", "C"
};
for(int i = 0; (i < 4) && ret; i++)
{
for(int j = 0; (j < 5) && ret; j++)
{
m_buttons[5*i + j] = new QPushButton(this);
if(m_buttons[5*i + j] != NULL)
{
m_buttons[5*i + j]->move(10 + (10 + 40)*j, 50 + (10 + 40)*i);
m_buttons[5*i + j]->resize(40, 40);
m_buttons[5*i + j]->setText(buttontext[5*i + j]);
connect(m_buttons[5*i + j], SIGNAL(clicked()), this, SLOT(onCalculate()));
}
else
{
ret = false;
}
}
}
return ret;
}
void CalculatorUI::setCalculator(ICalculator* cal)
{
m_cal = cal;
}
ICalculator* CalculatorUI::getCalculator()
{
return m_cal;
}
void CalculatorUI::onCalculate()
{
QPushButton* button = dynamic_cast(sender());
if( button != NULL )
{
QString buttontext = button->text();
if( buttontext == "<-" )
{
QString text = m_edit->text();
if( text.length() > 0 )
{
text.remove(text.length()-1, 1);
m_edit->setText(text);
}
}
else if( buttontext == "C" )
{
m_edit->setText("");
}
else if( buttontext == "=" )
{
if( m_cal != NULL )
{
m_cal->expression(m_edit->text());
m_edit->setText(m_cal->result());
}
}
else
{
m_edit->setText(m_edit->text() + buttontext);
}
}
}
5、Calculator类
计算器Calculator类包含计算器程序界面CalculatorUI和核心算法CalculatorDec两部分。在Calculator构造过程中需要指定CalculatorUI对应的核心算法CalculatorDec。
Calculator.h文件:
#ifndef CALCULATOR_H
#define CALCULATOR_H
#include "CalculatorUI.h"
#include "CalculatorDec.h"
class Calculator
{
private:
CalculatorUI *m_ui;
CalculatorDec m_cal;
Calculator();
bool construct();
public:
static Calculator* newInstance();
void show();
~Calculator();
};
#endif // CALCULATOR_H
Calculator.cpp文件:
#include "Calculator.h"
Calculator::Calculator()
{
}
bool Calculator::construct()
{
m_ui = CalculatorUI::newInstance();
if(m_ui != NULL)
{
m_ui->setCalculator(&m_cal);
}
return (m_ui != NULL);
}
Calculator* Calculator::newInstance()
{
Calculator *ret = new Calculator();
if((ret == NULL) || (!ret->construct()))
{
delete ret;
ret = NULL;
}
return ret;
}
void Calculator::show()
{
m_ui->show();
}
Calculator::~Calculator()
{
delete m_ui;
}
6、使用实例
Main.cpp文件:
#include
#include "Calculator.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Calculator *cal = Calculator::newInstance();
int ret = 0;
if(cal != NULL)
{
cal->show();
ret = a.exec();
delete cal;
}
return ret;
}