用C語言實現——一個中綴表達式的計算器。支持用戶輸入和動畫演示過程。

一、思路概要和知識回顧

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_numpop_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.執行計算函數?

?

  1. 如果運算符是 '+',則返回兩個操作數 ab 的和。

?

?

如果運算符是 '-',則返回兩個操作數 ab 的差。?

?

如果運算符是 '*',則返回兩個操作數 ab 的積。?

?

  1. 如果運算符是 '/',則首先檢查除數 b 是否為零。如果是零,打印錯誤信息并退出程序;否則,返回兩個操作數 ab 的商。
    ?

?如果傳入的運算符不屬于上述四種基本運算符之一,則返回 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 函數 :之前定義的函數,用于彈出運算符棧的棧頂元素并返回該元素。

?

?

彈出操作數 :依次彈出操作數棧的棧頂元素,分別存儲在變量 ba 中。這里假設 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 函數 :對 ab 執行 op 運算,并返回結果。

push_num 函數 :將計算結果壓入操作數棧。

?

顯示棧狀態:

display_stacks 函數 :顯示當前操作數棧和運算符棧的狀態。

壓入當前運算符:

?

?

push_op 函數 :將當前運算符壓入運算符棧。

15.清理輸入緩沖區函數

  1. 聲明變量 c 用于存儲從標準輸入讀取的字符。

  2. 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 循環

②定義表達式緩沖區

③提示用戶輸入

④讀取用戶輸入

⑤去除換行符

⑥檢查輸入是否為空

⑦處理表達式

⑧檢查是否繼續

三、完整代碼


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/78372.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78372.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/78372.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Langchain_Agent+數據庫

本處使用Agent數據庫&#xff0c;可以直接執行SQL語句。可以多次循環查詢問題 前文通過chain去聯系數據庫并進行操作&#xff1b; 通過鏈的不斷內嵌組合&#xff0c;生成SQL在執行SQL再返回。 初始化 import os from operator import itemgetterimport bs4 from langchain.ch…

Python 爬蟲如何偽裝 Referer?從隨機生成到動態匹配

一、Referer 的作用與重要性 Referer 是 HTTP 請求頭中的一個字段&#xff0c;用于標識請求的來源頁面。它在網站的正常運行中扮演著重要角色&#xff0c;例如用于統計流量來源、防止惡意鏈接等。然而&#xff0c;對于爬蟲來說&#xff0c;Referer 也可能成為被識別為爬蟲的關…

Post-Processing PropertySource instance詳解 和 BeanFactoryPostProcessor詳解

PropertySourcesBeanFactoryPostProcessor詳解 1. 核心概念 BeanFactoryPostProcessor 是 Spring 框架中用于在 BeanFactory 初始化階段 對 Environment 中的 PropertySource 進行后處理的接口。它允許開發者在 Bean 創建之前 對屬性源進行動態修改&#xff0c;例如添加、刪除…

[C]基礎13.深入理解指針(5)

博客主頁&#xff1a;向不悔本篇專欄&#xff1a;[C]您的支持&#xff0c;是我的創作動力。 文章目錄 0、總結1、sizeof和strlen的對比1.1 sizeof1.2 strlen1.3 sizeof和strlen的對比 2、數組和指針筆試題解析2.1 一維數組2.2 字符數組2.2.1 代碼12.2.2 代碼22.2.3 代碼32.2.4 …

賽靈思 XCKU115-2FLVB2104I Xilinx Kintex UltraScale FPGA

XCKU115-2FLVB2104I 是 AMD Xilinx Kintex UltraScale FPGA&#xff0c;基于 20 nm 先進工藝&#xff0c;提供高達 1 451 100 個邏輯單元&#xff08;Logic Cells&#xff09;&#xff0c;77 721 600 bit 的片上 RAM 資源&#xff0c;以及 5 520 個 DSP 切片&#xff08;DSP48E…

CAPL編程_03

1_文件操作的相關函數&#xff1a; 讀文本文件內容 讀取文本文件操作的三部曲 1&#xff09;打開文件 —— openFileRead ( ) 2&#xff09;逐行讀取 —— fileGetString ( ) 、fileGetStringSZ ( ) 3&#xff09;關閉文件 —— fileClose ( ) char content[100];…

2025年江西建筑安全員A證適合報考人群

江西建筑安全員A證適合報考人群 江西省建筑安全員A證&#xff08;建筑施工企業主要負責人安全生產考核合格證書&#xff09;主要面向建筑行業管理人員&#xff0c;適合以下人員報考&#xff1a; 1. 企業主要負責人 法人代表、總經理、分管安全副總&#xff1a;依法需持A證&a…

Docker安裝(Ubuntu22版)

前言 你是否還在為Linux上配置Docker而感到煩惱&#xff1f; 你是否還在為docker search&#xff0c;docker pull連接不上&#xff0c;而感到沮喪&#xff1f; 本文將解決以上你的所有煩惱&#xff01;快速安裝好docker&#xff01; Docker安裝 首先&#xff0c;我們得先卸載…

Ubuntu18.04配置C++環境和Qt環境

Ubuntu18.04配置C環境和Qt環境 1、前言3.2 安裝其他庫3.3 查看有沒有安裝成功3.4測試C環境 4、配置Qt環境4.1 安裝相關的庫4.2 測試 5、總結 1、前言 記錄一下Ubuntu18.04配置C環境和Qt環境的過程&#xff0c;方便自己日后回顧&#xff0c;也可以給有需要的人提供幫助。 # 2…

ACWing——算法基礎課

置頂思考&#xff1a; 算法的本質是什么樣的思想&#xff1f; 這種思想可以解決哪類問題&#xff1f; 有沒有其他的解決思路&#xff1f; 關注數值范圍&#xff0c;思考可不可以針對性解決問題&#xff1f; 目錄 https://leetcode.cn/circle/discuss/RvFUtj/ 滑動窗口與雙指針…

私鑰連接服務器(已經有服務器私鑰

前言&#xff1a;假設我們已經有了服務器的私鑰&#xff0c;我們怎么配置呢&#xff1f; 下面我會從vsc的配置角度來寫 ? 步驟一&#xff1a;準備工作 安裝 VS Code&#xff08;如果還沒裝&#xff09; &#x1f449; https://code.visualstudio.com/ 安裝插件&#xff1a;Re…

Redis LFU 策略參數配置指南

一、基礎配置步驟? 設置內存上限? 在 redis.conf 配置文件中添加以下指令&#xff0c;限制 Redis 最大內存使用量&#xff08;例如設置為 4GB&#xff09;&#xff1a; maxmemory 4gb選擇 LFU 淘汰策略? 根據鍵的作用域選擇策略&#xff1a; # 所有鍵參與淘汰 maxmemory-…

嵌入式 C 語言面試核心知識點全面解析:基礎語法、運算符與實戰技巧

在嵌入式面試中&#xff0c;C 語言基礎是重中之重。本文針對經典面試題進行詳細解析&#xff0c;幫助新手系統掌握知識點&#xff0c;提升面試應對能力。 一、數據結構邏輯分類 題目 在數據結構中&#xff0c;從邏輯上可以把數據結構分為&#xff08; &#xff09;。 A、動態…

11.AOP開發

十一、AOP開發 1、Spring Boot實現 AOP 11.1.1、SpringBootAop簡介 Spring Boot的AOP編程和Spring框架中AOP編程的唯一區別是&#xff1a;引入依賴的方式不同,其他內容完全一樣 Spring Boot中AOP編程需要引入aop啟動器&#xff1a; <!--aop啟動器--> <dependency…

【網絡入侵檢測】基于源碼分析Suricata的PCAP模式

【作者主頁】只道當時是尋常 【專欄介紹】Suricata入侵檢測。專注網絡、主機安全,歡迎關注與評論。 1. 概要 ?? 本文聚焦于 Suricata 7.0.10 版本源碼,深入剖析其 PCAP 模式的實現原理。通過系統性拆解初始化階段的配置流程、PCAP 數據包接收線程的創建與運行機制,以及數據…

.NET 10 中的新增功能

.NET 運行時 .NET 10 運行時引入了新功能和性能改進。 關鍵更新包括&#xff1a; 數組接口方法反虛擬化&#xff1a;JIT 現在可以取消虛擬化和內聯數組接口方法&#xff0c;從而提高數組枚舉的性能。數組枚舉去抽象化&#xff1a;改進功能以通過枚舉器減少數組迭代的抽象開銷…

盲注命令執行(Blind Command Execution)

一、核心原理 1. 無回顯命令執行的本質 盲命令執行&#xff08;Blind Command Execution&#xff09;是一種攻擊形式&#xff0c;攻擊者通過注入系統命令到Web應用或后端系統中&#xff0c;但無法直接獲取命令執行結果。盲命令執行的本質在于攻擊者無法直接看到執行結果&#x…

Linux多線程技術

什么是線程 在一個程序里的多執行路線就是線程。線程是進程中的最小執行單元&#xff0c;可理解為 “進程內的一條執行流水線”。 進程和線程的區別 進程是資源分配的基本單位&#xff0c;線程是CPU調度的基本單位。 fork創建出一個新的進程&#xff0c;會創建出一個新的拷貝&…

計算機組成原理實驗(1) 算術邏輯運算單元實驗

實驗一 算術邏輯運算單元實驗 一、實驗目的 1、掌握簡單運算器的數據傳輸方式 2、掌握74LS181的功能和應用 二、實驗內容 1、不帶進位位邏輯或運算實驗 2、不帶進位位加法運算實驗 3、實驗指導書2.15實驗思考 三、實驗步驟和結果 實驗內容一&#xff1a;不帶進位…

Android將啟動畫面實現遷移到 Android 12 及更高版本

如果在 Android 11 或更低版本中實現自定義啟動畫面&#xff0c;請遷移應用遷移到 SplashScreen API 以獲取幫助 確保其在 Android 12 及更高版本中正確顯示。 從 Android 12 開始&#xff0c;在所有應用的冷啟動和溫啟動期間&#xff0c;系統都會應用 Android 系統的默認啟動…