字符串表达式计算器的设计

思路

  1. 将通常的中缀表达式(Infix Notation)转化为后缀表达式(Postfix Notation)
  2. 计算后缀表达式

后缀表达式的计算

比如中缀表达式9 + (3 - 1) * 3 + 10 / 2

转化成后缀表达式为9 3 1 - 3 * + 10 2 / +

具体计算步骤为:

  1. 9 2 3 * + 10 2 / +
  2. 9 6 + 10 2 / +
  3. 15 10 2 / +
  4. 15 5 +
  5. 20

计算时,遍历后缀表达式,碰到操作符后,就取符号前面的两个数字作为操作数,计算完后再把结果放回去,如此下来,直到全部算完只剩下一个数,就是结果。后缀表达式相较于中缀表达式更适合计算机计算,不需要括号,只依靠顺序来确定运算符优先级。


如何将中缀表达式转化为后缀表达式?

A方法

从头到尾读取中缀表达式的每一个对象,对不同对象按不同的情况处理。

  1. 运算数:直接输出;
  2. 左括号:压入堆栈;
  3. 右括号将栈顶的运算符弹出输出直到遇到左括号(出栈,不输出);
  4. 运算符
  • 优先级大于栈顶运算符,则压入堆栈
  • 优先级小于等于栈顶运算符,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
  1. 若各对象处理完毕,则把堆栈中存留的运算符一并输出

思考一下为什么要这么做

用计算机的视角去看待这个问题:

我:输入a

计算机:a :a是操作数吧

(于是输出a)

我:输入+

计算机: a +: 知道了,+号的左操作数是a

(+入栈)

我:输入b

计算机:a+b:+号的右操作数还不能断定是b,看下一个符号的情况吧

我:输入*

计算机:a+b*:ok,*号的左操作数是b

我:输入c

计算机:a+b*c:*号的右操作数是c

我:输入-

计算机:a+b*c-:b*c是-的左操作数

我:输入d,回车

计算机:a+b*c-d,d是-的右操作数

我:怎么感觉和递归好像...

B方法

例如表达式a + b * c - (d + e)

  1. 按照运算符优先级对所有运算单位加括号

((a + (b * c)) - (d + e))

  1. 将括号内的运算符移到括号外右边

((a (b c)*) + (d e)+)-

  1. 去掉括号,即得到了后缀表达式

a b c * + d e + -

具体实现(C语言)

该函数返回符号对应的优先级

int getPriority(char sign)
{
	switch(sign) {
		case '+':
		case '-': return 1;
		case '/':
		case '*': return 2;
		case '^': return 3;
		case '(': return 0;
		default:  return -1;
	}
}

该函数将中缀表达式转化为后缀表达式,使用了链式结构的堆栈。因为要返回一个新的字符串,所以使用了static修饰符,需要注意的是就是如果第一次用temp保存返回的结果,再次调用transform函数之后,temp指向的内容也会发生变化。还有就是如果出现了'-',那么它既有可能是单目的取反符号,也有可能是双目的减号,需要判断一下。如果是单目的取反符,那么就要在它前面补上0使之成为减法运算。

char* transform(const char infix[])
{
	unsigned cnt = 0;
	static char ret[MAX];
	Stack signs = CreateStack();
	for (; *infix != ''; ++infix) {
		switch(*infix) {
			case '0':case '1':case '2':
			case '3':case '4':case '5':
			case '6':case '7':case '8':
			case '9':case '.':
			ret[cnt++] = *infix;break;
			
			case '-':
				if (cnt == 0 || !isdigit(*(infix-1))) {
					ret[cnt++] = '0';
				}
			case '+':case '*':case '/':case '^':
				ret[cnt++] = ' ';
				while (!StackIsEmpty(signs) && getPriority(*infix) <= getPriority(StackTop(signs)))
				{
					ret[cnt++] = PopStack(signs);
					ret[cnt++] = ' ';
				}
			case '(':
				PushStack(signs, *infix);break;

			case ')':
				ret[cnt++] = ' ';
				while (StackTop(signs) != '(') {
					ret[cnt++] = PopStack(signs);
					ret[cnt++] = ' ';
				}
				PopStack(signs);break;
		}			
	}
	ret[cnt++] = ' ';
	while (!StackIsEmpty(signs)) {
		ret[cnt++] = PopStack(signs);
		ret[cnt++] = ' ';
	}
	ret[cnt] = '';
	DeleteStack(signs);
	return ret;
}

该函数计算后缀表达式的值,使用了顺序结构的堆栈。先将后缀表达式按空格分割成单个的运算数或符号。再进行遍历,遇到数字便进栈,遇到符号便取出栈顶的两个元素计算后放回堆栈。遍历完之后,栈顶元素就是计算结果。

double compute(const char *postfix)
{
	
	double stack[MAX], op1, op2;
	char sequence[MAX][16];
	unsigned cnt = 0;
	int top = -1;
	char *sq = sequence[cnt]; 
	const char *pf = postfix;
	for (; *pf; ++pf)
		if (*pf != ' ') {
			for (; *pf != ' '; *sq++ = *pf++);
			*sq = '';
			sq = sequence[++cnt];
		}

	sq = sequence[0];
	for (unsigned i = 0; i < cnt; sq = sequence[++i]) {
		if (isdigit(sq[0])) stack[++top] = atof(sq);
		else {
			op1 = stack[top-1], op2 = stack[top];
			top = top - 2;
			switch (sq[0]) {
				case '+': stack[++top] = op1 + op2; break;
				case '-': stack[++top] = op1 - op2; break;
				case '*': stack[++top] = op1 * op2; break;
				case '/': stack[++top] = op1 / op2; break;
				case '^': stack[++top] = pow(op1, op2); break;
			}
		}
		
	}
	return stack[0];
}

一些小收获

在C的函数里,函数内是无法获取数组长度的,只能在传入参数是给定长度,不过,通过宏可以实现调用函数时无需传入数组长度。

void func_(int n[], int len);
#define func(n) func_((n), sizeof(n)/sizeof(n[0]))
原文地址:https://www.cnblogs.com/ark2000/p/10584302.html