C语言实现表达式求值
文章结构
本文尝试通过纯C语言写一个计算器,在Visual Studio和VScode运行都可以通过,大家可以下载到自己的编译器试一下。其中主要涉及的知识点是C语言手写栈,中缀表达式与后缀表达式的转换等。
C语言手写栈Struct Stack
stack.h
本文通过顺序结构写出栈的结构体,并实现了相应的入栈、出栈、初始化等操作
// An highlighted block
#include<stdlib.h>
typedef double ElemType; //这里比较重要,关乎栈中存储的是什么元素
typedef struct stack
{
int top; //表示栈顶元素标识
int maxSize;
ElemType *element;
}Stack;
//创建一个能容纳mSize和单元的空栈
void Create(Stack *S, int mSize) {
S->maxSize = mSize;
S->element = (ElemType*)malloc(sizeof(ElemType) * mSize); //申请了一段double型的内存当数组用了
S->top = -1;
}
//销毁栈,释放空间
void Destroy(Stack *S) {
S->maxSize = -1;
free(S->element);
S->top = -1;
}
//清楚栈中元素,但不释放空间
void Clear(Stack *S) {
S->top = -1;
}
//判断堆栈是否已满,若已满返回true,否则返回false
bool IsFull(Stack *S) {
return S->top == S->maxSize - 1;
}
//判断堆栈是否为空,空返回true, 否则返回false
bool IsEmpty(Stack *S) {
return S->top == -1;
}
//获取栈顶元素,通过指针x返回,操作成功返回true, 否则返回false
bool Top(Stack *S, ElemType *x) {
if(IsEmpty(S))
return false;
*x = S->element[S->top];
return true;
}
//栈顶位置插入元素x入栈
bool Push(Stack *S, ElemType x) {
if(IsFull(S))
return false;
S->top++;
S->element[S->top] = x;
return true;
}
//出栈,删除栈顶元素
bool Pop(Stack *S) {
if(IsEmpty(S))
return false;
S->top--;
return true;
}
后缀表达式求值
如果大家会写后缀表达式的话,直接调用前面写的“stack.h”就可以求值了。
中缀表达式是我们常见的“a + b”这种格式,转换后缀表达式就是“a b +”的形式了。
我们数学中理解表达式通常都是中缀表达式的格式,但计算机理解表达式需要转换为后缀表达式的形式(如果是通过栈来实现的话)。
后缀表达式计算思想:从左至右扫描后缀表达式,遇到操作数进栈;遇到操作符,则从栈中弹出前两个元素执行相应计算,将计算结果再进栈,直到后缀表达式扫描结束。注意这里只考虑双目操作符。
C语言部分操作符优先级:
操作符 | 优先级 |
---|---|
-, ! | 7 |
*, /, % | 6 |
+, - | 5 |
<, <=, >, >= | 4 |
==, != | 3 |
&& | 2 |
双竖“或”(这里敲不出来) | 1 |
中缀表达式、后缀表达式转换:
中缀表达式 | 后缀表达式 |
---|---|
a*b+c | ab*c+ |
a*b/c | ab*c/ |
abcde*f | abcdef* |
a+(b*c+d)/e | abc*d+e/+ |
a*((b+c)/(d-e)-f) | abc+de-/f-* |
a/(b-c)+d*e | abc-/de*+ |
// An highlighted block
#include<iostream>
#include "stack.h"
#include<math.h>
#include<string.h>
using namespace std;
#define STACKSIZE 20
#define ITEMSIZE 20
#define POSTFIXSIZE 200
//判断表达式中是否有非法字符(合法字符包括0~9 . + - * / ^ )
bool IsLegal(char *postfix) {
int i;
char c;
for(i = 0; i < strlen(postfix); i++) {
c = postfix[i];
if(!((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == ' '))
return false;
}
return true;
}
//从表达式当前位置curPos获取元素,完成后移动到下一元素首字符位置
//操作数返回0,操作符返回1,异常返回-1
int GetItem(char *postfix, int *curPos, char *item) {
int i = 0, k = *curPos, flag;
if(postfix[k] == '.') { //元素首字符不能是小数点
flag = -1;
} else if(postfix[k] >= '0' && postfix[k] <= '9') { //元素首字符是数字,是正确的操作数
while ((postfix[k] >= '0' && postfix[k] <= '9') || postfix[k] == '.') {
item[i++] = postfix[k++];
}
item[i] = '\0'; //将数字截取为字符串形式保存
flag = 0;
} else { //该元素为操作符
item[0] = postfix[k++];
item[1] = '\0';
flag = 1;
}
while(postfix[k] == ' ') { //跳过当前元素后面的空格,在下一次趣元素的起始位置为非空格字符
k++;
}
*curPos = k;
return flag;
}
//根据操作符执行计算,包括两个操作数和一个操作符(例如a+b, a左操作数,+操作符,b右操作数)
void DoOperator(Stack *S, char oper) {
double oper1, oper2;
if(!Top(S, &oper1)) { //从栈中弹出右操作数
printf("异常:后缀表达式格式出错,存在多余操作符\n");
exit(0);
}
Pop(S);
if(!Top(S, &oper2)) { //从栈中弹出左操作数
printf("异常:后缀表达式格式出错,存在多余操作符\n");
exit(0);
}
Pop(S);
switch (oper) //根据操作符执行相应运算
{
case '+' :
Push(S, oper2 + oper1);
break;
case '-' :
Push(S, oper2 - oper1);
break;
case '*' :
Push(S, oper2 * oper1);
break;
case '/' :
if(fabs(oper1) < 1e-6) { //判断分母是否为0
printf("异常:除数不可以为0!\n");
exit(0);
} else {
Push(S, oper2 / oper1);
}
break;
case '^' :
Push(S, pow(oper2, oper1));
break;
}
}
//对后缀表达式postfix求值
double Calculating(char *postfix) {
Stack S;
char item[ITEMSIZE]; //储存后缀表达式中的元素
double data;
int flag = -1; //标记当前扫描元素的类型,1表示操作数,0表示操作符,-1表示异常
int curPos = 0; //记录当前扫描元素首字符下标位置
while(postfix[curPos] == ' ') //过滤postfix前面所有空格
curPos++;
Create(&S, STACKSIZE); //创建栈,申请内存
while (curPos < strlen(postfix))
{
flag = GetItem(postfix, &curPos, item); //获取当前扫描的表达式的元素
if(flag == -1) {
printf("异常:后缀表达式元素不合法!\n");
exit(0);
} else if(flag == 1) { //执行相应的操作符运算
switch (item[0])
{
case '+':
case '-':
case '*':
case '/':
case '^':
DoOperator(&S, item[0]);
break;
}
} else { //操作数进栈
data = atof(item); //将字符串转换为浮点数输出
Push(&S, data);
}
}
if(S.top == 0) { //如果栈中只剩下唯一元素,则栈顶元素即为计算结果
Top(&S, &data);
} else {
printf("异常:后缀表达式格式出错,存在多余操作数\n");
exit(0);
}
Destroy(&S); //释放空间
return data;
}
int main() {
char postfix[POSTFIXSIZE]; //存储表达式中的扫描元素
printf("请输入后缀表达式格式(连续操作数之间用空格隔开):\n");
gets(postfix); //输入流读取后缀表达式,cmd输入计算表达式(后缀表达式形式)
if(!IsLegal(postfix)) {
printf("异常:中缀表达式存在非法字符\n");
} else
printf("%s = %.2f\n", postfix, Calculating(postfix)); //输出计算结果,保留两位小数1
system("pause");
return 0;
}
输入格式(数字之间需要空格隔开)
1 2 +
===> 3.00
6 4 2 - / 3 2 * +
===> 9.00
计算复杂度:O(n)
中缀表达式转后缀表达式
"stack.h"
#include<stdlib.h>
typedef char ElemType;
typedef struct stack
{
int top; //表示栈顶元素标识
int maxSize;
ElemType *element;
}Stack;
//创建一个能容纳mSize和单元的空栈
void Create(Stack *S, int mSize) {
S->maxSize = mSize;
S->element = (ElemType*)malloc(sizeof(ElemType) * mSize); //申请一段double型的内存当数组用
S->top = -1;
}
//销毁栈,释放空间
void Destroy(Stack *S) {
S->maxSize = -1;
free(S->element);
S->top = -1;
}
//清楚栈中元素,但不释放空间
void Clear(Stack *S) {
S->top = -1;
}
//判断堆栈是否已满,若已满返回true,否则返回false
bool IsFull(Stack *S) {
return S->top == S->maxSize - 1;
}
//判断堆栈是否为空,空返回true, 否则返回false
bool IsEmpty(Stack *S) {
return S->top == -1;
}
//获取栈顶元素,通过指针x返回,操作成功返回true, 否则返回false
bool Top(Stack *S, ElemType *x) {
if(IsEmpty(S))
return false;
*x = S->element[S->top];
return true;
}
//栈顶位置插入元素x入栈
bool Push(Stack *S, ElemType x) {
if(IsFull(S))
return false;
S->top++;
S->element[S->top] = x;
return true;
}
//出栈,删除栈顶元素
bool Pop(Stack *S) {
if(IsEmpty(S))
return false;
S->top--;
return true;
}
“main.cpp”
#include<iostream>
#include "stack.h"
#include<math.h>
#include<string.h>
using namespace std;
#define STACKSIZE 20 //定义栈的容量大小
#define ITEMSIZE 20 //表达式中元素最大长度
#define EXPSIZE 200 //表达式最大长度
//判断表达式中是否有非法字符(合法字符包括0~9 . + - * / ^ )
bool IsLegal(char *postfix) {
int i;
char c;
for(i = 0; i < strlen(postfix); i++) {
c = postfix[i];
if(!((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == ' '))
return false;
}
return true;
}
//从表达式当前位置curPos获取元素,完成后移动到下一元素首字符位置
//操作数返回0,操作符返回1,异常返回-1
int GetItem(char *postfix, int *curPos, char *item) {
int i = 0, k = *curPos, flag;
if(postfix[k] == '.') { //元素首字符不能是小数点
flag = -1;
} else if(postfix[k] >= '0' && postfix[k] <= '9') { //元素首字符是数字,是正确的操作数
while ((postfix[k] >= '0' && postfix[k] <= '9') || postfix[k] == '.') {
item[i++] = postfix[k++];
}
item[i] = '\0'; //将数字截取为字符串形式保存
flag = 0;
} else { //该元素为操作符
item[0] = postfix[k++];
item[1] = '\0';
flag = 1;
}
while(postfix[k] == ' ') { //跳过当前元素后面的空格,在下一次趣元素的起始位置为非空格字符
k++;
}
*curPos = k;
return flag;
}
//获取操作符的栈外优先级
int ICP(char c) {
if(c == '#') {
return 0;
} else if(c == '(') {
return 7;
} else if(c == '*' || c == '/') {
return 4;
} else if(c == '+' || c == '-') {
return 2;
} else if(c == ')') {
return 1;
} else {
printf("%c!\n", c); //后缀表达式不支持操作符
exit(-1);
}
}
//获取操作符的栈内优先级
int ISP(char c) {
if(c == '#') {
return 0;
} else if(c == '(') {
return 1;
} else if(c == '*' || c == '/') {
return 5;
} else if(c == '+' || c == '-') {
return 3;
} else if(c == ')') {
return 7;
} else {
printf("后缀表达式不支持操作符%c!\n", c); //后缀表达式不支持操作符
exit(-1);
}
}
//将中缀表达式infix转换为后缀表达式postfix,后缀表达式中的相邻元素(操作符或操作数)用空格分开
void InfixToPostfix(char *infix, char *postfix) {
Stack S;
char item[ITEMSIZE]; //存储中缀表达式的元素,通过字符数组形式,C++中可以通过String类型,相同道理
int flag = -1; //标记当前扫描元素类型,1操作符,0操作数,-1异常
int curPos = 0; //记录当前扫描元素首字符下标位置
int k = 0, i;
char ch, curOP;
while(infix[curPos] == ' ') //过滤infix前面所有空格
curPos++;
Create(&S, STACKSIZE); //创建栈,申请内存空间
Push(&S, '#');
while(curPos < strlen(infix)) {
flag = GetItem(infix, &curPos, item); //获取当前扫描表达式的元素
if(flag == -1) {
printf("异常:中缀表达式元素不合法!\n"); //异常:中缀表达式元素不合法!
exit(-1);
} else if(flag == 1) { //当前元素为操作符或界符
curOP = item[0]; //curOP是当前操作符,只有一个字符表示就够了
if(curOP == ')') { //扫描到右括号的时候进行出栈操作,实现括号闭合,直到遇到左括号
do {
Top(&S, &ch);
Pop(&S);
if(ch == '#') {
printf("异常:中缀表达式元素不合法!\n"); //异常:中缀表达式元素不合法!
exit(-1);
}
if(ch != '(') { //左括号不输出
postfix[k++] = ch;
postfix[k++] = ' '; //相邻元素用空格分开
}
} while (ch != '(');
} else { //扫描到其他操作符时候的处理
Top(&S, &ch); //获取当前栈顶操作符
while (ICP(curOP) <= ISP(ch))
{
Pop(&S);
postfix[k++] = ch;
postfix[k++] = ' '; //相邻元素空格分开
Top(&S, &ch);
}
Push(&S, curOP); //当前扫描到的操作符进栈
}
} else { //当前扫描到的是操作数,不做处理直接输出
for(i = 0; i < strlen(item); i++, k++) {
postfix[k] = item[i];
}
postfix[k++] = ' '; //相邻元素空格分开
}
}
while (!IsEmpty(&S)) //输出栈中剩余操作符
{
Top(&S, &ch);
Pop(&S);
if(ch != '#') {
postfix[k++] = ch;
postfix[k++] = ' ';
}
}
postfix[--k] = '\0'; //去除最后多余空格
}
int main() {
char infix[EXPSIZE]; //存储中缀表达式
char postfix[EXPSIZE]; //存储后缀表达式
printf("请输入中缀表达式:%s\n", infix); //请输入中缀表达式:
gets(infix);
if(!IsLegal(infix)) {
printf("异常:中缀表达式存在非法字符\n"); //异常:中缀表达式存在非法字符
return -1;
} else {
InfixToPostfix(infix, postfix);
printf("%s ===> %s\n", infix, postfix); //输出转换后的后缀表达式
}
system("pause");
return 0;
}
1+2
===>1 2 +
时间复杂度O(n)