前言
目前這個計算器還存在的問題:數字只能是單個整數、只能進行加減乘除、取餘、乘方的操作!
計算器是什麼?輸入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這個樣子的式子,遇到三角函數和對數沒辦法!
下面是實際效果: