一、思路概要和知識回顧
1.思路概要
①中綴表達式計算:
需要處理運算符的優先級,可能需要用到棧結構。
??如何將中綴表達式轉換為后綴表達式?或者直接計算?
通常,中綴轉后綴(逆波蘭式)再用后綴計算比較方便。可以直接用兩個棧,一個操作數棧,一個運算符棧,邊轉換邊計算。比如Shunting-yard算法。可能后者更適合,因為可以邊處理邊顯示步驟,方便動畫演示。
??數據結構的選擇: 棧?可以用簡單的數組實現
操作數棧:存放double類型.
運算符棧:存放字符。
然后需要處理運算符的優先級,比如加減是1,乘除是2,可能需要一個函數來返回優先級值。
②用戶輸入:
需要讀取表達式,處理可能的錯誤輸入。
用fgets讀取輸入的表達式,可能包含空格,需要處理掉空格。同時,要考慮多位數和小數點的處理,比如遇到連續的數字字符需要合并成一個數。
③動畫演示:
需要逐步顯示計算過程,比如顯示棧的狀態,當前處理的字符等。
動畫演示的話,需要在每一步處理時輸出當前的狀態,比如顯示操作數棧和運算符棧的內容,當前處理的字符,以及進行的操作,比如入棧、出棧計算等。可能需要用一些控制臺輸出技巧,比如清屏、延遲,讓用戶能看到過程。
可能還需要處理括號的情況,因為括號會影響運算順序。所以在處理運算符的時候,遇到左括號入棧,遇到右括號則彈出直到左括號,同時處理運算.
具體實現步驟:
定義棧結構。操作數棧和運算符棧。每個棧需要有數組,棧頂指針。然后實現棧的基本操作:初始化、入棧、出棧、查看棧頂等。
然后,處理輸入的表達式。可能需要過濾空格,將表達式中的空格去掉。
接下來,逐個字符處理:
如果是數字或者小數點,開始收集整個數字,直到遇到非數字或小數點,然后將該數字字符串轉換為double,壓入操作數棧。同時,在動畫中顯示這一步。
如果是運算符或括號,則比較當前運算符與棧頂運算符的優先級。如果當前運算符優先級小于等于棧頂的,則彈出棧頂運算符,從操作數棧彈出兩個操作數進行計算,結果壓回操作數棧。重復直到棧頂優先級低于當前運算符,然后將當前運算符壓棧。左括號直接壓棧,遇到右括號則彈出直到左括號,并處理運算。
在每一步操作時,顯示當前處理的位置,棧的狀態,以及進行的操作。例如,顯示“當前字符:+”,操作數棧的內容,運算符棧的內容,然后說明正在將+入棧,或者進行計算等。
關于動畫的步驟顯示,可以在每次處理一個字符后,暫停一下,輸出當前狀態。例如,每次處理完一個操作符或數字后,打印兩個棧的狀態,然后等待用戶按回車繼續,或者自動延遲。
代碼結構的大致框架:
- 主函數讀取輸入,并處理每個字符。
- 處理數字的函數,收集數字字符串。
- 處理運算符的函數,處理棧的操作。
- 計算函數,執行具體的運算,并處理錯誤如除以零。
- 顯示棧狀態的函數,用于動畫輸出。
可能的錯誤處理:
- 除以零的情況,需要提示錯誤并退出。
- 括號不匹配的情況。
- 無效字符的輸入。
2.知識回顧
二、具體實現步驟
1.定義棧結構
操作數棧和運算符棧。每個棧需要有數組,棧頂指針。然后實現棧的基本操作:初始化、入棧、出棧、查看棧頂等。
操作數棧:用于存儲計算過程中涉及的數值,當遇到運算符時,會從操作數棧中彈出相應數量的操作數進行計算,并將計算結果重新壓入操作數棧中。
運算符棧:用于存儲遇到的運算符,依據運算符的優先級來決定何時執行相應的計算操作。當遇到優先級低或相等的運算符時,會彈出棧中的運算符進行計算,直到棧頂運算符的優先級低于當前運算符。
①操作數棧:
數據存儲 :一個固定大小的數組(data
):用于存放棧中的元素。
? ? ? ? ? ? ? ? ? ?數組類型為 double:
可存儲浮點數操作數
棧頂指針 :top
用于指示當前棧頂的位置。初始時 top
設為 -1,表示棧為空;每當有元素入棧? ? ? ? ? ? ? ? ? ? ? ? ? 時,top
增 1,指向新的棧頂;彈出元素時,top
減 1,指向新的棧頂。??
②運算符棧:
數據存儲 :一個固定大小的數組(data
),用于存放棧中的元素。
? ? ? ? ? ? ? ? ? ?運算符棧的數組類型為 char
,用于存儲運算符字符。
棧頂指針 :top
用于指示當前棧頂的位置。初始時 top
設為 -1,表示棧為空;每當有元素入棧? ? ? ? ? ? ? ? ? ? ? ? ?時,top
增 1,指向新的棧頂;彈出元素時,top
減 1,指向新的棧頂。
2.初始化棧
棧初始化的核心是設置棧頂指針的初始狀態為 -1,這是棧為空的標志。這樣,在后續的操作中,可以通過判斷棧頂指針是否為 -1 來確定棧是否為空,從而避免非法的出棧操作。
3.向|操作數棧|中壓入數值元素函數
參數檢查 :首先判斷棧頂指針 s->top
是否已到達棧的最大容量減一(MAX_SIZE - 1
)。如果是,則說明棧已滿,無法再壓入新的元素,函數返回 false
,表示壓棧失敗。
元素入棧 :如果棧未滿,則將棧頂指針加 1(++s->top
),并將傳入的數值 val
存入棧頂位置(s->data[s->top]
)。
返回成功 :壓棧成功后,函數返回 true
。
4.向|運算符棧|中壓入元素的函數
參數檢查 :首先判斷棧頂指針 s->top
是否已到達棧的最大容量減一(MAX_SIZE - 1
)。如果是,則說明棧已滿,無法再壓入新的元素,函數返回 false
,表示壓棧失敗。
元素入棧 :如果棧未滿,則將棧頂指針加 1(++s->top
),并將傳入的數值 val
存入棧頂位置(s->data[s->top]
)。
返回成功 :壓棧成功后,函數返回 true
。
5.彈出|操作數棧|棧頂元素函數
檢查棧頂指針是否為 -1。如果棧頂指針是 -1,說明棧為空,此時無法彈出元素,函數返回 0.0。
如果棧不為空,這行代碼將棧頂指針減 1(s->top--
),并返回原來棧頂位置的數值(s->data[s->top]
)。
在完整代碼中的作用:
支持中綴表達式計算: 在處理中綴表達式時,需要從操作數棧中彈出數值進行計算。例如,當遇到運算符時,會從操作數棧中彈出兩個數值,進行相應的計算,并將結果重新壓入棧中。
動態數據管理: 允許在程序運行過程中動態地從棧中移除數值,使得棧能夠根據計算的需要調整存儲的內容,實現了靈活的數據管理。
6.彈出|運算符棧|中棧頂元素函數
檢查棧頂指針是否為 -1。如果棧頂指針是 -1,說明棧為空,此時無法彈出元素,函數返回空字符 \0
。
如果棧不為空,這行代碼將棧頂指針減 1(s->top--
),并返回原來棧頂位置的運算符(s->data[s->top]
)。
在完整代碼中的作用
支持中綴表達式計算: 在處理中綴表達式時,需要從運算符棧中彈出運算符進行計算。例如,當遇到優先級較低或相等的運算符時,會從運算符棧中彈出棧頂的運算符,獲取該運算符后進行相應的計算操作。
動態數據管理: 允許在程序運行過程中動態地從棧中移除運算符,使得棧能夠根據計算的需要調整存儲的內容,實現了靈活的數據管理。
7.獲取棧頂函數
①獲取棧頂元素函數的作用
-
top_num
函數:-
功能 :返回操作數棧棧頂的數值,但不改變棧的結構(即棧頂指針不變,元素不被移除)。
-
實現邏輯 :通過條件運算符判斷棧是否為空。如果棧為空(
s->top == -1
),返回 0.0;否則,返回棧頂位置的數值(s->data[s->top]
)。
-
-
top_op
函數:-
功能 :返回運算符棧棧頂的運算符,同樣不改變棧的結構。
-
實現邏輯 :同樣使用條件運算符判斷棧是否為空。如果棧為空,返回空字符
\0
;否則,返回棧頂位置的運算符(s->data[s->top]
)。
-
②與彈出棧頂元素函數的區別
-
pop_num
和pop_op
函數:-
功能 :不僅獲取棧頂元素,還會將棧頂元素從棧中移除,并將棧頂指針減 1。
-
應用場景 :當你需要使用棧頂元素并將其從棧中移除時,使用
pop
函數。例如,在進行數值計算時,需要從操作數棧中彈出兩個數值進行計算;在處理運算符優先級時,需要從運算符棧中彈出運算符進行比較或計算。
-
③為什么需要兩種操作?
獲取棧頂元素(top 函數):
應用場景 :當你需要查看棧頂元素但不想改變棧的結構時使用。例如,在比較運算符優先級時,需要查看運算符棧棧頂的運算符,但不應將其移除。
彈出棧頂元素(pop 函數):
應用場景 :當你需要使用棧頂元素并將其從棧中移除時使用。例如,在進行數值計算時,彈出操作數棧中的數值進行計算;在處理完一個運算符后,將其從運算符棧中移除。
④協同工作實現棧頂元素的利用
獲取棧頂元素和彈出棧頂元素的操作是相輔相成的:
-
獲取棧頂元素: 允許你在不破壞棧結構的情況下查看棧頂元素,用于決策(如比較運算符優先級)。
-
彈出棧頂元素: 允許你在需要時移除并使用棧頂元素,用于實際的操作(如數值計算)。
8. 判斷棧空函數
①函數功能
-
is_empty_op
函數:-
功能 :判斷運算符棧是否為空。
-
實現邏輯 :檢查運算符棧的棧頂指針
s->top
是否等于 -1。如果等于 -1,說明棧為空,返回true
;否則,返回false
。
-
-
is_empty_num
函數:-
功能 :判斷操作數棧是否為空。
-
實現邏輯 :檢查操作數棧的棧頂指針
s->top
是否等于 -1。如果等于 -1,說明棧為空,返回true
;否則,返回false
。
-
②在完整代碼中的作用
-
支持中綴表達式計算: 在處理中綴表達式時,需要頻繁地檢查棧的狀態。例如,在彈出元素之前,需要確保棧不為空,以避免非法操作。通過調用這兩個函數,可以在執行彈出操作或訪問棧頂元素之前,確保棧處于有效狀態。
-
提供棧狀態檢查手段: 這兩個函數為程序提供了簡單直接的棧狀態檢查手段。通過返回布爾值,程序可以輕松地根據棧是否為空來決定后續的操作流程。
9.優先級函數
如果傳入的運算符是加號 '+'
或減號 '-'
,則返回優先級 1。
如果傳入的運算符是乘號 '*'
或除號 '/'
,則返回優先級 2。
如果傳入的字符不是上述運算符,則返回優先級 0,表示該字符不具有運算符優先級。
在處理中綴表達式時,需要根據運算符的優先級來決定計算的順序。優先級高的運算符會先進行計算。通過調用 get_priority
函數,可以獲取當前運算符的優先級,并與棧頂運算符的優先級進行比較,從而決定是直接壓入棧還是先進行計算
10.清屏函數
?如果代碼在 Windows 系統上運行,這行代碼會執行系統命令 "cls"
來清除屏幕內容。
如果代碼在非 Windows 系統(如 Linux 或 macOS)上運行,這行代碼會執行系統命令 "clear"
來清除屏幕內容。
在完整代碼中的作用
-
提升用戶體驗: 在處理中綴表達式時,每次顯示棧狀態之前調用
clear_screen
函數,可以清除之前的顯示內容,為用戶呈現一個整潔的界面,避免屏幕內容過于雜亂。 -
保持界面整潔: 通過清屏操作,程序可以在每次顯示新的棧狀態時,只展示當前的相關信息,而不是在之前內容的基礎上追加顯示。這樣可以使界面更加簡潔,信息更加清晰。
11.延時函數
?
如果代碼在 Windows 系統上運行,這行代碼會調用 Windows API 的 Sleep
函數來實現延時,Sleep
函數的參數是以毫秒為單位的延時時間。
如果代碼在非 Windows 系統(如 Linux 或 macOS)上運行,這行代碼會調用 POSIX 的 usleep
函數來實現延時,usleep
函數的參數是以微秒為單位的延時時間,因此需要將傳入的毫秒值乘以 1000 進行轉換。
在完整代碼中的作用
-
控制顯示節奏: 在處理中綴表達式時,每次顯示棧狀態之后調用
delay
函數,可以暫停程序的執行,使用戶有時間查看當前的棧狀態,從而更好地理解程序的執行過程。 -
提升用戶體驗: 通過延時操作,程序可以在每次顯示新的棧狀態時,以合適的節奏展示信息,避免內容切換過快導致用戶無法看清。
?
12.顯示棧函數
?
13.執行計算函數?
?
-
如果運算符是
'+'
,則返回兩個操作數a
和b
的和。
?
?
如果運算符是 '-'
,則返回兩個操作數 a
和 b
的差。?
?
如果運算符是 '*'
,則返回兩個操作數 a
和 b
的積。?
?
-
如果運算符是
'/'
,則首先檢查除數b
是否為零。如果是零,打印錯誤信息并退出程序;否則,返回兩個操作數a
和b
的商。
?
?如果傳入的運算符不屬于上述四種基本運算符之一,則返回 0。這通常表示傳入了不支持的運算符。
在完整代碼中的作用
-
執行計算: 在處理中綴表達式時,當遇到運算符需要進行計算時,會調用
calculate
函數。該函數根據運算符和彈出的操作數執行相應的計算,并返回結果,結果隨后會被壓入操作數棧。 -
錯誤處理: 在進行除法運算時,該函數會檢查除數是否為零,從而避免程序因除以零而崩潰。如果檢測到除數為零,程序會打印錯誤信息并安全退出。
14.處理表達式函數
①初始化棧?
初始化操作數棧 num_stack
和運算符棧 op_stack
,將它們的棧頂指針設置為 -1,表示棧為空。?
②循環處理表達式
使用一個 while
循環遍歷輸入的表達式字符串,直到遇到字符串結束符 \0
。
③跳過空格?
如果當前字符是空格,則跳過該字符,繼續處理下一個字符。
④顯示當前棧狀態?
調用 display_stacks
函數顯示當前的操作數棧和運算符棧狀態,以及當前處理的字符。?
⑤?處理數字
判斷是否為數字或小數點:
isdigit
是庫函數 :isdigit
是 C 標準庫 <ctype.h>
中的函數,用于判斷一個字符是否是數字('0'-'9')。如果是數字字符,返回一個非零值(通常為 true
);否則返回 0(false
)。
判斷邏輯 :這行代碼檢查當前字符 expr[i]
是否是數字或者是否為小數點(.
)。如果是其中之一,則表示這是一個數字的開始,需要進行數字處理。
?
處理數字字符串:?
?
定義數字字符串緩沖區 :num_str
用于暫時存儲從表達式中提取的完整數字字符串,包括整數部分和小數部分。長度設為 32,足夠存儲大多數常見的數字。
初始化索引變量 :j
用于在 num_str
中存儲字符時的索引定位,初始值為 0。
?
循環提取數字字符 :這個 while
循環會一直執行,只要當前字符 expr[i]
是數字或者小數點。
存儲數字字符 :在循環中,將當前字符 expr[i]
存入 num_str
的第 j
個位置,然后 j
自增 1,以便下一個字符存儲在正確的位置。同時,i
自增 1,移動到下一個字符。
?
?
終止字符串 :在提取完數字的所有字符后,在 num_str
的末尾添加空字符 \0
,將其轉換為一個有效的 C 字符串。這一步是必要的,因為許多字符串處理函數都需要依賴空字符來確定字符串的結束位置。
?
轉換為浮點數 :atof
是 C 標準庫 <stdlib.h>
中的函數,用于將字符串轉換為一個雙精度浮點數(double
)。這里將 num_str
轉換為浮點數。
壓入操作數棧 :調用 push_num
函數,將轉換后的浮點數壓入操作數棧 num_stack
中,以便后續進行計算。
?
?
索引糾正 :因為在 while
循環中處理完最后一個數字字符后,i
又被自增了一次,所以這里要將 i
減 1,使它指向數字的最后一個字符,以便在下一次循環中正確處理后續字符。
⑥處理左括號
?
如果當前字符是左括號 '('
,則直接壓入運算符棧。
⑦處理右括號?
彈出運算符并執行計算:
?
循環條件 :檢查運算符棧的棧頂元素是否為左括號 '('
。如果不是,則進入循環體。
top_op
函數 :這是之前定義的函數,用于獲取運算符棧的棧頂元素,但不彈出該元素。它返回棧頂的運算符字符。
?
彈出運算符 :調用 pop_op
函數彈出運算符棧的棧頂運算符,并將其存儲在變量 op
中。
pop_op
函數 :之前定義的函數,用于彈出運算符棧的棧頂元素并返回該元素。
?
?
彈出操作數 :依次彈出操作數棧的棧頂元素,分別存儲在變量 b
和 a
中。這里假設 b
是第二個操作數,a
是第一個操作數。
?pop_num
函數 :之前定義的函數,用于彈出操作數棧的棧頂元素并返回該元素。
?
執行計算并壓入結果 :調用 calculate
函數對彈出的兩個操作數和運算符進行計算,得到結果后,將結果壓入操作數棧。
push_num
函數 :之前定義的函數,用于將數值壓入操作數棧。
calculate
函數 :之前定義的函數,用于執行具體的算術運算。
?
?顯示棧狀態 :調用 display_stacks
函數顯示當前的操作數棧和運算符棧狀態,以及當前處理的運算符。
display_stacks
函數 :之前定義的函數,用于顯示棧狀態。
彈出左括號:
?
顯示棧狀態 :在處理完右括號內的所有運算符后,再次顯示棧狀態,以便用戶可以看到當前的棧情況。
?
彈出左括號 :調用 pop_op
函數彈出運算符棧的棧頂元素(左括號 '('
),但不進行任何計算。
?pop_op
函數 :之前定義的函數,用于彈出運算符棧的棧頂元素并返回該元素。
⑧處理運算符
檢查是否為運算符:
?
如果當前字符不是數字、小數點或括號,則進入此分支處理運算符。
循環處理運算符優先級:
?
is_empty_op
函數 :檢查運算符棧是否為空。
get_priority
函數 :獲取當前運算符和棧頂運算符的優先級。
?循環條件 :如果運算符棧不為空且當前運算符的優先級小于或等于棧頂運算符的優先級,則進入循環體。
彈出運算符和操作數并計算:
?pop_op
函數 :彈出運算符棧的棧頂運算符。
pop_num
函數 :依次彈出操作數棧的棧頂元素,b
是第二個操作數,a
是第一個操作數。
?
calculate
函數 :對 a
和 b
執行 op
運算,并返回結果。
push_num
函數 :將計算結果壓入操作數棧。
?
顯示棧狀態:
display_stacks
函數 :顯示當前操作數棧和運算符棧的狀態。
壓入當前運算符:
?
?
push_op
函數 :將當前運算符壓入運算符棧。
15.清理輸入緩沖區函數
-
聲明變量
c
: 用于存儲從標準輸入讀取的字符。 -
while
循環: 使用getchar()
函數從標準輸入讀取字符,并將其存儲在變量c
中。循環會一直執行,直到讀取到換行符'\n'
或文件結束標志EOF
。
作用:
在程序中,當使用 fgets
或其他輸入函數讀取用戶輸入時,輸入緩沖區中可能會殘留一些未處理的字符(例如多余的換行符或其他輸入)。這些殘留字符可能會影響后續的輸入操作。clean_stdin
函數的作用就是清除這些殘留字符,確保后續的輸入操作能夠正確進行。
16.獲取用戶輸入函數?
①無限循環?
使用 while (1)
創建一個無限循環,直到用戶輸入有效選擇(Y 或 N)為止。?
②提示用戶輸入?
printf
函數 :輸出提示信息,詢問用戶是否繼續。
fflush(stdout)
:刷新標準輸出緩沖區,確保提示信息立即顯示。
③讀取用戶輸入
fgets
函數 :從標準輸入讀取用戶輸入,最多讀取 sizeof(input) - 1
個字符(這里為 3 個字符),并自動在末尾添加空字符 \0
。
輸入緩沖區 :input
數組的大小為 4,可以存儲最多 3 個字符加上空字符。
錯誤檢查 :如果 fgets
返回 NULL
,表示讀取輸入失敗,函數返回 false
。
?
?④處理過長輸入
strchr
函數 :檢查 input
中是否包含換行符 \n
。如果不包含,說明用戶輸入過長,超出 input
的容量。
clean_stdin
函數 :清空標準輸入緩沖區,避免多余的輸入字符影響后續操作。
⑤轉換并檢查用戶選擇?
?
tolower
函數 :將用戶輸入的首字符轉換為小寫,以便大小寫不敏感地檢查輸入。
檢查選擇 :如果轉換后的字符是 'y'
,返回 true
;如果是 'n'
,返回 false
。
⑥無效輸入提示?
如果用戶輸入的不是 Y 或 N(大小寫不敏感),輸出提示信息,要求用戶重新輸入。
?
?17.主函數邏輯
①開始 do-while
循環
使用 do-while
循環確保程序至少運行一次,允許用戶多次輸入表達式進行計算。
②定義表達式緩沖區
?定義一個字符數組 expr
,用于存儲用戶輸入的中綴表達式,最大長度為 MAX_SIZE
。
③提示用戶輸入
輸出提示信息,告知用戶可以輸入支持 +-*/
和括號的中綴表達式。
④讀取用戶輸入
?
fgets
函數 :從標準輸入讀取用戶輸入的字符串,最多讀取 MAX_SIZE - 1
個字符,并自動在末尾添加空字符 \0
。
錯誤檢查 :如果 fgets
返回 NULL
,表示讀取輸入失敗,跳出循環。
?
⑤去除換行符
strcspn
函數 :計算 expr
中不包含換行符 \n
的字符數,返回該數作為索引。
去除換行符 :將換行符替換為空字符 \0
,確保輸入字符串正確結束。
?
⑥檢查輸入是否為空
strlen
函數 :檢查輸入字符串的長度。
提示信息 :如果輸入為空,輸出提示信息,并跳過本次循環的剩余部分,重新開始循環。
?
⑦處理表達式
調用 process_expression
函數處理用戶輸入的中綴表達式。
⑧檢查是否繼續
?
get_continue_choice
函數 :調用該函數詢問用戶是否繼續運行程序。
循環條件 :如果用戶選擇繼續(返回 true
),循環繼續;否則,循環結束。
三、完整代碼
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdbool.h>#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif#define MAX_SIZE 100// 操作數棧結構
typedef struct
{double data[MAX_SIZE];int top;
} NumStack;// 運算符棧結構
typedef struct
{char data[MAX_SIZE];int top;
} OpStack;// 初始化棧
void init_num_stack(NumStack* s) { s->top = -1; }
void init_op_stack(OpStack* s) { s->top = -1; }// 棧操作函數
bool push_num(NumStack* s, double val)
{if (s->top >= MAX_SIZE - 1) return false;s->data[++s->top] = val;return true;
}bool push_op(OpStack* s, char op)
{if (s->top >= MAX_SIZE - 1) return false;s->data[++s->top] = op;return true;
}double pop_num(NumStack* s)
{if (s->top == -1) return 0.0;return s->data[s->top--];
}char pop_op(OpStack* s)
{if (s->top == -1) return '\0';return s->data[s->top--];
}// 獲取棧頂元素
double top_num(NumStack* s)
{ return s->top == -1 ? 0.0 : s->data[s->top];
}
char top_op(OpStack* s)
{return s->top == -1 ? '\0' : s->data[s->top];
}// 判斷棧是否為空
bool is_empty_op(OpStack* s)
{ return s->top == -1;
}
bool is_empty_num(NumStack* s)
{return s->top == -1;
}// 獲取運算符優先級
int get_priority(char op)
{if (op == '+' || op == '-') return 1;if (op == '*' || op == '/') return 2;return 0;
}// 清屏函數
void clear_screen()
{
#ifdef _WIN32system("cls");
#elsesystem("clear");
#endif
}// 延時函數
void delay(int ms)
{
#ifdef _WIN32Sleep(ms);
#elseusleep(ms * 1000);
#endif
}// 顯示棧狀態
void display_stacks(NumStack* ns, OpStack* os, char current)
{clear_screen();printf("當前字符: %c\n\n", current);printf("操作數棧: ");for (int i = 0; i <= ns->top; i++)printf("%.2f ", ns->data[i]);printf("\n運算符棧: ");for (int i = 0; i <= os->top; i++)printf("%c ", os->data[i]);printf("\n\n");delay(1000);
}// 執行計算
double calculate(double a, double b, char op) {switch (op) {case '+': return a + b;case '-': return a - b;case '*': return a * b;case '/':if (b == 0) {printf("錯誤:除數不能為零!\n");exit(EXIT_FAILURE);}return a / b;default: return 0;}
}// 處理表達式
void process_expression(const char* expr)
{NumStack num_stack;OpStack op_stack;init_num_stack(&num_stack);init_op_stack(&op_stack);int i = 0;while (expr[i] != '\0') {if (expr[i] == ' ') {i++;continue;}display_stacks(&num_stack, &op_stack, expr[i]);if (isdigit(expr[i]) || expr[i] == '.') {// 處理數字char num_str[32];int j = 0;while (isdigit(expr[i]) || expr[i] == '.')num_str[j++] = expr[i++];num_str[j] = '\0';push_num(&num_stack, atof(num_str));i--;}else if (expr[i] == '('){push_op(&op_stack, '(');}else if (expr[i] == ')') {while (top_op(&op_stack) != '(') {char op = pop_op(&op_stack);double b = pop_num(&num_stack);double a = pop_num(&num_stack);push_num(&num_stack, calculate(a, b, op));display_stacks(&num_stack, &op_stack, expr[i]);}pop_op(&op_stack); // 彈出左括號}else {// 處理運算符while (!is_empty_op(&op_stack) &&get_priority(expr[i]) <= get_priority(top_op(&op_stack))){char op = pop_op(&op_stack);double b = pop_num(&num_stack);double a = pop_num(&num_stack);push_num(&num_stack, calculate(a, b, op));display_stacks(&num_stack, &op_stack, op);}push_op(&op_stack, expr[i]);}i++;}// 處理剩余運算符while (!is_empty_op(&op_stack)){char op = pop_op(&op_stack);double b = pop_num(&num_stack);double a = pop_num(&num_stack);push_num(&num_stack, calculate(a, b, op));display_stacks(&num_stack, &op_stack, op);}printf("最終結果: %.2f\n", pop_num(&num_stack));
}// 清空輸入緩沖區
void clean_stdin()
{int c;while ((c = getchar()) != '\n' && c != EOF);
}// 獲取用戶選擇
bool get_continue_choice()
{while (1){printf("是否繼續?(Y/N): ");fflush(stdout);char input[4];if (fgets(input, sizeof(input), stdin) == NULL){return false;}// 處理過長輸入if (strchr(input, '\n') == NULL){clean_stdin();}char choice = tolower(input[0]);if (choice == 'y') return true;if (choice == 'n') return false;printf("無效輸入,請重新輸入!\n");}
}int main()
{do {char expr[MAX_SIZE];printf("請輸入中綴表達式(支持+-*/和括號):> ");if (fgets(expr, MAX_SIZE, stdin) == NULL) {break;}expr[strcspn(expr, "\n")] = '\0';if (strlen(expr) == 0) {printf("輸入不能為空!\n");continue;}process_expression(expr);} while (get_continue_choice());printf("感謝使用計算器!\n");return 0;
}
?以上是基于VS2022編譯器,用C語言實現——一個中綴表達式的計算器。支持用戶輸入和動畫演示過程。
?????????????????????
目錄
一、思路概要和知識回顧
1.思路概要
①中綴表達式計算:
②用戶輸入:
③動畫演示:
具體實現步驟:
代碼結構的大致框架:
2.知識回顧
二、具體實現步驟
1.定義棧結構
①操作數棧:
②運算符棧:
2.初始化棧
3.向|操作數棧|中壓入數值元素函數
4.向|運算符棧|中壓入元素的函數
5.彈出|操作數棧|棧頂元素函數
6.彈出|運算符棧|中棧頂元素函數
7.獲取棧頂函數
①獲取棧頂元素函數的作用
②與彈出棧頂元素函數的區別
③為什么需要兩種操作?
④協同工作實現棧頂元素的利用
8. 判斷棧空函數
①函數功能
②在完整代碼中的作用
9.優先級函數
10.清屏函數
11.延時函數
12.顯示棧函數
13.執行計算函數?
14.處理表達式函數
①初始化棧?
②循環處理表達式
③跳過空格?
④顯示當前棧狀態?
⑤?處理數字
⑥處理左括號
⑦處理右括號?
⑧處理運算符
16.獲取用戶輸入函數?
①無限循環?
②提示用戶輸入?
③讀取用戶輸入
?④處理過長輸入
⑤轉換并檢查用戶選擇?
⑥無效輸入提示?
?17.主函數邏輯
①開始 do-while 循環
②定義表達式緩沖區
③提示用戶輸入
④讀取用戶輸入
⑤去除換行符
⑥檢查輸入是否為空
⑦處理表達式
⑧檢查是否繼續
三、完整代碼