前言
目前这个计算器还存在的问题:数字只能是单个整数、只能进行加减乘除、取余、乘方的操作!
计算器是什么?输入2+3X1,会得到5的结果。这也是在做字符串识别,同样涉及编译原理的知识。先算2+3,还是先算3X1,这需要判断优先级!
自定义栈
自定义的栈要求就是能放入和弹出自定义对象!用STL的容器也行!关键在于知道什么时候该释放内存,在哪释放内存,释放哪里的内存!
下面这个自定义栈使用的企业链表的方式构建的,每一个节点只有一个next指针,所以在站内部是访问不到对象的其他元素的!!!
企业链表就像挂衣服一样!自定义一个对象,其中包含一个LinkNode节点,在传递进来时,把整个对象类型转换成LinkNode*,拿出来时再转换成自定义的数据对象就行!在这个过程中,实例对象除了LinkNode的next节点,其他数据都被隐藏起来了。
//LinkStack.h
#pragma once
#include <iostream>
#include <string>
#include <cmath>
//用链表来实现栈
//节点
typedef struct LINKNODE {
struct LINKNODE* next;
}LinkNode;
//栈
typedef struct LINKSTACK {
LinkNode head;//头节点
int size;
}LinkStack;
//初始化
LinkStack* Init_LinkStack();
//入栈
void Push_LinkStack(LinkStack* stack, LinkNode* data);
//返回栈顶元素
LinkNode* Top_LinkStack(LinkStack* stack);
//出栈
void Pop_LinkStack(LinkStack* stack);
//返回栈元素个数
int Size_LinkStack(LinkStack* stack);
//是否为空
bool IsEmpty(LinkStack* stack);
//清空栈
void Clear_LinkStack(LinkStack* stack);
//销毁栈
void FreeSpace_LinkStack(LinkStack* stack);
//LinkStack.cpp
#include "LinkStack.h"
//初始化
LinkStack* Init_LinkStack() {
//LinkStack* stack = (LinkStack*)malloc(sizeof(LinkStack));
LinkStack* stack = new LinkStack;
stack->head.next = nullptr;//没有后续节点
stack->size = 0;//大小为0
return stack;
}
//入栈
void Push_LinkStack(LinkStack* stack, LinkNode* data) {
if (stack == nullptr||data==nullptr) {
return;
}
//把data节点接到头节点后面
data->next = stack->head.next;
stack->head.next = data;
stack->size++;
}
//返回栈顶元素
LinkNode* Top_LinkStack(LinkStack* stack) {
if (stack == nullptr || stack->size == 0) {
return nullptr;
}
return stack->head.next;//第一个有效节点
}
//出栈
void Pop_LinkStack(LinkStack* stack) {
if (stack == nullptr||stack->size==0) {
return;
}
//第一个有效节点
LinkNode* pNext = stack->head.next;
stack->head.next = pNext->next;
//删除该节点,数据内存在链表外面,所以内部操作不能删除内存
/*delete pNext;
pNext = nullptr;*/
stack->size--;
}
//返回栈元素个数
int Size_LinkStack(LinkStack* stack) {
if (stack == nullptr) {
return -1;
}
return stack->size;
}
//是否为空
bool IsEmpty(LinkStack* stack) {
if (stack == nullptr||stack->size==0) {
return true;
}
return false;
}
//清空栈
void Clear_LinkStack(LinkStack* stack) {
if (stack == nullptr) {
return;
}
//先拿到第一个有效节点
stack->head.next = nullptr;
stack->size = 0;
}
//销毁栈
void FreeSpace_LinkStack(LinkStack* stack) {
if (stack == nullptr) {
return;
}
//能回收的内存只能是stack这一个头节点
//free(stack);
stack->head.next = nullptr;
stack->size = 0;
delete stack;
stack = nullptr;
}
自定义数据结构
typedef struct MYCHAR {
LinkNode node;
char* p;
}MyChar;
中缀表达式转后缀表达式
中缀表达式就是我们写的计算式子,这种式子计算机没法认,所以需要转换成后缀表达式。像这样,5+4 => 5 4 +
1+2*3 => 1 2 3 * +
8+(3-1)*5 => 8 3 1 - 5 * +
如何得到后缀表达式?
遍历中缀表达式中的数字和符号
1,对于数字,直接输出
2,对于符号,
\;\;\;\; (1)如果是左括号,左括号进栈
\;\;\;\; (2)如果是运算符,运算符号与栈顶符号进行优先级比较
\; \; \;\;\;\;\;\; 1> 如果栈顶符号优先级低,此符号进栈
\; \; \; \;\;\;\;\;\; 2> 如果栈顶符号优先级不低,将栈顶符号弹出并输出,直到栈里没有元素或者栈顶符号 \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; 优先级低于此符号之后,此符号进栈
\;\;\;\; (3)如果是右括号,将栈顶符号弹出并输出,直到匹配到左括号
遍历结束后将栈中所有符号弹出并输出
^表示乘方操作、%表示MOD取余操作,因为这两个和加减乘除一样都是二元操作符,所以直接设定一下优先级,直接加入与加减乘除没有区别。
//main.cpp
//创建MyChar
MyChar* CreateMyChar(LinkStack* stack, char* p) {
MyChar* mychar = new MyChar;
mychar->p = p;
return mychar;
}
//判断是否是数字
bool IsNumber(char* p) {
return *p >= '0'&&*p <= '9';
}
//数字操作
void NumberOperate(char* p) {
std::cout << *p;
}
//判断是否是左括号
bool IsLeft(char* p) {
return *p == '(';
}
//左括号操作
void LeftOperate(LinkStack* stack, char* p) {
Push_LinkStack(stack, (LinkNode*)CreateMyChar(stack, p));
}
//判断是否是右括号
bool IsRight(char *p) {
return *p == ')';
}
//右括号操作
void RightOperate(LinkStack* stack, char* p) {
while (Size_LinkStack(stack) > 0) {
MyChar* mychar = (MyChar*)Top_LinkStack(stack);
//如果匹配到左括号
if (IsLeft(mychar->p)) {
Pop_LinkStack(stack);
break;
}
//输出并弹出
std::cout << *(mychar->p);
Pop_LinkStack(stack);
//释放内存
delete mychar;
}
}
//判断运算符
bool IsOperator(char* p) {
return *p == '+' || *p == '-' || *p == '*' || *p == '/'||*p=='%'||*p=='^';
}
//返回运算符优先级
int GetPriority(char* p) {
if (*p == '^') {
return 1;
}
else if (*p == '*' || *p == '/'||*p=='%') {
return 2;
}
else if (*p == '+' || *p == '-') {
return 3;
}
else if (*p=='('||*p==')') {
return 4;
}
return 5;
}
//操作符操作
void OpOperate(LinkStack* stack, char* p) {
if (stack == nullptr || p == nullptr) {
return;
}
//取出栈顶符号
MyChar* mychar = (MyChar*)Top_LinkStack(stack);
//如果栈里没有符号,直接入栈
if (mychar == nullptr) {
Push_LinkStack(stack, (LinkNode*)CreateMyChar(stack, p));
return;
}
while (Size_LinkStack(stack) > 0) {
mychar = (MyChar*)Top_LinkStack(stack);
if (GetPriority(mychar->p) > GetPriority(p)) { //如果优先级低
Push_LinkStack(stack, (LinkNode*)CreateMyChar(stack, p));
return;
}
//输出并弹出
std::cout << *(mychar->p);
Pop_LinkStack(stack);
//释放内存
delete mychar;
}
//没有遇到优先级低的,所以只有等到所有元素弹完了后,再把此符号压入栈
Push_LinkStack(stack, (LinkNode*)CreateMyChar(stack, p));
}
int main(){
char* str = (char*)"8+(3-1)*5";
std::cout << "需要计算的式子为:\n"<<str<<std::endl;
char* p = str;
//创建栈
LinkStack* stack = Init_LinkStack();
while (*p != '\0') {
if (IsNumber(p)) {
NumberOperate(p);
}
else if (IsLeft(p)) {
LeftOperate(stack, p);
}
else if (IsRight(p)) {
RightOperate(stack, p);
}
else if (IsOperator(p)) {
OpOperate(stack, p);
}
p++;
}
//最后把栈里所有符号弹出
while (Size_LinkStack(stack) > 0) {
MyChar* mychar = (MyChar*)Top_LinkStack(stack);
//输出并弹出
std::cout <<*(mychar->p);
Pop_LinkStack(stack);
//释放内存
delete mychar;
mychar = nullptr;
}
//清空栈
FreeSpace_LinkStack(stack);
system("pause");
}
后缀表达式求解
后缀表达式里只有运算符和数字!!!
遍历后缀表达式中的运算符和数字
1,对于数字,直接进栈
2,对于符号
\;\;\; (1)从栈中弹出右操作数
\;\;\; (2)从栈中弹出左操作数
\;\;\; (3)根据符号进行运算
\;\;\; (4)将这个结果再压入栈中
遍历结束,栈中唯一数字为计算结果!当遇到负数时,负数前面的符号不是二元的,是一元的!但是可以当做是二元的,其左操作数当做是零!!!所以在只有右操作数,没有左操作数时,就是遇到了一元运算符!!!
如果此时符号是减号,那么左操作数补个零就行。
如果此时符号是其他的一元操作符,需要另外考虑。
前面显示出后缀表达式但是没有接收,所以需要用一个string的遍历接收一下!
//main.cpp
//初始化后缀表达式的接收变量
std::string suffix_expr = "";
//在每一个输出字符的地方,换成拼接字符串
suffix_expr += *p;
//suffix_expr += *(mychar->p);
//main.cpp
typedef struct MYNUM {
LinkNode node;
double val;
}MyNum;
double Caculate(double leftNum, double rightNum, char* p) {
double ret = 0;
switch (*p) {
case '+'://加
ret = leftNum + rightNum;
break;
case '-'://减
ret = leftNum - rightNum;
break;
case '*'://乘
ret = leftNum * rightNum;
break;
case '%'://取余
ret = (int)leftNum % (int)rightNum;
break;
case '/'://除
if (rightNum == 0) {
std::cout << "除数不能为零!!!" << std::endl;
return -1;
}
ret = leftNum / rightNum;
break;
case '^'://乘方
ret = pow(leftNum, rightNum);
break;
default:
std::cout << "计算符号不对!!!" << std::endl;
return -1;
}
return ret;
}
int main(){
char ch='y';
do {
//初始化字符串
suffix_expr = "";
//char* str = (char*)"8+(3-1)*5";
char str[50];
std::cout << "需要计算的式子为:\n";
std::cin >> str;
//.....中间代码省略
//创建新栈
LinkStack* stack2 = Init_LinkStack();
//后缀表达式成功得到了
char* str2 = const_cast<char *>(suffix_expr.c_str());
std::cout << "式子的后缀表达式为:\n" << str2 << std::endl;
p = str2;
while (*p != '\0') {
if (IsNumber(p)) {
//数字直接入栈
MyNum* mynum = new MyNum;
mynum->val = *p - '0';//转换成数字
Push_LinkStack(stack2, (LinkNode*)mynum);
}
else if (IsOperator(p)) {
//先弹出右操作数
MyNum* right = ((MyNum*)Top_LinkStack(stack2));
double rightNum = right->val;
Pop_LinkStack(stack2);
delete right;
right = nullptr;
//取出左操作数
MyNum* left = ((MyNum*)Top_LinkStack(stack2));
double leftNum=0;//当栈为空时,即此运算符应该是一元运算符
if (left ) {//如果栈不为空
leftNum = left->val;
Pop_LinkStack(stack2);
delete left;
left = nullptr;
}
//根据操作符进行计算
MyNum* mynum = new MyNum;
mynum->val = Caculate(leftNum, rightNum, p);
//把计算结果再压入栈中
Push_LinkStack(stack2, (LinkNode*)mynum);
}
p++;
}
if (Size_LinkStack(stack2) == 1) {
MyNum* mynum = ((MyNum*)Top_LinkStack(stack2));
std::cout << "计算结果是:\n" << mynum->val << std::endl;
Pop_LinkStack(stack2);
delete mynum;
mynum = nullptr;
}
else {
std::cout << "栈中元素还有 " << Size_LinkStack(stack2)<<" 个"<<std::endl;
}
//释放栈
FreeSpace_LinkStack(stack2);
std::cout << "继续吗?Y/N" << std::endl;
std::cin >> ch;
}while (ch=='y'||ch=='Y');
system("pause");
}
另外,这个计算器只能算1+2*(7%2-5^2)/9这个样子的式子,遇到三角函数和对数没办法!
下面是实际效果: