1.實(shí)驗(yàn)內(nèi)容
(1)設(shè)計并完成一個完整的應(yīng)用程序,完成加減乘除模等運(yùn)算,功能多多益善。
(2)考核基本語法、判定語句、循環(huán)語句、邏輯運(yùn)算等知識點(diǎn)。
2. 實(shí)驗(yàn)過程及結(jié)果
(1)設(shè)計思路
該實(shí)驗(yàn)?zāi)繕?biāo)是制作一個計算器,能夠完成加減乘除、求余等運(yùn)算,我選擇參考手機(jī)系統(tǒng)自帶的計算器進(jìn)行實(shí)現(xiàn)。簡單的運(yùn)算能夠使用運(yùn)算符直接計算,開平方根等復(fù)雜運(yùn)算通過Python的標(biāo)準(zhǔn)庫math實(shí)現(xiàn)。另一方面,使用pyqt5實(shí)現(xiàn)計算器的UI界面,便于用戶使用和規(guī)范用戶輸入。輸入完成后,對字符串進(jìn)行識別并完成相應(yīng)的計算過程。在設(shè)計階段設(shè)想能否在用戶輸入的同時處理算式,簡化代碼,但由于要給用戶提供退格功能,無法實(shí)現(xiàn)這個設(shè)想。于是采用了大三在編譯原理課程上學(xué)到的知識:使用符號優(yōu)先表和雙棧算法來解決此問題。
(2)算法邏輯
雙棧算法,即“邊存邊看,邊走邊算”,設(shè)置符號棧和數(shù)字棧,符號棧中存儲運(yùn)算符,數(shù)字棧中存儲數(shù)字。同時設(shè)置符號優(yōu)先表,為所有可能出現(xiàn)的符號設(shè)置優(yōu)先級。程序從左到右掃描算式,遇到數(shù)字則將其壓進(jìn)數(shù)字棧;遇到符號則比較該符號與符號棧棧頂元素的優(yōu)先級,若當(dāng)前符號的優(yōu)先級高,則將其壓棧,否則取數(shù)字棧棧頂元素,與符號一起完成計算,并將結(jié)果壓入數(shù)字棧。迭代整個過程,直到算式被識別完畢。
(3)主要代碼介紹
部分代碼有參考。
報告中只貼出了部分代碼,全部代碼已上傳碼云:運(yùn)算部分,用戶界面部分(使用qt designer生成.ui文件,通過pyqt5庫將其轉(zhuǎn)換為.py文件)
計算函數(shù):
完成數(shù)字、運(yùn)算符彈棧后的計算操作,階乘、對數(shù)、開平方根等復(fù)雜運(yùn)算通過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
符號優(yōu)先級判斷函數(shù):
根據(jù)符號優(yōu)先表,對比判斷當(dāng)前符號與符號棧棧頂符號的優(yōu)先級,對數(shù)運(yùn)算與平方根運(yùn)算是一元運(yùn)算,因此比乘、除等二元運(yùn)算優(yōu)先級高。
def decision(last_op, new_op):
“”“
:param last_op: 運(yùn)算符棧的最后一個運(yùn)算符
:param new_op: 從算式列表取出的當(dāng)前運(yùn)算符
:return: 1 代表彈棧運(yùn)算,0 代表彈運(yùn)算符棧最后一個元素, -1 表示入棧
”“”
# 定義5種運(yùn)算符級別
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:
# 說明連續(xù)兩個運(yùn)算優(yōu)先級不一樣,需要入棧
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 # 只要棧頂元素為(,當(dāng)前元素不是)都應(yīng)入棧。
else:
return -1
過程函數(shù):
實(shí)現(xiàn)了雙棧算法的函數(shù),主要思路如上文所述。與二元運(yùn)算不同的是,對數(shù)運(yùn)算、平方根運(yùn)算等一元運(yùn)算只需要數(shù)字棧棧頂?shù)囊粋€元素即可。另外由于乘方運(yùn)算的特殊性,函數(shù)中代表乘方運(yùn)算的“!”符號一旦出現(xiàn),就與數(shù)字棧棧頂?shù)脑匾黄鹜瓿蛇\(yùn)算,并將結(jié)果壓入數(shù)字棧,即乘方運(yùn)算是不進(jìn)入符號棧的。
def final_calc(formula_list):
num_stack = [] # 數(shù)字棧
op_stack = [] # 運(yùn)算符棧
for e in formula_list:
operator = is_operator(e)
if not operator:
# 壓入數(shù)字棧
# 字符串轉(zhuǎn)換為符點(diǎn)數(shù)
num_stack.append(float(e))
else:
# 如果是運(yùn)算符
while True:
# 如果該運(yùn)算符是“!”,則不進(jìn)入符號棧,馬上算出數(shù)字棧棧頂元素的乘方,并將結(jié)果壓入數(shù)字棧
if e == ‘!’:
num1 = num_stack.pop()
num_stack.append(calculate(e, num1))
break
# 如果運(yùn)算符棧等于0無條件入棧
if len(op_stack) == 0:
op_stack.append(e)
break
# decision 函數(shù)做決策
tag = decision(op_stack[-1], e)
if tag == -1:
# 如果是-1壓入運(yùn)算符棧進(jìn)入下一次循環(huán)
op_stack.append(e)
break
elif tag == 0:
# 如果是0彈出運(yùn)算符棧內(nèi)最后一個(, 丟掉當(dāng)前),進(jìn)入下一次循環(huán)
op_stack.pop()
break
elif tag == 1:
# 如果是1彈出運(yùn)算符棧內(nèi)最后兩個元素,彈出數(shù)字棧最后兩位元素。
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()
# 執(zhí)行計算
# 計算之后壓入數(shù)字棧
num_stack.append(calculate(op, num1, num2))
# 處理大循環(huán)結(jié)束后 數(shù)字棧和運(yùn)算符棧中可能還有元素 的情況
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()
# 執(zhí)行計算
# 計算之后壓入數(shù)字棧
num_stack.append(calculate(op, num1, num2))
return num_stack, op_stack
用戶界面
使用pyqt5庫和qt designer,仿照手機(jī)計算器完成的用戶界面。
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):
# 設(shè)置輸出框游標(biāo)
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):
# 運(yùn)算
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中設(shè)計的.ui文件如下圖所示:
(4)程序結(jié)果
如下圖所示,圖片左邊為我的程序結(jié)果,右邊為手機(jī)計算器的運(yùn)算結(jié)果。從兩者對比來看,本程序的運(yùn)算能力有所保障。
3. 實(shí)驗(yàn)過程中遇到的問題和解決過程
問題1:輸出框不能自動滾動到最底部
問題1解決方案:在博客園找到了解決方案,雖然不完全貼合我的情況,但稍加改動后就實(shí)現(xiàn)了想要的效果。
問題2:不知道如何實(shí)現(xiàn)退格功能
問題2解決方案:在一位大佬的個人博客里找到了實(shí)現(xiàn)的方法,在文章大概中間的位置介紹了如何實(shí)現(xiàn)QTextEdit的兩種刪除操作。
問題3:在pyqt5啟動的用戶界面下,程序運(yùn)算出錯時進(jìn)程會直接崩潰退出,且不會在cmd中trace back,給debug帶來了很多麻煩。
問題3解決方案:不要直接運(yùn)行,而是使用調(diào)試模式,這樣出錯能夠看到trace back。這也提醒了我在調(diào)試程序階段不要直接運(yùn)行,使用調(diào)試模式更穩(wěn)妥。
-
程序設(shè)計
+關(guān)注
關(guān)注
3文章
261瀏覽量
30391 -
python
+關(guān)注
關(guān)注
56文章
4792瀏覽量
84627
發(fā)布評論請先 登錄
相關(guān)推薦
評論