设计模式 一 简单工场模式

前几天遇到一个面试题,问设计模式相关的东西。

完全没有答出来,所以痛定思痛,觉得好好研究一下什么是设计模式,就百度一一下,发现大话设计模式这本书。

确实写得不错,很生动形象,但是里面用的是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自己会舍弃掉。

    可见如果真的用计算机做精度极高的运算,要特别小心,重新设计计算符号。


所以,开始的时候我想用模板类来实现四则运算,是不对的。

但是如果做程序员的计算器则是合理的。

我们怎么设计这个呢?

简单工场的模式

大家可以去github上面下载代码,如果有人嫌麻烦说一下,比较多的话我传到csdn上面。但是问题在于csdn上面就不会更新了。
https://github.com/destinym/caculataue_mfc

将数据和运算分开。

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 界面的设计

设计模式 一 简单工场模式_第1张图片


一个很简单的界面。


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 一些问题

1 这个计算器在显示的时候有一些问题,显示的精度不够。

2 一个奇怪的问题,浮点数自身的特性。
   大家觉得10/3.0的结果是什么呢?用系统自带的计算器计算之后,发现是3.333333333333333
   上面的结果没有任何异议,但是如果你用c++编写代码,则结果是设置小数点后面的精度为15,则有的时候输出的结果为3.3333333333333334
   于是单步跟踪发现3.33333333333333335,总共17位(小数点后面为16为)最后一位是一个随机数,仔细想想浮点数double本身的精度为16位有效数字,所以最后一位,我感觉是标志浮点数结束之类的符号,但是不行的是printf()或者m_result.Format有一个不好的地方,就是要根据有效数字后面的一位四舍五入,这个就要命了,结果是否正确完全看人品了,所以是对是错。
   这个怎么解决我还没有想好,思路就是最后一位有效数字不要参与运算,直接舍弃。
3 输入的格式问题
   有三种格式,整数,小数,科学计数。
  本文中已经解决了整数的问题。
    下来就是小数和科学计数的问题。
    科学计数应用于超过9999999999999999,或者小于-999999999999999的时候
    但是最后的小数部分的显示比较麻烦。
    第一种情况,无限小数,
    比较简单,你算出整数部分的长度len,然后用16-len得到小数部分的长度。
    第二种情况,有限小数
    比较麻烦了,例如0.2+0.3,得到的结果是0.500000,或者说计算机对浮点数的技术其实都是不正确,之前有人写过文章,写的很深刻,现在才明白。
      那么怎么办呢,对于+,-*三种运算有两种方法,第一,将小数转换为整数,然后在将整数转换为小数,这样就知道小数点的位数,另一种方法是显示的时候使用"%g"格式化,就会去掉后面的0,做了一个近似转换,但是结果基本都是对的,因为我不敢确定极端情况的。
     对于/就比较麻烦了,还要分析是有限的还是无限的,目前还没有比较好的思路。





你可能感兴趣的:(设计模式,C++,mfc,计算器,简单工程模式)