堆栈计算器/字符串表达式的计算的基本思路和实现 winapi c++

程序的运行效果在最后面,本片内容从最简单的入手,一步一步实现最终的复杂效果,所以不要急于求成,一点一点来。如果有兴趣想要源码或者说进一步交流的可以私信我哦。(如果觉得还行的话,希望点个赞哦,给我一点写下去的动力,(@^ 0 ^@)/)还是先看效果吧!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
程序百度网盘地址:
链接:链接:https://pan.baidu.com/s/10esBJauqxqdabfE7Xo5XvQ
提取码:mhur (程序)
链接:https://pan.baidu.com/s/1JQlbXaZCqOpwJbsYLjzB6A
提取码:y3jh (程序配置文件,需要放在C:\AppData文件夹下面)

纵观全文,刚开始做起来可能会有一定的难度(主要是比较繁琐),但是后面大家会发现越做越简单,大家也可以发挥自己的聪明才智为它增加更多的功能。(放在最后感觉看的人不会多,所以就把总结写在前面了哈哈哈哈,机智如我)

总体思路

总的来说需要两个堆栈,一个是数据栈,一个是运算符栈。我们来一起开始吧。。。

一、实现最简单的四则运算
先找一个比较简单的表达式开始吧,1+2/3-4=(最后加上一个=表示的是结束标志)吧。(运算符栈为了不在运算过程中栈产生空的情况,预先压入一个 = )
声明:运算符优先级 */ > ± > =
(此处用户可以任意的增加运算符,但是需要处理好运算符的优先级顺序,可自行实践:^(指数运算)%(取余运算))

  • 读取到 1,(数值都是直接压入)
    数据栈:
    [1]
    运算符栈 :
    [=]
  • 读取到 +,+优先级大于=,直接压入
    数据栈:
    [1]
    运算符栈 :
    [+]
    [=]
  • 读取到 2,(数值都是直接压入)
    数据栈:
    [2]
    [1]
    运算符栈 :
    [+]
    [=]
  • 读取到 /,/ 优先级不小于 +,直接压入
  • 数据栈:
    [2]
    [1]
    运算符栈 :
    [/]
    [+]
    [=]
  • 读取到 3,(数值都是直接压入)
    数据栈:
    [3]
    [2]
    [1]
    运算符栈 :
    [/]
    [+]
    [=]
  • 读取到 -, - 优先级小于等于 (注意此处这么写是要和上面的不大于呼应)* ,需要弹出运算优先级高的 *,顺便从数据栈弹出两个数字:3,2(注意2在前,此处故意用 / 也是为了强调这个内容),然后弹出的运算符是 / ,所以计算 2/3 = 0.667,然后把0.667压进去数值栈。
    数据栈:
    [0.667]
    [1]
    运算符栈 :
    [+]
    [=]
  • 继续对比,由于 - 和 + 的优先级相同,满足小于等于的条件,所以继续进行上面类似的操作,1+0.667 = 1.667,把1.667压进去数值栈。
    数据栈:
    [1.667]
    运算符栈 :
    [=]
  • 继续对比,由于 - 的优先级大于 = ,所以将 - 压入运算符栈,然后继续读取内容,读取到了4,压入数据栈。
    数据栈:
    [4]
    [1.667]
    运算符栈 :
    [-]
    [=]
  • 读取到了 = ,= 的优先级小于等于 - ,所以继续计算,压入结果-2.33,然后继续对比,运算符是 = ,所以已经计算完毕了,直接弹出数据栈的最终结果就是表达式的最终结果。
    数据栈:
    [-2.33]
    运算符栈 :
    [=]
  • 所以最终的结果是:-2.33

简单说一下程序的设计思路:
首先便是运算符的结构体设计,运算符大致来说一共有三个参数,名称,优先级,运算参数个数,运算符描述(后面有更好用的用处),所以我定义了如下的结构体用来保存运算符的各种参数以及属性:(变量类型主要在后面用处比较大,此处先放在这里,大家一看便懂了)

//自定义的运算符的结构体
struct MyOperator
{
	BYTE bPriority;		//表示运算符的优先级
	BYTE bOperand;		//操作数的个数
	bool fIsPreset;		//是否是自定义
	tstring sCalcRule;	//表示的是运算的规则,采用字符串保存,参数为x和y,运算时候自动更换
};

//主要枚举了变量类型
enum EDataType
{
	eNone = 0,			//初始化的内容
	eOperator = 1,		//普通运算符
	eLBracket = 2,		//括号类型
	eRBracket = 3,		//括号类型
	eNumber = 4,		//数字
	eFunction = 5,		//函数类型
};

是不是发现少了一个参数,为了调用更加方便,此处我用的是c++标准stl容器中的map容器将名称和属性联系起来的:

//注意使用 map容器需要包含头文件
#include <stdlib.h>
#include <time.h>		//上面的两个是为了能够实现随机数的功能
#include <stack>		//主要用于存储的是数据栈和运算符栈
#include <map>			//主要用于保存运算符代号和对应的运算符的样子

using namespace std;	//必须要写的,这个叫命名空间,大家有兴趣的去查一下

//其中运算符的名称是map容器的键,简单来说就是唯一标识,然后属性保存在对应的值的位置
//这么通过map容器是为了后面如果检测到运算符运算的时候可以比较方便的获得运算符的属性
map<tstring, MyOperator> m_mapOperator;

///
//这个map容器的用法这里简单的说一点吧,渗透一下
//首先是增加内容
MyOperator operatorTemp;
operatorTemp.fIsPreset = fIsPreset;
operatorTemp.bPriority = bPriority;
operatorTemp.bOperand = bOperand;
operatorTemp.sCalcRule = sCalcRule;
m_mapOperator.insert(m_mapOperator.begin(), pair<tstring, MyOperator>(sOperator, operatorTemp));
//然后是如何查找对应的键的值并获得属性
map<tstring, MyOperator>::iterator it;
it = m_mapOperator.find(sOperator);
if (it != m_mapOperator.end())
{
	//it->first是对应的键的内容
	//it->second.xxx就可以获得对应的属性数值了
}
///	

—————————————————————————————
在表达式解析的时候,我的大概框架是这个样子的(最开始的源代码我也找不到了,现在只有最终的,大家理解思路就好):
首先是框架版本:(后面有补全的版本,此处仅仅是为了让大家实际操作一下)

bool CBCalcText::ParseExpression(tstring sTextToCalc, double& dResult)
{
	//运算符堆栈和数值堆栈的大概介绍
	stack<tstring> m_stackOperator;			//这个是运算符堆栈
	stack<double> m_stackNumber;			//这个是数值堆栈
	m_stackOperator.push(L"=");

	sTextToCalc += L"=";	//自动处理加上 =

	//
	//解析内容,填充堆栈内容并计算
	tstring sContent = L"";
	int i;
	EDataType eDataType = eNone;

	for (i = 0; i < (int)sTextToCalc.length(); i++)
	{
		//此处对内容进行匹配,包括几种吧:数值和运算符
		switch (sTextToCalc[i])
		{
		case '=':
		{
			switch (eDataType)
			{
			case eNone:
			case eOperator:		
				return false;	//这两种情况肯定是错误的
				break;
			case eNumber:
			{
				
			}
			break;
			default:
				break;
			}
		}
		break;
		case '0':	case '1':	case '2':	case '3':
		case '4':	case '5':	case '6':	case '7':
		case '8':	case '9':	case '.':
		{
			switch (eDataType)
			{
			case eNone:	
			{
				
			}
			case eNumber:
			{
				
			}
				break;
			case eOperator:
			{
				
			}
				break;
			default:
				break;
			}
		}
		break;
		case '+':	case '-':	case '*':	case '/': 
		{
			switch (eDataType)
			{
			case eNone:
			{
				
			}
				break;
			case eNumber:
			{
				
			}
				break;
			case eOperator:
			{
				
			}
				break;
			default:
				break;
			}
			
		}
		break;
		default:
			break;
		}
	}
	//此处要有错误警告,否则计算错误用户也不知道内容
	if (m_stackNumber.size() != 1 || m_stackOperator.size() != 1)
		return false;

	dResult = m_stackNumber.top();	//设置结果内容
	return true;
}

—————————————————————————————
然后是补全的版本,希望大家能够自己先实现一下,然后对着我的这个对比一下看看和自己写的异同点。

bool CBCalcText::ParseExpression(tstring sTextToCalc, double& dResult)
{
	//运算符堆栈和数值堆栈的大概介绍
	stack<tstring> m_stackOperator;			//这个是运算符堆栈
	stack<double> m_stackNumber;			//这个是数值堆栈
	m_stackOperator.push(L"=");

	sTextToCalc += L"=";	//自动处理加上 =

	//
	//解析内容,填充堆栈内容并计算
	tstring sContent = L"";
	int i;
	EDataType eDataType = eNone;

	for (i = 0; i < (int)sTextToCalc.length(); i++)
	{
		//此处对内容进行匹配,包括几种吧:数值和运算符
		switch (sTextToCalc[i])
		{
		case '=':
		{
			switch (eDataType)
			{
			case eNone:			//那就是表达式是什么也没有,只有加的一个 = 
			case eOperator:		//想想这个表达式出现这种情况也不对是吧
				return false;
				break;
			case eNumber:
			{
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
				sContent = L"";		//清空内容
				eDataType = eNone;
				if (!CalcStack(m_stackOperator, m_stackNumber, m_stackOperator.top()))
					return false;
				if (!CalcStack(m_stackOperator, m_stackNumber, L"="))
					return false;
			}
			break;
			default:
				break;
			}
		}
		break;
		case '0':	case '1':	case '2':	case '3':
		case '4':	case '5':	case '6':	case '7':
		case '8':	case '9':	case '.':
		{
			switch (eDataType)
			{
			case eNone:	
				eDataType = eNumber;
			case eNumber:
			{
				sContent += sTextToCalc[i];
				continue;
			}
				break;
			case eOperator:
			{
				if (!CalcStack(m_stackOperator, m_stackNumber, sContent))	//计算堆栈的内容
					return false;
			}
				break;
			default:
				break;
			}
			//此处表示运算也成功了,推入当前的运算符,然后继续
			m_stackOperator.push(sContent);
			sContent = sTextToCalc[i];
			eDataType = eNumber;
		}
		break;
		case '+':	case '-':	case '*':	case '/': 
		{
			switch (eDataType)
			{
			case eNone:
				if (sTextToCalc[i] == L'-')
					m_stackNumber.push(0);
				else
					return false;	//这个地方需要判断是不是 - ,如果是个负数那么可以这么填
				break;
			case eNumber:
			{
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			}
			break;
			case eOperator:
			{
				sContent += sTextToCalc[i];
				continue;
			}
			break;
			default:
				break;
			}
			sContent = sTextToCalc[i];
			eDataType = eOperator;
		}
		break;
		default:
			break;
		}
	}
	//此处要有错误警告,否则计算错误用户也不知道内容
	if (m_stackNumber.size() != 1 || m_stackOperator.size() != 1)
		return false;

	dResult = m_stackNumber.top();	//设置结果内容
	return true;
}

上面有个函数叫做CalcStack我们还没实现,接下来我们一起实现这个具体用来运算的函数:(此处就是一个运算符的优先级的比对,并没有特别复杂的地方,仅供大家参考,以实现具体功能为准)

bool CBCalcText::CalcStack(stack<tstring>& m_stackOperator, stack<double>& m_stackNumber, tstring sContent)
{
	double dNumTemp1 = 0;	//中间的运算数临时保存
	double dNumTemp2 = 0;	//中间的运算数临时保存
	map<tstring, MyOperator>::iterator it=m_mapOperator.begin();
	tstring sTopOper = L"";
	int bPriority = 0;

	//此处查找找到要压栈的运算符的优先级并保存,如果没有找到,那么直接返回失败
	it = m_mapOperator.find(sContent);
	if (it != m_mapOperator.end())
		bPriority = it->second.bPriority;
	else
		return false;

	//此处是找到运算符堆栈的栈顶元素的属性
	it = m_mapOperator.begin();
	it = m_mapOperator.find(m_stackOperator.top());
	if (it == m_mapOperator.end())
		return false;

	//此处采用循环的方式来对堆栈内的内容进行运算
	//如果运算优先级大于等于栈顶运算符,那么直接继续计算
	while (bPriority <= it->second.bPriority)
	{
		//首先对内容进行提取准备
		if (it->second.bOperand == 2)
		{
			//此处由于用户错误输入可能出错导致没有数值入栈,强行pop会导致错误
			if (m_stackNumber.size() < 2)	return false;

			dNumTemp2 = m_stackNumber.top();	m_stackNumber.pop();
			dNumTemp1 = m_stackNumber.top();	m_stackNumber.pop();
			sTopOper = m_stackOperator.top();

			//此处采用if-else if语法是为了后面的运算符可能是多个这样子的字符的组合设计的,
			//不能用 switch 语句
			if (sTopOper == L"+")
				m_stackNumber.push(dNumTemp1 + dNumTemp2);
			else if (sTopOper == L"-")
				m_stackNumber.push(dNumTemp1 - dNumTemp2);
			else if (sTopOper == L"*")
				m_stackNumber.push(dNumTemp1 * dNumTemp2);
			else if (sTopOper == L"/")
				m_stackNumber.push(dNumTemp1 / dNumTemp2);
			else if (sTopOper == L"^")
				m_stackNumber.push(pow(dNumTemp1, dNumTemp2));
			else if (sTopOper == L"%")
				m_stackNumber.push((int)dNumTemp1 % (int)dNumTemp2);
			else
				;
			m_stackOperator.pop();	//然后剔除出去这个运算符

			//此处继续找到运算符堆栈的栈顶元素的属性
			it = m_mapOperator.begin();
			it = m_mapOperator.find(m_stackOperator.top());
			if (it == m_mapOperator.end())
				return false;
		}
		else
			break;
	}
	return true;
}

好了第一步已经基本实现了,快拿去给好伙伴们炫耀一下吧。整体来说这个第一步不难,相信大家一定能够实现属于自己的四则运算的小程序的,然后第一步实现之后我们来一起进行下一步内容,使得表达式解析支持括号运算。

二、增加括号运算

好了,如果你已经完成了第一步的内容,那么就让我们开始第二步的内容吧,这里主要是为了添加括号运算对吧。

对于括号而言优先级是这样子的:等于 < 左括号 < 右括号 < 加减乘除(后面你会渐渐发现这么做的理由)。突然我们遇到了一个括号,如果是左括号的话,直接将左括号压进运算符的栈,如果遇到了右括号,由于右括号的优先级小于等于 + - * / 所以是不是直到发现左括号才停止,并且不要把右括号压入对应的符号栈之中。下面是更改后的代码内容:(为了方便起见,我直接写的是最终的结果,有兴趣的可以在第一个基础上增加内容)

bool CBCalcText::ParseExpression(tstring sTextToCalc, double& dResult)
{
	//运算符堆栈和数值堆栈的大概介绍
	stack<tstring> m_stackOperator;			//这个是运算符堆栈
	stack<double> m_stackNumber;			//这个是数值堆栈
	m_stackOperator.push(L"=");

	sTextToCalc += L"=";	//自动处理加上 =

	//
	//解析内容,填充堆栈内容并计算
	tstring sContent = L"";
	int i;
	EDataType eDataType = eNone;

	for (i = 0; i < (int)sTextToCalc.length(); i++)
	{
		//此处对内容进行匹配,包括几种吧:数值和运算符
		switch (sTextToCalc[i])
		{
		case '=':
		{
			switch (eDataType)
			{
			case eNone:			//那就是表达式是什么也没有,只有加的一个 = 
			case eOperator:		//想想这个表达式出现这种情况也不对是吧
				return false;
				break;
			case eNumber:
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			case eRBracket:
			{
				sContent = L"";		//清空内容
				eDataType = eNone;
				if (!CalcStack(m_stackOperator, m_stackNumber, m_stackOperator.top()))
					return false;
				if (!CalcStack(m_stackOperator, m_stackNumber, L"="))
					return false;
			}
			break;
			case eLBracket:
				return false;
				break;
			default:
				break;
			}
		}
		break;
		case '0':	case '1':	case '2':	case '3':
		case '4':	case '5':	case '6':	case '7':
		case '8':	case '9':	case '.':
		{
			switch (eDataType)
			{
			case eNone:		//此处由于执行的语句和下面的一样,所以不做特殊处理了
				eDataType = eNumber;
			case eNumber:
			{
				sContent += sTextToCalc[i];
				continue;
			}
				break;
			case eOperator:
			{
				if (!CalcStack(m_stackOperator, m_stackNumber, sContent))	//计算堆栈的内容
					return false;
			}
				break;
			case eLBracket:
				break;
			case eRBracket:
			{
				if (!CalcStack(m_stackOperator, m_stackNumber, L"*"))	//计算堆栈的内容
					return false;
				m_stackOperator.push(L"*");
			}
				break;
			default:
				break;
			}
			//此处表示运算也成功了,推入当前的运算符,然后继续
			m_stackOperator.push(sContent);
			sContent = sTextToCalc[i];
			eDataType = eNumber;
		}
		break;
		case '+':	case '-':	case '*':	case '/': 
		{
			switch (eDataType)
			{
			case eLBracket:
				m_stackOperator.push(L"(");
			case eNone:
				if (sTextToCalc[i] == L'-')
					m_stackNumber.push(0);
				else
					return false;	//这个地方需要判断是不是 - ,如果是个负数那么可以这么填
				break;
			case eNumber:
			{
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			}
			break;
			case eOperator:
			{
				sContent += sTextToCalc[i];
				continue;
			}
			break;
			case eRBracket:
				break;
			default:
				break;
			}
			sContent = sTextToCalc[i];
			eDataType = eOperator;
		}
		break;
		case '(':
		{
			switch (eDataType)
			{
			case eNone:
				break;
			case eNumber:
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			case eRBracket:
			{
				//此处支持一个比较好玩的功能吧,数字和括号直接接触,默认按照乘法计算
				if (!CalcStack(m_stackOperator, m_stackNumber, L"*"))	//计算堆栈的内容
					return false;
				m_stackOperator.push(L"*");
			}
				break;
			case eOperator:
			{
				if (!CalcStack(m_stackOperator, m_stackNumber, sContent))	//计算堆栈的内容
					return false;
				m_stackOperator.push(sContent);
			}
				break;
			case eLBracket:
				break;
			default:
				break;
			}
			sContent = sTextToCalc[i];
			eDataType = eLBracket;
		}
		break;
		case ')':
		{
			switch (eDataType)
			{
			case eNone:
			case eOperator:
			case eLBracket:
				return false;
				break;
			case eNumber:
			{
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			}
				break;
			case eRBracket:
				break;
			default:
				break;
			}
			//这边碰到右括号需要计算内容,直到找到左括号
			if (!CalcStack(m_stackOperator, m_stackNumber, L")"))	//计算堆栈的内容
				return false;
			sContent = L"";
			eDataType = eRBracket;
		}
		break;
		default:
			break;
		}
	}
	//此处要有错误警告,否则计算错误用户也不知道内容
	if (m_stackNumber.size() != 1 || m_stackOperator.size() != 1)
		return false;

	dResult = m_stackNumber.top();	//设置结果内容
	return true;
}

另外上面的CalcStack也要改动一点点,改动如下:

		//首先对内容进行提取准备
		if (it->second.bOperand == 2)
		{
			//内容太多,此处不贴出来了,多了看起来太乱
			//。。。。。。。。
		}
		else
		{
			if (m_stackOperator.top() == L"(")	//如果是 "(" 需要弹出去
				m_stackOperator.pop();
			break;
		}

/*
感觉大家可能听懂了就是不知道怎么实现,或者说实现之后有一定的小bug不知道怎么调试,我有时间将上面的对应的代码都给大家再写个标准代码,给大家做个参考,今天累了,睡了,白白。。。(2021年4月28日 0:08)
*/
好了很容易的你就实现了括号运算的功能,是不是很开心呢,接下来我们就要增加函数运算的功能了。(2021年4月8日 14:34)

三、增加单参数函数运算

此处函数的操作我的想法是在括号运算之后判判断前面是否是一个函数,如果是的话,那么就进行进一步的函数运算,如果没有那么直接返回就行,另外还有原来的字符解析的过程中的一点小改动,代码如下:

bool CBCalcText::ParseExpression(tstring sTextToCalc, double& dResult)
{
	//运算符堆栈和数值堆栈的大概介绍
	stack<tstring> m_stackOperator;			//这个是运算符堆栈
	stack<double> m_stackNumber;			//这个是数值堆栈
	m_stackOperator.push(L"=");

	sTextToCalc += L"=";	//自动处理加上 =

	//
	//解析内容,填充堆栈内容并计算
	tstring sContent = L"";
	int i;
	EDataType eDataType = eNone;

	for (i = 0; i < (int)sTextToCalc.length(); i++)
	{
		//此处对内容进行匹配,包括几种吧:数值和运算符
		switch (sTextToCalc[i])
		{
		case '=':
		{
			switch (eDataType)
			{
			case eNone:			//那就是表达式是什么也没有,只有加的一个 = 
			case eOperator:		//想想这个表达式出现这种情况也不对是吧
				return false;
				break;
			case eNumber:
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			case eRBracket:
			{
				sContent = L"";		//清空内容
				eDataType = eNone;
				if (!CalcStack(m_stackOperator, m_stackNumber, m_stackOperator.top()))
					return false;
				if (!CalcStack(m_stackOperator, m_stackNumber, L"="))
					return false;
			}
			break;
			case eLBracket:
				return false;
				break;
			default:
				break;
			}
		}
		break;
		case '0':	case '1':	case '2':	case '3':
		case '4':	case '5':	case '6':	case '7':
		case '8':	case '9':	case '.':
		{
			switch (eDataType)
			{
			case eFunction:
				return false;
				break;
			case eNone:		//此处由于执行的语句和下面的一样,所以不做特殊处理了
				eDataType = eNumber;
			case eNumber:
			{
				sContent += sTextToCalc[i];
				continue;
			}
				break;
			case eOperator:
			{
				if (!CalcStack(m_stackOperator, m_stackNumber, sContent))	//计算堆栈的内容
					return false;
			}
				break;
			case eLBracket:
				break;
			case eRBracket:
			{
				if (!CalcStack(m_stackOperator, m_stackNumber, L"*"))	//计算堆栈的内容
					return false;
				m_stackOperator.push(L"*");
			}
				break;
			default:
				break;
			}
			//此处表示运算也成功了,推入当前的运算符,然后继续
			m_stackOperator.push(sContent);
			sContent = sTextToCalc[i];
			eDataType = eNumber;
		}
		break;
		case '+':	case '-':	case '*':	case '/': 
		{
			switch (eDataType)
			{
			case eFunction:
				return false;
				break;
			case eLBracket:
				m_stackOperator.push(L"(");
			case eNone:
				if (sTextToCalc[i] == L'-')
					m_stackNumber.push(0);
				else
					return false;	//这个地方需要判断是不是 - ,如果是个负数那么可以这么填
				break;
			case eNumber:
			{
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			}
			break;
			case eOperator:
			{
				sContent += sTextToCalc[i];
				continue;
			}
			break;
			case eRBracket:
				break;
			default:
				break;
			}
			sContent = sTextToCalc[i];
			eDataType = eOperator;
		}
		break;
		case '(':
		{
			switch (eDataType)
			{
			case eNone:
				break;
			case eNumber:
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			case eRBracket:
			{
				//此处支持一个比较好玩的功能吧,数字和括号直接接触,默认按照乘法计算
				if (!CalcStack(m_stackOperator, m_stackNumber, L"*"))	//计算堆栈的内容
					return false;
				m_stackOperator.push(L"*");
			}
				break;
			case eOperator:
				if (!CalcStack(m_stackOperator, m_stackNumber, sContent))	//计算堆栈的内容
					return false;
			case eFunction:
				m_stackOperator.push(sContent);
				break;
			case eLBracket:
				break;
			default:
				break;
			}
			sContent = sTextToCalc[i];
			eDataType = eLBracket;
		}
		break;
		case ')':
		{
			switch (eDataType)
			{
			case eFunction:
			case eNone:
			case eOperator:
			case eLBracket:
				return false;
				break;
			case eNumber:
			{
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			}
				break;
			case eRBracket:
				break;
			default:
				break;
			}
			//这边碰到右括号需要计算内容,直到找到左括号
			if (!CalcStack(m_stackOperator, m_stackNumber, L")"))	//计算堆栈的内容
				return false;
			sContent = L"";
			eDataType = eRBracket;
		}
		break;
		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
		case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
		case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
		case 's': case 't': case 'w': case 'v': case 'u': case 'x':
		case 'y': case 'z':	case '\'':
		{
			switch (eDataType)
			{
			case eFunction:
				sContent += sTextToCalc[i];
				continue;
				break;
			case eNone:
				break;
			case eOperator:
			case eLBracket:
				m_stackOperator.push(sContent);
				break;
			case eNumber:
				//此处默认是乘法,类似的操作
				m_stackNumber.push(Val(sContent.c_str()));	//推入计算的数值
			case eRBracket:
			{
				if (!CalcStack(m_stackOperator, m_stackNumber, L"*"))	//计算堆栈的内容
					return false;
				m_stackOperator.push(L"*");
			}
				break;
			default:
				break;
			}
			eDataType = eFunction;
			sContent = sTextToCalc[i];
		}
			break;
		default:
			break;
		}
	}
	//此处要有错误警告,否则计算错误用户也不知道内容
	if (m_stackNumber.size() != 1 || m_stackOperator.size() != 1)
		return false;

	dResult = m_stackNumber.top();	//设置结果内容
	return true;
}

然后是CalcStack函数的一点改动:

			//首先对内容进行提取准备
		if (it->second.bOperand == 2)
		{
			//内容太多,此处不贴出来了,多了看起来太乱
			//。。。。。。。。
		}
		else
		{
			if (m_stackOperator.top() == L"(")	//如果是 "(" 需要弹出去
				m_stackOperator.pop();

			
			// 此处编写进一步的函数处理代码
			it = m_mapOperator.begin();
			it = m_mapOperator.find(m_stackOperator.top());
			if (it == m_mapOperator.end())
				return false;

			//判断下面是不是函数,如果是的话进行下面的操作
			if (it->second.bPriority == 40)
			{
				dNumTemp1 = m_stackNumber.top();
				sTopOper = m_stackOperator.top();
				if (sTopOper == L"log")
				{
					dNumTemp1 = log(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"rand")
				{
					dNumTemp1 = rand() % (int)dNumTemp1;
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"sin")
				{
					dNumTemp1 = sin(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"cos")
				{
					dNumTemp1 = cos(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"tan")
				{
					dNumTemp1 = tan(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"asin")
				{
					dNumTemp1 = asin(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"acos")
				{
					dNumTemp1 = acos(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"atan")
				{
					dNumTemp1 = atan(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"abs")
				{
					dNumTemp1 = abs(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"int")
				{
					dNumTemp1 = int(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"exp")
				{
					dNumTemp1 = exp(dNumTemp1);
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"fac")
				{
					for (int i = (int)dNumTemp1 - 1; i > 0; i--)
					{
						dNumTemp1 *= i;
					}
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else if (sTopOper == L"dfac")
				{
					for (int i = (int)dNumTemp1 - 2; i > 0; )
					{
						dNumTemp1 *= i;
						i -= 2;
					}
					m_stackNumber.pop();
					m_stackNumber.push(dNumTemp1);
					m_stackOperator.pop();
				}
				else //不是标准的可以识别的函数,所以直接返回失败
					return false;
			}
			break;
		}
			

哇哦,基础工作差不多做完了,后面就是为它增加可以自定义运算符的功能了,这个是怎么实现的呢,让我们共同期待下一个话题的更新。。。(2021年4月8日 21:10)

四、如何嵌套调用实现用户可以自定义的函数的功能

嘻嘻,是不是发现前的基础功能实现后后面的功能越来越简单了,此处增加自定义函数的功能并不需要改动多少代码,只需要稍稍改动一下原来的CalcStack函数的内容就行了,废话少说,上代码:

	
	//1.在下面这里的最后的else添加如下的代码
	if (sTopOper == L"+")
		m_stackNumber.push((int)dNumTemp1 % (int)dNumTemp2);
	//else if(...)
	//	......
	else
	{
		//此处便是判断是不是自定义运算符的地方了,如果存在的话可以直接计算,否则返回失败
		it = m_mapOperator.find(sTopOper);
		if (it != m_mapOperator.end() && it->second.bOperand == 2)	//找到了对应的保存的新的运算符
		{
			//根据保存的运算方式进行字符串替换,然后进行新的计算
			sCalcText = it->second.sCalcRule;
			ChangeText(sCalcText, L"X", Str(dNumTemp1));
			ChangeText(sCalcText, L"Y", Str(dNumTemp2));
			if (!ParseExpression(sCalcText, dNumTemp1))
				return false;
	
			m_stackNumber.push(dNumTemp1);	//推入计算结果
		}
		else
			return false;
	}

	//2.在下面这里的最后的else添加如下的代码
	dNumTemp1 = m_stackNumber.top();
	sTopOper = m_stackOperator.top();
	if (sTopOper == L"log")
	{
		dNumTemp1 = log(dNumTemp1);
		m_stackNumber.pop();
		m_stackNumber.push(dNumTemp1);
		m_stackOperator.pop();
	}
	//else if(...)
	//	......
	else //不是标准的可以识别的函数,所以直接返回失败
	{
		//此处可能是对应的自定义的运算符,所以进行自定义的检测和运算
		it = m_mapOperator.find(sTopOper);
		if (it != m_mapOperator.end()&& it->second.bOperand == 1)	//找到了对应的保存的新的运算符
		{
			sCalcText = it->second.sCalcRule;
			ChangeText(sCalcText, L"X", Str(dNumTemp1));
			ParseExpression(sCalcText, dNumTemp1);
			m_stackNumber.pop();
			m_stackNumber.push(dNumTemp1);
			m_stackOperator.pop();
		}
		else
			return false;
	}

其中的ChangeText就是将其中的某些字符串用其他的字符串替换,生成正确的运算表达式罢了,下面便是对应的实现代码:

void CBCalcText::ChangeText(tstring& sTextSrc, tstring sTextFind, tstring sTextChange)
{
	//对换行消息进行预处理,将 \r\n 转换为 \\n
	int iPos = 0;
	while (true)
	{
		iPos = sTextSrc.find(sTextFind);
		if (iPos != string::npos)
		{
			sTextSrc = sTextSrc.erase(iPos, sTextFind.length());
			sTextSrc.insert(iPos, sTextChange);
		}
		else
			break;
	}
}

好了到这里全部的功能就算是实现了,看到最后的人一定都是认真学习的,感谢您的阅读!(2021年4月8日 21:40)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值