一、理论知识
表达式=(操作数)+(运算符)+(操作数)设 Exp = S1+OP+S2则称OP+S1+S2为前缀表示法S1+OP+S2为中缀表示法 S1+S2+OP为后缀表示法
例如:Exp = a x b + (c – d / e) x f,其前缀式:+ xa b x – c / d e f;中缀式:a x b +c – d / e x f; 后缀式:a b x c d e / - f x+
特点:
1) 失去了括号,操作数之间的相对次序不变;
2) 运算符的相对次序不同;(中缀没变)
3) 中缀式丢失了括弧信息,致使运算的次序不确定。
前缀式的运算规则特点:
1) 连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式;
2) 前缀式唯一确定了运算顺序。
后缀式的运算规则特点:
1) 运算符在式中出现的顺序恰为表达式的运算顺序;
2) 每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式;
3) 先找运算符,再找操作数;
4) 操作数的顺序不变。
以后缀式为例,后缀式计算方法:
1) 每个运算符的运算次序要由它之后的一个运算符来定;
2) 优先级高的运算符领先于优先级低的运算符。
从原表达式求得后缀式的规律
1) 设立运算符栈;
2) 设表达式的结束符为“#”,预设运算符栈的栈底为“#”;
3) 若当前字符是操作数,则直接发送给后缀式;
4) 若当前运算符的优先级高于栈顶运算符,则进栈;
5) 否则,退出栈顶运算符发送给后缀式;
6) “(”对它之前后的运算符起隔离作用,“)”可视为自相应左括弧开始的表达式的结束符。
算法求解过程:
1) 设置两个栈,一个存操作数,栈名为OPND,一个存运算符,栈名为OPTR栈。
2) 首先置操作数栈为空,表达式起始符#为运算符栈的栈底元素;
3) 依次读入表达式中每个字符,若是操作数则进OPND栈,若是运算符则和OPTR栈的栈顶运算符比较优先权;
4) 若栈顶运算符小于输入运算符,输入运算符进栈;
5) 若栈顶运算符等于输入运算符(只有栈顶是“(”,输入是“)”,或者栈顶是“#”,输入是“#”两种情况),分别去除一对括号或结束;
6) 若栈顶运算符大于输入运算符,弹出栈顶运算符,从OPND中弹出两个操作数与弹出运算符计算后再存入OPND栈,继续第3步,直到表达式操作结束。
二、代码实现
对输入表达式的要求:
1) 可以有空格,可以是多位数但是数字与数字之间不能有空格;
2) 只能输入数字和 + - */ ( ) 运算符,括号要是英文的,否则会报错;
3) 可以计算负数类型,但是输入表达式中的负数前面只能是 + 运算符或作为表达式开头,如- 5 + - 2 + -3或-5 + (-2) + (-3),2 + -5 * 3 – 6,负数前后最好不要加括号,2 + (-5) *3 –6结果为15,就出错了。
代码如下:
#include
#include
#include // 申请内存
#include // 内含isdigit()函数
#include // 断言函数
#include // 内含字符串处理函数
#define STACK_INIT_SIZE 100 // 栈容量
#define STACK_INCREMENT 10 // 栈增量
typedef float DATATYPE;
typedef char SYMBOLTYPE;
typedef struct stack
{
int *base; // 基地址
int *top; // 栈顶指针
int stackSize; // 栈容量
}*Stack;
// 栈的初始化
Stack Init_Stack(Stack S)
{
S=(Stack)malloc(sizeof(Stack));
if(!S)
exit(0);
S->base = (int*)malloc(STACK_INIT_SIZE*sizeof(DATATYPE));
if(!S->base)
exit(0);
S->top = S->base;
S->stackSize = STACK_INIT_SIZE;
return S;
}
// 判栈空
int IsEmpty(Stack S)
{
if (S->top == S->base)
{
return 1;
} else
{
return 0;
}
}
// 判栈满
int IsFull(Stack S)
{
if (S->top - S->base == S->stackSize)
{
return 1;
} else
{
return 0;
}
}
// 操作数压栈
void Push(Stack S, DATATYPE e)
{
assert(S);
if (IsFull(S))
{
S->base = (int*)malloc((STACK_INIT_SIZE+STACK_INCREMENT)*sizeof(DATATYPE));
if (!S->base)
exit(0); // 存储分配失败
S->top = S->base + S->stackSize;
S->stackSize += STACK_INCREMENT;
}
*S->top++ = e;
}
// 运算符压栈
void PushSymbol(Stack S, SYMBOLTYPE e)
{
assert(S);
if (IsFull(S))
{
S->base = (int*)malloc((STACK_INIT_SIZE+STACK_INCREMENT)*sizeof(DATATYPE));
if (!S->base)
exit(0); // 存储分配失败
S->top = S->base + S->stackSize;
S->stackSize += STACK_INCREMENT;
}
*S->top++ = e;
}
// 操作数弹栈
DATATYPE Pop(Stack S)
{
assert(S);
if (S->top == S->base)
return 0; // 空栈弹出0保证部分负数的正确运算
else
{
return *--S->top; // *--S->top就是*(--S->top)
}
}
// 运算符弹栈
SYMBOLTYPE PopSymbol(Stack S)
{
assert(S);
if (S->top == S->base)
return 0;
else
{
return *--S->top;
}
}
// 栈的销毁
void DestroyStack(Stack S) {
free(S->base);
free(S);
}
// 运算符优先级表
char Priority[7][7] =
{ // '+' '-' '*' '/' '(' ')' '#' 行row(左边的)是栈顶运算符,列col(上边的)是入栈运算符
{/*'+'*/'>','>','<','<','<','>','>'},
{/*'-'*/'>','>','<','<','<','>','>'},
{/*'*'*/'>','>','>','>','<','>','>'},
{/*'/'*/'>','>','>','>','<','>','>'},
{/*'('*/'<','<','<','<','<','=','0'},
{/*')'*/'>','>','>','>','0','>','>'},
{/*'#'*/'<','<','<','<','<','0','='}
};
// 确定运算符所在的行数或列数
int Operator(char c)
{
switch(c)
{
case '+': return 0;
case '-': return 1;
case '*': return 2;
case '/': return 3;
case '(': return 4;
case ')': return 5;
case '#': return 6;
default: return -1;
}
}
// 计算弹出的两个操作数与弹出栈顶运算符的值
float Calculation(float a, char op, float b)
{
switch(op)
{
case '+': return a+b;
case '-': return a-b;
case '*': return a*b;
case '/': return a/b;
default: return -1;
}
}
// 表达式求值函数
float CalculatingExpression(char *s)
{
int i;
strcat(s, "#"); // 为表达式s串接"#"
Stack OPND=NULL;
OPND = Init_Stack(OPND); // 创建操作数栈
Stack OPTR=NULL;
OPTR = Init_Stack(OPTR); // 创建运算符栈
PushSymbol(OPTR, '#'); //"#"压栈作为运算符栈的栈底元素
for (i=0; itop-1)), col = Operator(s[i]); // 确定栈顶运算符的行数,入栈运算符的列数
switch(Priority[row][col]) // 确定优先级
{
case '<': PushSymbol(OPTR, s[i]); break;
case '>': Push(OPND, Calculation(Pop(OPND), PopSymbol(OPTR), Pop(OPND))); --i; break;
//Push()参数里右边的Pop先执行;--i是为了下次继续对当前入栈运算符s[i]进行判断
case '=': PopSymbol(OPTR); break;
default: printf("输入错误,请检查数字之间是否有空格,表达式是否正确!\n");
DestroyStack(OPTR);
DestroyStack(OPND);
return -4294967296; // 运行到这一步,说明表达式错误,直接返回调用函数(主函数)
}
}
}
DestroyStack(OPTR);
return Pop(OPND); // 运行到这一步,说明表达式正确,弹出操作数栈的值即为运算结果
}
int main()
{
char s[100];
printf("请输入要计算的表达式:\n");
gets(s);
printf("表达式 %s 的值为:\n", s);
printf("%1.2f", CalculatingExpression(s));
return 0;
}
运行结果:
三、程序实现过程当中遇到的问题总结:
1) 从键盘获取一段字符串,gets(s)函数与scanf("%s",字符数组名或指针)相似,但不完全相同,使用scanf("%s, s)函数输入字符串时如果遇到空格会认为字符串结束,空格后的字符将作为下一个输入项处理,而gets()函数将接收输入的整个字符串直到遇到换行为止。相同点是都会自动在字符串末尾加’\0’。scanf(“%s”, str);str前面不要加&,数组名就是数组的首地址。
2) C语言中没有专门的函数,可以取字符串指定起始和终止位置的子串的这样一个函数,strcat()是串连接函数,strcpy()是串拷贝函数。如果已知起始和终止位置,可直接通过字符向字符串的转换完成,使用for循环(for循环通常是为了确定终止位置)结合strcat()也可达到取指定起始和终止位置的子串的目的。
3) 字符串默认结束标志是'\0',自己不加编译器会自动加,字符串处理函数(strcat、strlen、strlwr、strupr、puts、gets)操作字符串时不要考虑'\0',不管’\0’是手动加的还是函数自动加的,当做透明的就好。
4) switch语句的case里有了return就不用加break了,return是直接结束当前函数返回,break是直接结束当前循环返回,exit是直接结束当前程序返回。
5) 函数里面有多个参数,且参数也是函数时,计算顺序从右往左,即先确定右边的参数,再确定左边的参数。若两个函数参数是一样的,比如出栈,则右边的参数是先出栈的值,左边的是后出栈的值。
6) C中字符向字符串的转换:char str[3] = {c[1], '\0'};c[1]是字符,这种以字符方式初始化的结束字符要为’\0’;另外一种写在双引号里的以字符串方式初始化,编译器会自动为字符串末尾加’\0’,不管哪一种初始化方式都要留出加’\0’的空间。
7) 关于C数组越界,C中编译器不会检查数组是否越界。我们定义一个变量,实际上是向操作系统申请一段内存。这个内存块是随机的,它可以是目前空闲的任意一个内存段。由于这个不确定性,所以它后面的内存块有两种可能:如果恰好这段内存的后面的内存段(即越界地址)没有被其他程序占用,那么我们对它进行使用是不会出错的,但是它随时可能被其他程序修改。同时因为这段内存不属于本程序管理,那么它也有可能被其他程序占用,或者干脆就是操作系统禁止访问的区域,这时候就会导致错误的发生。
8) 字符串向整型、长整型、浮点数的转换函数atoi、atol、atof等,这些函数都只有一个参数,即要转换为数字的字符串,函数的返回值就是转换所得的数值。strtod()、strtol()、strtoul()函数也能将字符串转换为数字,同时还能检查溢出情况。strtod() 将字符串转换为双精度浮点型值,并报告不能被转换的所有剩余数字; strtol() 将字符串转换为长整值,并报告不能被转换的所有剩余数字; strtoul() 将字符串转换为无符号长整型值,并报告不能被转换的所有剩余数字。
9) C中要求有返回值的函数,如果函数里是if分支语句,要保证每个分支末尾都要有return。