1.實驗內容
(1)設計并完成一個完整的應用程序,完成加減乘除模等運算,功能多多益善。
(2)考核基本語法、判定語句、循環語句、邏輯運算等知識點。
2. 實驗過程及結果
(1)設計思路
該實驗目標是制作一個計算器,能夠完成加減乘除、求余等運算,我選擇參考手機系統自帶的計算器進行實現。簡單的運算能夠使用運算符直接計算,開平方根等復雜運算通過Python的標準庫math實現。另一方面,使用pyqt5實現計算器的UI界面,便于用戶使用和規范用戶輸入。輸入完成后,對字符串進行識別并完成相應的計算過程。在設計階段設想能否在用戶輸入的同時處理算式,簡化代碼,但由于要給用戶提供退格功能,無法實現這個設想。于是采用了大三在編譯原理課程上學到的知識:使用符號優先表和雙棧算法來解決此問題。
(2)算法邏輯
雙棧算法,即“邊存邊看,邊走邊算”,設置符號棧和數字棧,符號棧中存儲運算符,數字棧中存儲數字。同時設置符號優先表,為所有可能出現的符號設置優先級。程序從左到右掃描算式,遇到數字則將其壓進數字棧;遇到符號則比較該符號與符號棧棧頂元素的優先級,若當前符號的優先級高,則將其壓棧,否則取數字棧棧頂元素,與符號一起完成計算,并將結果壓入數字棧。迭代整個過程,直到算式被識別完畢。
(3)主要代碼介紹
部分代碼有參考。
報告中只貼出了部分代碼,全部代碼已上傳碼云:運算部分,用戶界面部分(使用qt designer生成.ui文件,通過pyqt5庫將其轉換為.py文件)
計算函數:
完成數字、運算符彈棧后的計算操作,階乘、對數、開平方根等復雜運算通過math庫完成
import math
def calculate(operator, n1, n2=0):
“”“
:param n1: float
:param n2: float
:param operator: + - * / ^ % ! lg ln √
:return: float
”“”
if operator == “+”:
return n1 + n2
if operator == “-”:
return n1 - n2
if operator == “*”:
return n1 * n2
if operator == “/”:
return n1 / n2
if operator == ‘^’:
return n1 ** n2
if operator == ‘%’:
return n1 % n2
if operator == ‘!’:
return math.factorial(n1)
if operator == ‘lg’:
return math.log10(n1)
if operator == ‘ln’:
return math.log(n1)
if operator == ‘√’:
return math.sqrt(n1)
return 0
符號優先級判斷函數:
根據符號優先表,對比判斷當前符號與符號棧棧頂符號的優先級,對數運算與平方根運算是一元運算,因此比乘、除等二元運算優先級高。
def decision(last_op, new_op):
“”“
:param last_op: 運算符棧的最后一個運算符
:param new_op: 從算式列表取出的當前運算符
:return: 1 代表彈棧運算,0 代表彈運算符棧最后一個元素, -1 表示入棧
”“”
# 定義5種運算符級別
grade1 = [‘+’, ‘-’]
grade2 = [‘*’, ‘/’, ‘%’, ‘!’, ‘^’]
grade3 = [‘lg’, ‘ln’, ‘√’]
grade4 = [‘(’]
grade5 = [‘)’]
if last_op in grade1:
if new_op in grade2 or new_op in grade3 or new_op in grade4:
# 說明連續兩個運算優先級不一樣,需要入棧
return -1
else:
return 1
elif last_op in grade2:
if new_op in grade3 or new_op in grade4:
return -1
else:
return 1
elif last_op in grade3:
if new_op in grade4:
return -1
else:
return 1
elif last_op in grade4:
if new_op in grade5:
return 0 # ( 遇上 ) 需要彈出 (,丟掉 )
else:
return -1 # 只要棧頂元素為(,當前元素不是)都應入棧。
else:
return -1
過程函數:
實現了雙棧算法的函數,主要思路如上文所述。與二元運算不同的是,對數運算、平方根運算等一元運算只需要數字棧棧頂的一個元素即可。另外由于乘方運算的特殊性,函數中代表乘方運算的“!”符號一旦出現,就與數字棧棧頂的元素一起完成運算,并將結果壓入數字棧,即乘方運算是不進入符號棧的。
def final_calc(formula_list):
num_stack = [] # 數字棧
op_stack = [] # 運算符棧
for e in formula_list:
operator = is_operator(e)
if not operator:
# 壓入數字棧
# 字符串轉換為符點數
num_stack.append(float(e))
else:
# 如果是運算符
while True:
# 如果該運算符是“!”,則不進入符號棧,馬上算出數字棧棧頂元素的乘方,并將結果壓入數字棧
if e == ‘!’:
num1 = num_stack.pop()
num_stack.append(calculate(e, num1))
break
# 如果運算符棧等于0無條件入棧
if len(op_stack) == 0:
op_stack.append(e)
break
# decision 函數做決策
tag = decision(op_stack[-1], e)
if tag == -1:
# 如果是-1壓入運算符棧進入下一次循環
op_stack.append(e)
break
elif tag == 0:
# 如果是0彈出運算符棧內最后一個(, 丟掉當前),進入下一次循環
op_stack.pop()
break
elif tag == 1:
# 如果是1彈出運算符棧內最后兩個元素,彈出數字棧最后兩位元素。
op = op_stack.pop()
if op == ‘lg’ or op == ‘ln’ or op == ‘√’:
num1 = num_stack.pop()
num_stack.append(calculate(op, num1))
else:
num2 = num_stack.pop()
num1 = num_stack.pop()
# 執行計算
# 計算之后壓入數字棧
num_stack.append(calculate(op, num1, num2))
# 處理大循環結束后 數字棧和運算符棧中可能還有元素 的情況
while len(op_stack) != 0:
op = op_stack.pop()
if op == ‘lg’ or op == ‘ln’ or op == ‘√’:
num1 = num_stack.pop()
num_stack.append(calculate(op, num1))
else:
num2 = num_stack.pop()
num1 = num_stack.pop()
# 執行計算
# 計算之后壓入數字棧
num_stack.append(calculate(op, num1, num2))
return num_stack, op_stack
用戶界面
使用pyqt5庫和qt designer,仿照手機計算器完成的用戶界面。
class MyWindows(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
# noinspection PyArgumentList
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.e = str(math.e)
self.pi = str(math.pi)
self.zero_button.clicked.connect(lambda: self.get_formula(‘0’))
self.one_button.clicked.connect(lambda: self.get_formula(‘1’))
self.two_button.clicked.connect(lambda: self.get_formula(‘2’))
# 共29個按鍵,隱藏部分相似代碼,全部代碼見上文中的碼云鏈接
self.clear.clicked.connect(self.clean_screen)
self.back_space.clicked.connect(self.delete_char)
self.equa_button.clicked.connect(self.get_result)
self.formula = ‘’
def set_cursor(self):
# 設置輸出框游標
self.showout.ensureCursorVisible()
cursor = self.showout.textCursor()
pos = len(self.showout.toPlainText())
cursor.setPosition(pos)
self.showout.setTextCursor(cursor)
def get_formula(self, param):
# 獲取輸入
if param == ‘lg’ or param == ‘ln’:
temp_param = param + ‘(’
elif param == ‘re’:
temp_param = ‘^(-1)’
else:
temp_param = param
self.formula += temp_param
self.showout.insertPlainText(temp_param)
self.set_cursor()
# print(self.formula)
def clean_screen(self):
# 清空輸出框
self.formula = ‘’
self.showout.clear()
self.set_cursor()
def delete_char(self):
# 退格
self.showout.ensureCursorVisible()
cursor = self.showout.textCursor()
cursor.deletePreviousChar()
self.formula = self.formula[:-1]
def get_result(self):
# 運算
formula_list = formula_format(self.formula)
# print(formula_list)
result, _ = final_calc(formula_list)
# print(result[0])
show_result = ‘=’ + str(result[0])
self.showout.append(show_result)
self.formula = ‘’
self.showout.insertPlainText(‘\n’)
self.set_cursor()
if __name__ == ‘__main__’:
app = QtWidgets.QApplication(sys.argv)
main_window = MyWindows()
main_window.setFixedSize(main_window.width(), main_window.height())
main_window.show()
sys.exit(app.exec_())
在qt designer中設計的.ui文件如下圖所示:
(4)程序結果
如下圖所示,圖片左邊為我的程序結果,右邊為手機計算器的運算結果。從兩者對比來看,本程序的運算能力有所保障。
3. 實驗過程中遇到的問題和解決過程
問題1:輸出框不能自動滾動到最底部
問題1解決方案:在博客園找到了解決方案,雖然不完全貼合我的情況,但稍加改動后就實現了想要的效果。
問題2:不知道如何實現退格功能
問題2解決方案:在一位大佬的個人博客里找到了實現的方法,在文章大概中間的位置介紹了如何實現QTextEdit的兩種刪除操作。
問題3:在pyqt5啟動的用戶界面下,程序運算出錯時進程會直接崩潰退出,且不會在cmd中trace back,給debug帶來了很多麻煩。
問題3解決方案:不要直接運行,而是使用調試模式,這樣出錯能夠看到trace back。這也提醒了我在調試程序階段不要直接運行,使用調試模式更穩妥。
-
程序設計
+關注
關注
3文章
261瀏覽量
30421 -
python
+關注
關注
56文章
4802瀏覽量
84889
發布評論請先 登錄
相關推薦
評論