前几天遇到一个面试题,问设计模式相关的东西。
完全没有答出来,所以痛定思痛,觉得好好研究一下什么是设计模式,就百度一一下,发现大话设计模式这本书。
确实写得不错,很生动形象,但是里面用的是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自己会舍弃掉。
可见如果真的用计算机做精度极高的运算,要特别小心,重新设计计算符号。
所以,开始的时候我想用模板类来实现四则运算,是不对的。
但是如果做程序员的计算器则是合理的。
我们怎么设计这个呢?
将数据和运算分开。
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; }
一个很简单的界面。
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
输入函数:主要输入条件,小于最大长度,输入状态为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; } }