前几天遇到一个面试题,问设计模式相关的东西。
完全没有答出来,所以痛定思痛,觉得好好研究一下什么是设计模式,就百度一一下,发现大话设计模式这本书。
确实写得不错,很生动形象,但是里面用的是c#,我本是不是特别懂c#就先用c++把它实现了。
本文主要围绕如何设计一个计算器为题目,来展开。
开始看到简单计算器的时候,觉得没有什么难度,但是自己仔细想了想,又实验了一下,发现其中还是大有文章。
里面有什么文章呢?这里计算器的操作数就两种类型int 和double
1 操作数的输入大小
你可以用电脑自带的计算器看一下,输入最大是16个有效数字。正数的输入范围是
0.0000000000000001--------9999999999999999
这个在设计输入的时候,要考虑清楚。
2 如果操作符左右两边是同一种类型,int, double。
那么如果操作符是+,-,*,我想当然的认为对于int两个数的结果也肯定是 int。
但是事实并非如此,听我慢慢道来,因为有个情况叫做越界。
我们可以用自带的计算机算一下,越界的时候并没有出问题。
9999999999999999 + 9999999999999999 =2.e+16
这是什么原因呢?很简单,计算器无论是int ,还是double,都先换算成double.
然后通过doulbe和int的类型去比较,如果两个差小于等于1e-15,那么就会按int显示出来。
3 int 和double运算
也是按上面的来做,原因很简单。
例如0.2*5 =1,而不能显示为1.0
还有个1+0.0000000000000001 显示的结果为1,而不是1.0000000000000001,大家可以计算一下试试,这个我开始想还以为是长度不够了,但是仔细看一下,发现是double的精度问题,有效数字0.0000000000000001是1个,而1.0000000000000001有效数字是17个,double自己会舍弃掉。
可见如果真的用计算机做精度极高的运算,要特别小心,重新设计计算符号。
所以,开始的时候我想用模板类来实现四则运算,是不对的。
但是如果做程序员的计算器则是合理的。
我们怎么设计这个呢?
简单工场的模式
将数据和运算分开。
1 先写运算操作类。
class Operation
{
public:
Operation(double op1, double op2, char op);
double getResult();
private:
double op1;
double op2;
char op;
};
Operation::Operation(double op1, double op2, char op)
{
this->op = op;
this->op1 = op1;
this->op2 = op2;
}
double Operation::getResult()
{
double result;
switch(op)
{
case '+':
result = op1 + op2;
break;
case '-':
result = op1 - op2;
break;
case '*':
result = op1 * op2;
break;
case '/':
if (op2< 1e-15 && op2>-1e-15)
{
//异常处理
return 0;
}
result = op1/op2;
break;
}
return result;
}
2 界面的设计
一个很简单的界面。
3 函数的成员变量
CString m_result; //最终的结果
CString m_op1; //第一个操作数
CString m_op2; //第二个操作数
CString m_current;//当前屏幕显示的
char m_operator; // 操作符
int m_opStat;//0 表示当前对第一个数进行输入,1表示当前对第二个数进行输入
bool m_inputStat;// 输入状态,如果出现异常,包括除以0,超过最大的数等,禁止任何操作
bool m_isInt;//输入是否为整数,主要用于区分.
int m_dataLength;//输入长度,不能超过1e17
4 结果重要的函数
输入函数:主要输入条件,小于最大长度,输入状态为true
void Cdesign_1_calculateDlg::refreshText(int input)
{
if(m_inputStat && m_dataLength <= MAX_LENGTH)
{
CString str;
str.Format(_T("%d"),input);
m_current += str;
CEdit* pEdit = (CEdit*) GetDlgItem(IDC_EDIT1);
pEdit->SetWindowText(m_current);
if (m_opStat==0)
{
m_op1 = m_current;
}
else
{
m_op2 = m_current;
}
m_dataLength++;
}
}
点击操作符,需要切换操作数,输入长度和当前显示都要清空。点操作符状态清空
void Cdesign_1_calculateDlg::setOperator(char input)
{
m_operator = input;
m_opStat = 1;
m_current.Empty();
m_dataLength = 0;
m_isInt = true;
}
输入.字符
只需要注意一点,就是只能进入一次。这里命名比较乱,用的都是数字。
void Cdesign_1_calculateDlg::OnBnClickedButton5()
{
// TODO: 在此添加控件通知处理程序代码
if(m_isInt)
{
m_isInt = false;
CString str;
str.Format(_T("%c"),'.');
m_current += str;
CEdit* pEdit = (CEdit*) GetDlgItem(IDC_EDIT1);
pEdit->SetWindowText(m_current);
}
}
最后显示函数,命名不好
void Cdesign_1_calculateDlg::OnBnClickedButton15()
{
// TODO: 在此添加控件通知处理程序代码
if (1 == m_opStat && true == m_inputStat)
{
double input1 = (double)_wtof(m_op1);
double input2 = (double)_wtof(m_op2);
if('/' == m_operator && (input2) < 1e-15 && (input2)> -1e15)
{
CEdit* pEdit = (CEdit*) GetDlgItem(IDC_EDIT1);
pEdit->SetWindowText(_T("除数不能为0"));
m_inputStat = false;
return;
}
Operation operation(input1,input2,m_operator);
double result = operation.getResult();
long long int intResult = static_cast<long long int>(result);
double dif = result - intResult;
if(dif <1e-15 && intResult < 1e16)
m_result.Format(_T("%ld"),intResult);
else
m_result.Format(_T("%g"),result);
CEdit* pEdit = (CEdit*) GetDlgItem(IDC_EDIT1);
pEdit->SetWindowText(m_result);
m_op1 = m_result;
m_current = m_result;
}
}
显示的时候要区分int 型的和double类型的。
5 一些问题