上一章中,我講解了如何定義函數(shù)。本章中,我會(huì)講解如何通過條件編寫過程。這個(gè)是編寫使用程序很重要的一步。
if
表達(dá)式將過程分為兩個(gè)部分。if
的格式如下:
(if predicate then_value else_value)
如果predicate
部分為真,那么then_value
部分被求值,否則else_value
部分被求值,并且求得的值會(huì)返回給if
語句的括號外。true
是除false
以外的任意值,true
使用#t
表示,false
用#f
表示。
在R5RS中,false
(#f
)和空表(’())
是兩個(gè)不同的對象。然而,在MIT-Scheme中,這兩個(gè)為同一對象。這個(gè)不同可能是歷史遺留問題,在以前的標(biāo)準(zhǔn)——R4RS中,#f
和’()
被定義為同一對象。
因此,從兼容性角度考慮,你不應(yīng)該使用表目錄作為謂詞。使用函數(shù)null?
來判斷表是否為空。
(null? '())
;Value: #t
(null? '(a b c))
;Value: () ;#f
函數(shù)not
可用于對謂詞取反。此函數(shù)只有一個(gè)參數(shù)且如果參數(shù)值為#f
則返回#t
,反之,參數(shù)值為#t
則返回#f
。if
表達(dá)式是一個(gè)特殊形式,因?yàn)樗粚λ械膮?shù)求值。因?yàn)槿绻?code>predicate為真,則只有then_value
部分被求值。另一方面,如果predicate
為假,只有else_value
部分被求值。
例:首項(xiàng)為a0
,增長率r
,項(xiàng)數(shù)為n
的幾何增長(geometric progression)數(shù)列之和
(define (sum-gp a0 r n)
(* a0
(if (= r 1)
n
(/ (- 1 (expt r n)) (- 1 r))))) ; !!
通常來說,幾何增長數(shù)列的求和公式如下:
a0 * (1 - r^n) / (1 - r) (r ≠ 1)
a0 * n (r = 1)
如果if
表達(dá)式對所有參數(shù)求值的話,那么有;!!
注釋的那行就算在r=1
時(shí)也會(huì)被求值,這將導(dǎo)致產(chǎn)生一個(gè)“除數(shù)為0”的錯(cuò)誤。
你也可以省去else_value
項(xiàng)。這樣的話,當(dāng)predicate
為假時(shí),返回值就沒有被指定。如果你希望當(dāng)predicate
為假時(shí)返回#f
,那么就要明確地將它寫出來。
then_value
和else_value
都應(yīng)該是S-表達(dá)式。如果你需要副作用,那么就應(yīng)該使用begin
表達(dá)式。我們將在下一章討論begin
表達(dá)式。
練習(xí)1
編寫下面的函數(shù)。閱讀第五節(jié)了解如何編寫謂詞。
- 返回一個(gè)實(shí)數(shù)絕對值的函數(shù)。
- 返回一個(gè)實(shí)數(shù)的倒數(shù)的函數(shù)。如果參數(shù)為
0
,則返回#f
。- 將一個(gè)整數(shù)轉(zhuǎn)化為ASCII碼字符的函數(shù)。整數(shù)可以被轉(zhuǎn)化為33-126號之間的ASCII碼。使用
integer->char
可以將整數(shù)轉(zhuǎn)化為字符。如果給定的整數(shù)不能夠轉(zhuǎn)化為字符,那么就返回#f
。
and
和or
是用于組合條件的兩個(gè)特殊形式。Scheme中的and
和or
不同于C語言中的約定。它們不返回一個(gè)布爾值(#t
或#f
),而是返回給定的參數(shù)之一。and
和or
可以使你的代碼更加短小。
and
具有任意個(gè)數(shù)的參數(shù),并從左到右對它們求值。如果某一參數(shù)為#f
,那么它就返回#f
,而不對剩余參數(shù)求值。反過來說,如果所有的參數(shù)都不是#f
,那么就返回最后一個(gè)參數(shù)的值。
(and #f 0)
;Value: ()
(and 1 2 3)
;Value: 3
(and 1 2 3 #f)
;Value: ()
or
具有可變個(gè)數(shù)的參數(shù),并從左到右對它們求值。它返回第一個(gè)不是值#f
的參數(shù),而余下的參數(shù)不會(huì)被求值。如果所有的參數(shù)的值都是#f
的話,則返回最后一個(gè)參數(shù)的值。
(or #f 0)
;Value: 0
(or 1 2 3)
;Value: 1
(or #f 1 2 3)
;Value: 1
(or #f #f #f)
;Value: ()
練習(xí)2
編寫下面的函數(shù)。
- 一個(gè)接受三個(gè)實(shí)數(shù)作為參數(shù)的函數(shù),若參數(shù)皆為正數(shù)則返回它們的乘積。
- 一個(gè)接受三個(gè)實(shí)數(shù)作為參數(shù)的函數(shù),若參數(shù)至少一個(gè)為負(fù)數(shù)則返回它們的乘積。
盡管所有的分支都可以用if
表達(dá)式表達(dá),但當(dāng)條件有更多的可能性時(shí),你就需要使用嵌套的if
表達(dá)式了,這將使代碼變得復(fù)雜。處理這種情況可以使用cond
表達(dá)式。cond
表達(dá)式的格式如下:
(cond
(predicate_1 clauses_1)
(predicate_2 clauses_2)
......
(predicate_n clauses_n)
(else clauses_else))
在cond
表達(dá)式中,predicates_i
是按照從上到下的順序求值,而當(dāng)predicates_i
為真時(shí),clause_i
會(huì)被求值并返回。i
之后的predicates
和clauses
不會(huì)被求值。如果所有的predicates_i
都是假的話,則返回cluase_else
。在一個(gè)子句中,你可以寫數(shù)條S-表達(dá)式,而clause
的值是最后一條S-表達(dá)式。
例:城市游泳池的收費(fèi)。
Foo市的城市游泳池按照顧客的年齡收費(fèi):
如果?age?≤ 3 或者?age?≥ 65 則 免費(fèi);
如果 介于 4 ≤?age?≤ 6 則 0.5美元;
如果 介于 7 ≤?age?≤ 12 則 1.0美元;
如果 介于 13 ≤?age?≤ 15 則 1.5美元;
如果 介于 16 ≤?age?≤ 18 則 1.8美元;
其它 則 2.0美元;那么,一個(gè)返回城市游泳池收費(fèi)的函數(shù)如下:
(define (fee age)
(cond
((or (<= age 3) (>= age 65)) 0)
((<= 4 age 6) 0.5)
((<= 7 age 12) 1.0)
((<= 13 age 15) 1.5)
((<= 16 age 18) 1.8)
(else 2.0)))
練習(xí) 3
編寫下列函數(shù)。
成績(A-D)是由分?jǐn)?shù)決定的。編寫一個(gè)將分?jǐn)?shù)映射為成績的函數(shù),映射規(guī)則如下:
- A 如果?score?≥ 80
- B 如果 60 ≤?score?≤ 79
- C 如果 40 ≤?score?≤ 59
- D 如果?score?< 40
我將介紹一些用于做判斷的函數(shù)。這些函數(shù)的名字都以'?'
結(jié)尾。
基本函數(shù)eq?
、eqv?
、equal?
具有兩個(gè)參數(shù),用于檢查這兩個(gè)參數(shù)是否“一致”。這三個(gè)函數(shù)之間略微有些區(qū)別。
eq?
該函數(shù)比較兩個(gè)對象的地址,如果相同的話就返回#t
。例如,(eq? str str)
返回#t
,因?yàn)?code>str本身的地址是一致的。與此相對的,因?yàn)樽址?code>”hello”和”hello”
被儲(chǔ)存在了不同的地址中,函數(shù)將返回#f
。不要使用eq?
來比較數(shù)字,因?yàn)椴粌H在R5RS中,甚至在MIT-Scheme實(shí)現(xiàn)中,它都沒有指定返回值。使用eqv?
或者=
替代。
(define str "hello")
;Value: str
(eq? str str)
;Value: #t
(eq? "hello" "hello")
;Value: () ← It should be #f in R5RS
;;; comparing numbers depends on implementations
(eq? 1 1)
;Value: #t
(eq? 1.0 1.0)
;Value: ()
eqv?
該函數(shù)比較兩個(gè)存儲(chǔ)在內(nèi)存中的對象的類型和值。如果類型和值都一致的話就返回#t
。對于過程(lambda
表達(dá)式)的比較依賴于具體的實(shí)現(xiàn)。這個(gè)函數(shù)不能用于類似于表和字符串一類的序列比較,因?yàn)楸M管這些序列看起來是一致的,但它們的值是存儲(chǔ)在不同的地址中。
(eqv? 1.0 1.0)
;Value: #t
(eqv? 1 1.0)
;Value: ()
;;; don't use it to compare sequences
(eqv? (list 1 2 3) (list 1 2 3))
;Value: ()
(eqv? "hello" "hello")
;Value: ()
;;; the following depends on implementations
(eqv? (lambda(x) x) (lambda (x) x))
;Value: ()
equal?
該函數(shù)用于比較類似于表或者字符串一類的序列。
(equal? (list 1 2 3) (list 1 2 3))
;Value: #t
(equal? "hello" "hello")
;Value: #t
下面列舉了幾個(gè)用于檢查類型的函數(shù)。這些函數(shù)都只有一個(gè)參數(shù)。
pair?
?如果對象為序?qū)t返回#t
;list?
?如果對象是一個(gè)表則返回#t
。要小心的是空表’()
是一個(gè)表但是不是一個(gè)序?qū)Α?/li>
null?
?如果對象是空表’()的話就返回#t。symbol?
?如果對象是一個(gè)符號則返回#t。char?
?如果對象是一個(gè)字符則返回#t。string?
?如果對象是一個(gè)字符串則返回#t。number?
?如果對象是一個(gè)數(shù)字則返回#t。complex?
?如果對象是一個(gè)復(fù)數(shù)則返回#t。real?
?如果對象是一個(gè)實(shí)數(shù)則返回#t。rational?
?如果對象是一個(gè)有理數(shù)則返回#t。integer?
?如果對象是一個(gè)整數(shù)則返回#t。exact?
?如果對象不是一個(gè)浮點(diǎn)數(shù)的話則返回#t。inexact?
?如果對象是一個(gè)浮點(diǎn)數(shù)的話則返回#t。
=
、>
、<
、<=
、>=
這些函數(shù)都有任意個(gè)數(shù)的參數(shù)。如果參數(shù)是按照這些函數(shù)的名字排序的話,函數(shù)就返回#t
。
(= 1 1 1.0)
;Value: #t
(< 1 2 3)
;Value: #t
(< 1)
;Value: #t
(<)
;Value: #t
(= 2 2 2)
;Value: #t
(< 2 3 3.1)
;Value: #t
(> 4 1 -0.2)
;Value: #t
(<= 1 1 1.1)
;Value: #t
(>= 2 1 1.0)
;Value: #t
(< 3 4 3.9)
;Value: ()
odd?
、even?
、positive?
、negative?
、zero?
這些函數(shù)僅有一個(gè)參數(shù),如果這些參數(shù)滿足函數(shù)名所指示的條件話就返回#t
。
在比較字符的時(shí)候可以使用char=?
、char<?
、char>?
、char<=?
以及char>=?
函數(shù)。具體的細(xì)節(jié)請參見R5RS。
比較字符串時(shí),可以使用string=?
和string-ci=?
等函數(shù)。具體細(xì)節(jié)請參見R5RS。
在這一章中,我總結(jié)了關(guān)于分支的知識(shí)點(diǎn)。編寫分支程序可以使用if
表達(dá)式和cond
表達(dá)式。
下一章我將講解局部變量。
; 1
(define (my-abs n)
(* n
(if (positive? n) 1 -1)))
; 2
(define (inv n)
(if (not (zero? n))
(/ n)
#f))
; 3
(define (i2a n)
(if (<= 33 n 126)
(integer->char n)
#f))
; 1
(define (pro3and a b c)
(and (positive? a)
(positive? b)
(positive? c)
(* a b c)))
; 2
(define (pro3or a b c)
(if (or (negative? a)
(negative? b)
(negative? c))
(* a b c)))
(define (score n)
(cond
((>= n 80) 'A)
((<= 60 n 79) 'B)
((<= 40 n 59) 'C)
(else 'D)))
更多建議: