算术表达式可以使用中缀表达式和后缀表达式(逆波兰表达式)计算,以下分别实现这两个方法,以下实现方法可以在算术表达式中包含部分函数,如三角函数sin等。
由于普通的算术操作符(如“+-*/”)只包含一个字符,而函数会包含多个字符,为了方便先将函数用一个FunctionId的枚举替代,该枚举从1开始,算术表达式一般为明文字符串,这些枚举数只要使用20以下的数字就不会发生冲突,只是打印出来会是乱码,计算不会受影响。
enum FunctionId
{
None = 0,
Sin = 1,
Cos = 2,
Tan = 3,
Cot = 4,
FunctionMax,
};
然后定义函数和运算符的结构FunctionInfo和OperatorInfo,以及一些全局变量和方法。
struct FunctionInfo
{
FunctionId funcId;
char funcStr[10];//函数名,如"sin"
int bytesOfStr;//函数名字节数,如"sin"为3字节
FunctionInfo(FunctionId InFuncId, const char* InFuncStr, int InBytes = 0)
{
funcId = InFuncId;
memset(funcStr, 0, 10);
bytesOfStr = InBytes > 0 ? InBytes : strlen(InFuncStr);
memcpy(funcStr, InFuncStr, bytesOfStr);
}
static FunctionInfo None;
};
FunctionInfo FunctionInfo::None = FunctionInfo(FunctionId::None, "", 0);
FunctionInfo g_functionInfos[] =
{
FunctionInfo(FunctionId::Sin, "sin", 3),
FunctionInfo(FunctionId::Cos, "cos", 3),
FunctionInfo(FunctionId::Tan, "tan", 3),
FunctionInfo(FunctionId::Cot, "cot", 3),
FunctionInfo::None
};
//判断是否是函数
FunctionInfo GetFunctionInfo(const char* InExp)
{
for (int i = 0; i < FunctionId::FunctionMax; i++)
{
if (g_functionInfos->funcId == FunctionId::None)
{
return FunctionInfo::None;
}
if (memcmp(InExp, g_functionInfos[i].funcStr, g_functionInfos[i].bytesOfStr) == 0)
{
return g_functionInfos[i];
}
}
return FunctionInfo::None;
}
struct OperatorInfo
{
char ch;//运算符
int priority;//优先级
OperatorInfo(char InChar, int InPriority)
{
ch = InChar;
priority = InPriority;
}
};
OperatorInfo g_operatorInfos[] =
{
OperatorInfo('(', 0),
OperatorInfo(')', 0),
OperatorInfo('+', 1),
OperatorInfo('-', 1),
OperatorInfo('*', 2),
OperatorInfo('/', 2),
OperatorInfo('%', 2),
OperatorInfo('^', 3),
OperatorInfo('\0', -1)
};
//判断是否为操作符
bool IsOperator(char c)
{
for (int i = 0;; i++)
{
if (g_operatorInfos[i].ch == c)
{
return true;
}
if (g_operatorInfos[i].ch == '\0')
{
return false;
}
}
return false;
}
//获取操作符优先级
int GetPriority(char c)
{
//函数优先级最高
if (c > FunctionId::None && c < FunctionId::FunctionMax)
{
return 10;
}
for (int i = 0;; i++)
{
if (g_operatorInfos[i].ch == c)
{
return g_operatorInfos[i].priority;
}
if (g_operatorInfos[i].ch == '\0')
{
return -1;
}
}
return -1;
}
bool IsNumber(char c)
{
return c >= '0' && c <= '9';
}
//是否为数字,包括小数点
bool IsNumberOrDot(char c)
{
return IsNumber(c) || (c == '.');
}
1、中缀表达式计算
中缀表达式需要一个操作符栈和操作数栈,多个字符的函数被替换为一个字节存放到操作符栈中,判断时只需判断是否大于None且小于FunctionMax,计算值时操作数只有一个。
double CalcValueByInfixExpression(const char* expression)
{
std::stack<char> operatorStack;//操作符栈,包括普通运算符和函数
std::stack<double> dataStack;//操作数栈
const char* iterExp = expression;
if (*iterExp == '-')//判断第一个符号是否为'-',如果是就按"0-..."处理
{
dataStack.push(0);
operatorStack.push('-');
iterExp++;
}
while (*iterExp != '\0')
{
if (*iterExp == ' ')
{
iterExp++;
continue;
}
FunctionInfo funcInfo = GetFunctionInfo(iterExp);
if (funcInfo.funcId > FunctionId::None)
{
//函数直接入栈
operatorStack.push(funcInfo.funcId);
iterExp += funcInfo.bytesOfStr;
}
else if (IsOperator(*iterExp))
{
char ch = *iterExp;
if (ch == ')')
{
//遇到右括号,则弹出之前的运算符直到遇到左括号'(',并将计算数据保存到栈顶
while (!operatorStack.empty() && operatorStack.top() != '(')
{
char topOper = operatorStack.top();
if (topOper > FunctionId::None && topOper < FunctionId::FunctionMax)
{
//函数只有一个操作数,有多个操作数的函数不考虑
double topData = dataStack.top();
dataStack.top() = CalcFunctionValue(topData, (FunctionId)topOper);
}
else
{
//运算符有两个操作数,其他情况不考虑
double rightData = dataStack.top();
dataStack.pop();
double leftData = dataStack.top();
dataStack.top() = CalcOperatorValue(leftData, rightData, topOper);
}
operatorStack.pop();
}
operatorStack.pop();
if (!operatorStack.empty())
{
char topOper = operatorStack.top();//函数后跟的都是'(',所以要判断一下下一个运算符是不是函数
if (topOper > FunctionId::None && topOper < FunctionId::FunctionMax)
{
double topData = dataStack.top();
dataStack.top() = CalcFunctionValue(topData, (FunctionId)topOper);
operatorStack.pop();
}
}
}
else if (ch == '-' && *(iterExp - 1) == '(')
{
//'('后也可能跟负数,也当成"0-..."处理
//不考虑'-'前有空格的情况,可提前将表达式的空格去掉
dataStack.push(0);
operatorStack.push('-');
}
else if (operatorStack.empty() || ch == '(' || GetPriority(ch) > GetPriority(operatorStack.top()))
{
//这三种情况直接将运算符入栈
operatorStack.push(ch);
}
else
{
//优先级高于或等于ch优先级的出栈进行运算,最后将ch入栈
while (!operatorStack.empty() && GetPriority(ch) <= GetPriority(operatorStack.top()))
{
char topOper = operatorStack.top();
if (topOper > FunctionId::None && topOper < FunctionId::FunctionMax)
{
dataStack.top() = CalcFunctionValue(dataStack.top(), (FunctionId)topOper);
}
else
{
double rightData = dataStack.top();
dataStack.pop();
double leftData = dataStack.top();
dataStack.top() = CalcOperatorValue(leftData, rightData, topOper);
}
operatorStack.pop();
}
operatorStack.push(ch);
}
iterExp++;
}
else if (IsNumberOrDot(*iterExp))
{
//操作数入栈
char buf[20] = { 0 };
char* iterDest = buf;
while (IsNumberOrDot(*iterExp))
{
*iterDest++ = *iterExp++;
}
double opData = atof(buf);
dataStack.push(opData);
}
else
{
return 0;//表达式有误
}
}
//将操作符都出栈运算,这里不会有函数,因为函数后都跟'(',在'('出栈的时候已经计算了
while (!operatorStack.empty())
{
double rightData = dataStack.top();
dataStack.pop();
double leftData = dataStack.top();
dataStack.top() = CalcOperatorValue(leftData, rightData, operatorStack.top());
operatorStack.pop();
}
return dataStack.top();
}
2、后缀表达式计算
后缀表达式分为两步,先将中缀表达式转为后缀表达式,然后计算后缀表达式,运算符的处理上与中缀表达式有很多相似之处。
//InExpression为原表达式,OutSuffixExp为后缀表达式
bool ExpressionToSuffixExp(const char* InExpression, char* OutSuffixExp)
{
std::stack<char> operatorStack;//操作符栈,包括普通运算符和函数
const char* iterInfix = InExpression;
char* iterSuffix = OutSuffixExp;
if (*iterInfix == '-')//判断第一个符号是否为'-',如果是就按"0-..."处理
{
*iterSuffix++ = '0';
*iterSuffix++ = ' ';
operatorStack.push('-');
iterInfix++;
}
while (*iterInfix != '\0')
{
if (*iterInfix == ' ')
{
iterInfix++;
continue;
}
FunctionInfo funcInfo = GetFunctionInfo(iterInfix);
if (funcInfo.funcId > FunctionId::None)
{
operatorStack.push(funcInfo.funcId);
iterInfix += funcInfo.bytesOfStr;
}
else if (IsOperator(*iterInfix))
{
char ch = *iterInfix;
if (ch == ')')
{
//遇到右括号,则弹出之前的运算符直到遇到左括号'(',并将运算符加入到后缀表达式
while (!operatorStack.empty() && operatorStack.top() != '(')
{
*iterSuffix++ = operatorStack.top();
operatorStack.pop();
}
if (!operatorStack.empty())
{
operatorStack.pop();
}
}
else if (ch == '-' && *(iterInfix - 1) == '(')
{
//'('后也可能跟负数,也当成"0-..."处理
//不考虑'-'前有空格的情况,可提前将表达式的空格去掉
*iterSuffix++ = '0';
*iterSuffix++ = ' ';
operatorStack.push('-');
}
else if (operatorStack.empty() || ch == '(' || GetPriority(ch) > GetPriority(operatorStack.top()))
{
//这三种情况直接将运算符入栈
operatorStack.push(ch);
}
else
{
//优先级高于或等于ch优先级的出栈,并加入后缀表达式,最后将ch入栈
while (!operatorStack.empty() && GetPriority(ch) <= GetPriority(operatorStack.top()))
{
*iterSuffix++ = operatorStack.top();
operatorStack.pop();
}
operatorStack.push(ch);
}
iterInfix++;
}
else if (IsNumberOrDot(*iterInfix))
{
while (IsNumberOrDot(*iterInfix))
{
*iterSuffix++ = *iterInfix++;
}
*iterSuffix++ = ' ';//用空格分隔操作数
}
else
{
return false;//表达式有误
}
}
//将剩余操作符出栈加入后缀表达式
while (!operatorStack.empty())
{
*iterSuffix++ = operatorStack.top();
operatorStack.pop();
}
*iterSuffix = '\0';
return true;
}
double CalcSuffixValue(const char* InSuffixExp)
{
std::stack<double> dataStack;//操作数栈
const char* iterExp = InSuffixExp;
while (*iterExp != '\0')
{
if (*iterExp == ' ')
{
iterExp++;
continue;
}
//计算后缀表达式只需要区分操作数和操作符即可
if (IsNumberOrDot(*iterExp))
{
int t = 0;
char buf[20];
while (IsNumberOrDot(*iterExp))
{
buf[t++] = *iterExp++;
}
buf[t] = '\0';
double d = atof(buf);
dataStack.push(d);
}
else
{
if (*iterExp > FunctionId::None && *iterExp < FunctionId::FunctionMax)
{
double topData = dataStack.top();
dataStack.top() = CalcFunctionValue(topData, (FunctionId)* iterExp);
}
else
{
double rightData = dataStack.top();
dataStack.pop();
double leftData = dataStack.top();
dataStack.top() = CalcOperatorValue(leftData, rightData, *iterExp);
}
iterExp++;
}
}
if (dataStack.empty())
{
return 0;//表达式有误才会进到这里
}
return dataStack.top();
}
double CalcValueBySuffixExpression(const char* expression)
{
//分两步,先转为后缀表达式,后计算表达式
char suffixExp[100] = { 0 };
ExpressionToSuffixExp(expression, suffixExp);
return CalcSuffixValue(suffixExp);
}
由于算术运算符手动输入,会有各种各样的形式,甚至是输入错误的表达式,这里还有很多特殊情况没有考虑到,只处理一些比较简单的情况。另外如果要添加其他函数需在枚举(FunctionId)、全局数组g_functionInfos以及函数CalcFunctionValue中增加相关代码。
最后使用控制台程序测试。
int main()
{
char expression[100];
printf("请输入表达式:\n");
gets_s(expression, 100);
printf("表达式计算结果为:\n");
printf("CalcValueByInfixExpression:%f\n", CalcValueByInfixExpression(expression));
printf("CalcValueBySuffixExpression:%f\n", CalcValueBySuffixExpression(expression));
system("pause");
return 0;
}
输出结果: