【小项目】C++从0开始设计一个整型计算器(支持正负号,幂次方,公式正确性诊断)

目录

前置知识

进入设计

最好自己编几个输出函数,和辅助函数,方便检查

读入一个公式,在读入的时候进行一些处理,储存在str[ ]中

逆波兰式转化

逆波兰式计算

主函数以及测试输出


前置知识

c语言基础,最好看过c primer plus,因为我的很多套路都是用的这本书里的。

c++ STL了解,c++数据结构对栈和队列有基本了解,知道stack,queue的基本用法:push和pop,empty,size。

了解好了中缀转后缀(中缀转逆波兰式)。

这个随便在csdn上找就可以找到c语言的版本,流程大同小异,我稍作改动,但是基本一样。

进入设计

最好自己编几个输出函数,和辅助函数,方便检查

void outAntiBolan(std::queue<char>exp)
{
	while (!exp.empty())
	{
		char top = exp.front();
		exp.pop();
		putchar(top);
	}
	putchar('\n');
}

puts();//这个当然少不了

//判断操作符优先级
int getPriority(char op)
{
	switch (op)//因为全部return,所以不加break了
	{
	case '+':
	case '-':
	{
		return 1;
	}
	case '*':
	case '/':
	case '%':
	{
		return 2;
	}
	case '^':
	{
		return 3;
	}
	default:
	{
		return -1;
	}
	}
}

读入一个公式,在读入的时候进行一些处理,储存在str[ ]中

  • 左符号加正负号,在两个符号中补0.
    或者开头直接一个正负号,就先加0
    即可将带有正负号的全部转化成非负计算
  • 统计左右括号数量,进行简单的括号匹配,漏洞是比如这种 )(情况 
  • 碰到 3(或者)3或者)(或者(+之类的情况,可以选择中间补*号,也可以选择报错
  • 报错用bool返回值判定
  • 这样就可以得到一个可以直观上看不出问题的中缀了,但是有一个问题就是,如果经过计算发现除数是0呢?所以在计算步还要加验证

 所以这里有一个豪华的读入函数

其实我个人感觉,如果处理好了读入数据,内部运算会很舒服

bool Input(char str[])
{
	char ch;
	int p = 0;
	int left = 0, right = 0;

	ch = getchar();//先赋一个防止p-1越界
	if (ch == '+' || ch == '-')
		str[p++] = '0';//补0情况1
	if (ch == '(')//匹配括号的情况1
		left++;
	if (ch == ')')
		right++;
	str[p++] = ch;

	while ((ch = getchar()) != '\n')
	{
		//补0情况2/3,:左符号加正负号,以及左括号后加政府,简化,更改ch
		if (ispunct(str[p-1])
			&& (ch == '+' || ch == '-'))
		{
			str[p++] = '0';//补0
		}
		//数(,)数,)(,
		if ((isalnum(str[p - 1]) && ch == '(')
			|| (str[p - 1] == ')' && isalnum(ch))
			|| (str[p - 1] == ')' && ch == '('))
		{
			while (getchar() != '\n')
				continue;
			return false;//报错
		}
		//(op
		if (str[p - 1] == '(' 
			&& (ch == '*' || ch == '/' || ch == '%' || ch == '^'))
		{
			while (getchar() != '\n')
				continue;
			return false;//报错
		}
		//匹配括号的情况
		if (ch == '(')
			left++;
		if (ch == ')')
			right++;
		
		str[p++] = ch;
	}
	str[p] = '\0';//补上字符串结尾

	if (right != left)//括号匹配
	{
		//这里不用清理
		return false;
	}
		
	return true;//成功转化
}

逆波兰式转化

其实我的转化和他们写的稍微不同,我用的是队列储存exp,其实exp是只进不出的,所以stack和queue都行,但是考虑到输出方便,我才用了queue

我们还需要处理的就是:

  • 原有数字的结尾,这个简单,加个innum判断是否处于数字状态即可,一旦结束数字状态就压入一个结尾号
  • 需要注意的是最后一个数字,他是没有退出数字状态的,所以要额外判断一下

//转化处理好的str到逆波兰式的queue,其中会将整数用&分节
void antiBolan(std::queue<char>& exp, char str[])
{
	//初始化
	std::stack<char>op;
	char ch, temp;
	bool innum = false;

	//利用op栈和exp队列构造  
	for (int i = 0; i < strlen(str); i++)
	{
		ch = str[i];

		if (isalnum(ch))//num  
		{
			if (!innum)//标记进入数字
				innum = true;
			exp.push(ch);
			//如果是最后一个数字,那么后面的步骤都没了,自然就无法给他加&
			if (i == strlen(str) - 1)
				exp.push('&');
		}
			
		else if (ch == '(' || ch == ')')//括号  
		{
			if (innum)//刚出数字,标记出数字,加&结尾
			{
				innum = false;
				exp.push('&');
			}

			if (ch == '(')
				op.push(ch);
			else //右括号,那么这个右括号就不操作了,中间包的操作符一起压入  
			{
				while (1)
				{
					temp = op.top();
					op.pop();//不管如何,先弹出,如果是好,就压,不然就break,顺便还扔掉左括号  
					if (temp == '(')
						break;
					else
						exp.push(temp);
				}
			}
		}
		else //op  
		{
			if (innum)//刚出数字,标记出数字,加&结尾
			{
				innum = false;
				exp.push('&');
			}

			while (1)//不断和op的top元素比较,直到碰到恰当的  
			{
				if (op.empty() || op.top() == '(')//如果是空的,或者顶部为),直接入  
				{
					op.push(ch);
					break;
				}
				if (getPriority(ch) > getPriority(op.top())
					||(ch=='^'&&op.top()=='^'))//这里注意二连^可以套
				{
					op.push(ch);
					break;
				}
				else
				{
					temp = op.top();
					op.pop();
					exp.push(temp);
				}
			}
		}
	}
	while (!op.empty())
	{
		temp = op.top();
		op.pop();
		exp.push(temp);
	}
}

到这里,我们就得到一个可以计算的,但是不确定有0除数,和负次方,所以在后面的计算中要去判断,及时终止

逆波兰式计算

大体思路是先从前到后遍历,一整节一整节读数,转成int存到<int>型的stack里,如果要做浮点,在这种思路上做个扩充即可。

然后就是经典的后缀计算

整节读数可以用一个bool值标记是否进入数状态。

这里需要注意判断0除数和负次方问题

  • 0除数,当op为/但是exp.front()为0,这个时候就是0除数,直接终止函数打印报错
  • 负次方,这个在整型计算中算错,op为^但是front<0即可
int computeBolan(std::queue<char>exp)
{
	std::stack<int>ans;
	int a, b, c;
 	bool innum = false;
	char toInt[LEN];
	int p = 0;

	while (!exp.empty())
	{
		char temp = exp.front();
		exp.pop();

		if (isalnum(temp))//是数字
		{
			toInt[p++] = temp;//储存到临时字符串准备转化
			if (!innum)//进入数字状态
				innum = true;
		}
		else if (temp == '&')//是&
		{
			toInt[p] = '\0';//封口
			p = 0;//指针归零
			innum = false;//退出数字状态
			//puts(toInt);//输出验证
			ans.push(atoi(toInt));//转化并打入
		}
		else//这种temp就是op了
		{
			//弹出两个
			b = ans.top();
			ans.pop();
			a = ans.top();
			ans.pop();
			//判断计算,叠加错误判断
			switch (temp)
			{
			case '+':
			{
				ans.push(a + b);
				break;
			}
			case '-':
			{
				ans.push(a - b);
				break;
			}
			case '*':
			{
				ans.push(a * b);
				break;
			}
			case '/':
			{
				if (b == 0)//除数为0
				{
					puts("Divide 0.");
					return INT_MAX;//用int最大值标记,如果有好办法更好
				}
				ans.push(a / b);
				break;
			}
			case '%':
			{
				if (b == 0)//暂时不能对0求模
				{
					puts("error.");
					return INT_MAX;
				}
				ans.push(a % b);
				break;
			}
			case '^':
			{
				if (b < 0)
				{
					puts("error.");
					return INT_MAX;
				}
				ans.push((int)pow(a, b));
				break;
			}
			default :
			{
				puts("未知运算符");
			}
			}
		}
	}

	return ans.top();
}

主函数以及测试输出

input:

4
2^3
2^0
2^3^2
2^(3-1)^(10-8)

output:

8
1
512
16

input:

11
(2+8
2+8)
8/0
8/(8+5-13)
2^(2-5)
10-(80-30(/3*3+4
10-80-30)/3*3+4
(2+8)(3+2)
(2)3(8)
30(/3+3)+4
10(20-8)+2

output:

error.
error.
Divide 0.
Divide 0.
error.
error.
error.
error.
error.
error.
error.

input:

2
10(10)
14*10-(10)2

output:

error.
error.

input:

14
18-32
18/4
18%3
10+20*4
10-20/4
(18-3)*3
10*(10)
(10+2)/(8-10)
(2*3)/(5*2)
10-(80-30)/3*3+4
(((2+8)*2-(2+4)/2)*2-8)*2
(((8+2)*(4/2)))
10/0
(10-80*2

output:

-14
4
0
90
5
45
100
-6
0
-34
52
20
Divide 0.
error.

int main(void)
{
	freopen("input.txt", "r", stdin);
	int N, ans;
	scanf("%d\n", &N);
	while (N--)//一次性读入n行表达式
	{
	//中缀转逆波兰式,包含预处理正负号优化,,压入时增加数字分割,判定公式正确
		//读取,并优化正负号,初步判断公式正确性
		char str[LEN];
		if (Input(str) == false)//报错情况
		{
			puts("error.");
			continue;
		}
		//将str转换成逆波兰式,增加数字分割
		std::queue<char>exp;
		antiBolan(exp, str);
		//outAntiBolan(exp); //输出验证
		if ((ans = computeBolan(exp)) != INT_MAX)
		{
			printf("%d\n", ans);
		}
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亦梦亦醒乐逍遥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值