Pascal 程式設計/表示式和分支
在本章中,您將學習
- 區分語句和表示式,以及
- 如何編寫分支程式。
在我們瞭解“表示式”之前,讓我們更精確地定義“語句”:一條語句告訴計算機去改變某件事。所有語句以某種方式改變程式狀態。程式狀態指的是一整套個體狀態的集合,包括但不限於
- 變數具有的值,或者
- 通常程式指定的記憶體內容,還有
- (隱式) 當前正在處理的語句。
最後這個指標儲存在一個不可見的變數中,叫做程式計數器。PC總是指向當前正在處理的語句。想象一下用你的手指指向原始碼行(或者更準確地說是語句): “我們在這裡!” 在一條語句成功執行之後,PC向前移動,結果它指向下一條語句。[fn 1] PC不能直接改變,只能隱式改變。在本章中,我們將學習如何做到這一點。
語句可以分為兩類:基本語句和複合語句。基本語句是高階程式語言的基本構建塊。在 Pascal 中,它們是:[fn 2][fn 3]
- 賦值 (
:=),以及 - 過程[fn 4]呼叫(例如
readLn(x)和writeLn('Hi!'))。
“複合”語句是
- 序列(用
begin和end括起來), - 分支,以及
- 迴圈。
與許多其他程式語言不同,在 Pascal 中,分號 ;分隔兩條語句。許多程式語言使用某個符號來終止一條語句,例如分號。然而,Pascal 認識到,額外的符號不應該成為語句的一部分,以使其成為一條實際的語句。第二章中的 helloWorld 程式可以在 writeLn(…) 之後不使用分號,因為後面沒有語句。
program helloWorld(output);
begin
writeLn('Hello world!')
end.
然而,我們建議你在那裡加上分號,即使它不是必需的。在本章後面,你將學習一個可能(並不總是)不想加分號的地方。
雖然分號不會終止一條語句,但程式頭部、常量定義、變數宣告以及其他一些語言結構是由這個符號終止的。你不能在這些地方省略分號。
與語句不同,表示式不會改變程式狀態。它們是瞬時值,可以作為語句的一部分使用。表示式的例子有
42,'D’oh!',或者x(其中x是先前宣告的變數的名稱)。
每個表示式都具有型別:當表示式被求值時,它會生成一個特定資料型別的的值。表示式 42 的資料型別是 integer,'D’oh!' 是一種“字串型別”,而僅由變數名稱組成的表示式,例如 x,則會生成該變數的資料型別。由於表示式的型別非常重要,所以表示式是以它們的型別命名的。表示式 true 是一個布林表示式,false 也是。
表示式出現在很多地方
- 在賦值語句 (
:=) 中,你在RHS上寫一個表示式。這個表示式必須與LHS上變數的資料型別相同。[fn 5] 賦值透過將表示式的瞬時值儲存到變數的記憶體塊中來使其“永久化”。 - 過程呼叫的引數列表由表示式組成。為了呼叫一個過程,所有引數都必須儲存在記憶體中。可以將過程呼叫理解為在實際呼叫過程之前對不可見變數的一系列賦值。因此
writeLn(output, 'Hi!')可以理解為- destination 變成
output - “第一個引數” 變成
'Hi!' - 用不可見的“變數”destination 和 “第一個引數”呼叫過程
writeLn
- destination 變成
- 在常量定義中,RHS也是一個表示式,儘管它必須是常量(因此得名)。例如,你不能在該表示式中使用變數。
表示式的強大之處在於它能夠與其他表示式連結。這是透過使用叫做運算子的特殊符號來實現的。在上一章中,我們已經看到一個運算子,即等於運算子 =。現在我們可以分解這樣的表示式
response = 'y' {
│ └──────┰─────┘ ┃ └──────┰──────┘ │
│ sub-expression operator sub-expression │
│ │
└─────────────────────┰─────────────────────┘
expression }
如你所見,表示式可以是更大表達式的組成部分。子表示式透過運算子符號 = 連結在一起。透過運算子符號連結或關聯的子表示式也稱為運算元。
透過運算子連線表示式會“建立”一個新的表示式,它擁有自己的資料型別。雖然上面的例子中,response 和 'y' 都是 char-表示式,但整個表示式的總體資料型別為 Boolean,因為連線運算子是相等比較。相等比較會產生一個布林表示式。以下表格列出了我們可以使用已知知識使用的關係運算符。
| 名稱 | 原始碼符號 |
|---|---|
| =, 等於 | =
|
| ≠, 不等於 | <>
|
| <, 小於 | <
|
| >, 大於 | >
|
| ≤, 小於等於 | <=
|
| ≥, 大於等於 | >=
|
使用這些符號會產生布爾表示式。表示式的值將根據運算子的定義,是 true 或 false。
所有這些關係運算符都要求兩邊的運算元具有相同的資料型別。[fn 6] 儘管我們可以說 '$' = 1337 是錯誤的,這意味著它應該評估為 false,但它仍然是非法的,因為 '$' 是一個 char‑表示式,而 1337 是一個 integer‑表示式。Pascal 禁止你比較資料型別不同的東西/物件。所以,我想,你不能比較蘋果和橘子。(注意,一些轉換例程將允許你進行一些直接不允許的比較,但需要繞道。在下一章中,我們將看到其中一些。)
| 施加的資料型別限制是為了一個好的目的。你可能會覺得Pascal 對所有這些資料型別太挑剔了,但這種所謂的強型別安全性實際上是一種優勢。它可以防止你,作為程式設計師,犯無意的程式設計錯誤。 |
表示式也用於計算,你使用的機器並非無緣無故被稱為“計算機”。在標準Pascal中,你可以對兩個數字進行加、減、乘和除,即integer‑ 和 real‑表示式以及它們的任何組合。適用於所有組合的符號是
| 名稱 | 原始碼符號 |
|---|---|
| +, 加 | +
|
| −, 減 | -
|
| ×, 乘 | *
|
除法運算已被省略,因為它比較棘手,將在後面的章節中解釋。
如果至少有一個運算元是real‑表示式,則整個表示式將是real型別,即使精確的值可以用integer表示。 |
注意,與數學不同,兩個“運算元”之間沒有隱含的乘法:你總是需要顯式地寫出“乘法”,即星號*。
運算子符號+ 和 - 也可以只與一個數字表達式一起出現。它表示正負號,或者更正式地說,分別表示符號標識或符號反轉。
就像在數學中一樣,運算子具有一定的“力量”與之相關,在CS中,我們稱之為運算子優先順序。你可能還記得在小學或中學、學校或家庭教育中,PEMDAS 的首字母縮略詞:它是記憶術語,代表了
- 括號
- 指數
- 乘法/除法
- 加法/減法
它為我們在數學中評估算術表示式的正確順序提供了指導。幸運的是,Pascal 的運算子優先順序完全相同,雖然嚴格來說,它並沒有由“PEMDAS”這個詞定義。[fn 7]
標準Pascal沒有定義任何指數/冪運算子,因此,例如, 必須寫成 x * x,不能再簡化了。但是,擴充套件的Pascal標準定義了pow 運算子。 |
你可能已經猜到了,運算子優先順序可以透過使用括號在每個表示式中被覆蓋:為了評估5 * (x + 7),子表示式x + 7 首先被評估,然後該值乘以 5,即使乘法通常優先於加法或減法。
分支是複雜的語句。到目前為止,我們編寫的程式都是線性的:它們從頂部開始,計算機(理想情況下)逐行執行它們,直到最後的end.。分支允許你選擇不同的路徑,就像在十字路口一樣:“我向左轉還是向右轉?” 處理程式“向下”的總體趨勢依然存在,但(原則上)存在選擇。
讓我們回顧上一章的程式iceCream。條件語句已突出顯示
program iceCream(input, output);
var
response: char;
begin
writeLn('Do you like ice cream?');
writeLn('Type “y” for “yes” (“Yummy!”) and “n” for “no”.');
writeLn('Confirm your selection by hitting Enter.');
readLn(input, response);
if response = 'y' then
begin
writeLn('Awesome!');
end;
end.
現在我們可以說response = 'y' 是一個布林表示式。單詞if 和 then 是我們稱為條件語句的語言結構的一部分。在then 之後,是一個語句,在本例中是一個複雜語句:begin … end 是一個序列,被認為是一個語句。
如果你記得或可以從原始碼中推斷出來,begin … end 之間的語句,writeLn('Awesome!') 只有在表示式response = 'y' 評估為true 時才會執行。否則,它將被跳過,就像什麼都沒有一樣。
由於這種二元性(是/否,執行程式碼或跳過它),if 和 then 之間的表示式必須是布林表示式。你不能寫 if 1 * 1 then …,因為 1 * 1 是一個 integer-表示式。計算機無法根據一個 integer-表示式來決定是否應該選擇一條路徑。
讓我們擴充套件程式iceCream,如果使用者說不喜歡冰淇淋,就給出另一個響應。我們可以用另一個if‑語句來實現,但對於這種情況,有一個更聰明的解決方案。
if response = 'y' then
begin
writeLn('Awesome!');
end
else
begin
writeLn('That’s a pity!');
end;
突出顯示的備選方案,即 else 分支,僅在提供的布林表示式計算結果為 false 時執行。無論哪種情況,無論 then 分支還是 else 分支被執行,程式執行將在 else 語句之後恢復(在本例中是在最後一行中的 end; 之後)。
在 end 之後沒有分號,在 else 之前。分號用於分隔語句,但是整個結構 if … then … else … 是一個(複雜)語句。不允許使用分號來分割語句的一部分。注意,在某些情況下,您可以在此位置放置分號。我們將在後面的高階章節中詳細介紹。 |
分支和(即將解釋的)迴圈是根據資料、表示式以及因此響應使用者輸入的唯一方法來修改程式計數器 (PC),即指向當前執行語句的“手指”。沒有它們,您的程式將是靜態的,並且會一遍又一遍地做同樣的事情,所以很無聊。利用分支和迴圈將使您的程式對給定輸入更加響應。
char 表示式?所有?沒有?'0' 到 '9','A' 到 'Z' 以及 'a' 到 'z' 按您從英文字母表中熟悉的順序排序,或者(關於數字)按它們的數值升序排序。因此,您可以進行 'A' <= 'F'(它將計算為 true)之類的比較。
註釋
- ↑ 本段有意使用不精確的術語來保持簡單。事實上,PC 是一個處理器暫存器(例如
%eip,擴充套件指令指標),並指向下一個指令(而不是當前語句)。有關更多詳細資訊,請參見主題:組合語言。 - ↑ 跳轉 (
goto) 已有意禁止進入附錄,並且在此處不予介紹,但goto也是一個基本語句。 - ↑ 異常擴充套件還將
raise定義為一個基本語句。 - ↑ 更準確地說:
procedure呼叫。 - ↑ 或者,一種“相容”資料型別,例如,
integer表示式可以儲存到資料型別為real的變數中,反之則不行。隨著我們不斷學習,我們將瞭解有關“相容”型別的更多資訊。 - ↑ 在關於集合的章節中,我們將擴充套件此語句。
- ↑ 要閱讀技術定義,請參閱 ISO 標準 7185 中的 § 表示式,子部分 1“一般”。