📅 第 2 天:让程序学会“做选择”(条件判断与逻辑)
🎯 今日目标:
- 彻底搞懂 Scheme 中的“真假”概念(只有
#f是假)。 - 掌握多重条件判断
cond(相当于其他语言的 if-else if-else)。 - 学会逻辑组合
and,or,not。 - 理解逻辑运算符的“短路”特性。
💡 核心心法:
昨天我们只用了
if做二选一,今天我们要处理复杂的世界。
记住:在 Scheme 眼里,除了#f(false),连 0、空字符串""、空列表()全都是真的!这点和 Python/C/Java 完全不同,千万别惯性思维。
📚 理论讲解 + 10 个实战例子
1. 真假值的真相
在大多数语言里,0 是假。在 Scheme 里,0 是真!只有#f是假。
例子 1:0 也是真
; 如果 0 是假,应该返回 "False",但实际返回 "True" (if 0 "True" "False") ; 结果:"True" ; 空字符串也是真 (if "" "True" "False") ; 结果:"True" ; 只有 #f 是假 (if #f "True" "False") ; 结果:"False"
2. 逻辑组合:and, or, not
它们可以接受任意多个参数,不仅仅是两个。
例子 2:and (与)
; 所有条件都为真,结果才为真。返回最后一个真值,或者第一个假值。
(and #t #t “全是真的”)
; 结果:“全是真的”(and #t #f “这就没了”)
; 结果:#f (遇到假就停止)(and (> 5 3) (< 5 10))
; 结果:#t
例子 3:or (或)
; 只要有一个为真,结果就为真。返回第一个真值,或者最后一个假值。
(or #f #f “终于有个真的了”)
; 结果:“终于有个真的了”(or #f 0 “0 也是真哦”)
; 结果:0 (注意:返回的是第一个真值本身,这里是 0)
例子 4:not (非)
(not #t)
; 结果:#f(not 0)
; 结果:#f (因为 0 是真,取反就是假)
3. 多重选择:cond
当if不够用时(比如要分 5 种情况),用cond。它是if-elif-else的超级版。
语法:
(cond (条件 1 结果 1) (条件 2 结果 2) ... (else 默认结果))例子 5:成绩分级
(define (get-grade score) (cond ((>= score 90) "A") ((>= score 80) "B") ((>= score 60) "C") (else "F"))) ; else 相当于默认情况 (get-grade 85) ; 结果:"B"注意:每个条件都要包在括号里
((>= score 90) "A"),别漏了内层括号!
例子 6:cond 的求值顺序
; cond 从上往下测,一旦命中就停止,后面的不再看。
(cond
((> 10 5) “第一个命中”)
((> 10 2) “这个虽然也对,但不会执行”))
; 结果:“第一个命中”
4. 结合昨天知识的综合应用
把define, 数学运算,if/cond, 逻辑符串起来。
例子 7:判断闰年 (经典逻辑题)
规则:能被 4 整除但不能被 100 整除,或者能被 400 整除。(define (is-leap-year? year) (or (and (= (remainder year 4) 0) (not (= (remainder year 100) 0))) (= (remainder year 400) 0))) (is-leap-year? 2000) ; #t (is-leap-year? 1900) ; #f (能被 4 整除,也能被 100 整除,但不能被 400 整除) (is-leap-year? 2024) ; #t注:
remainder是取余数函数,相当于%。
例子 8:符号比较 (eq?)
; 昨天学了符号 'apple,今天用来做状态机
(define (check-traffic-light color)
(cond
((eq? color 'red) “Stop”)
((eq? color 'yellow) “Wait”)
((eq? color 'green) “Go”)
(else “Unknown color”)))(check-traffic-light 'yellow)
; 结果:“Wait”*注意:比较符号常用 `eq?`,比较数字用 `=`,比较通用内容用 `equal?`。*
例子 9:嵌套逻辑
; 判断一个数是否是“正偶数”
(define (is-positive-even? n)
(and
(> n 0)
(= (remainder n 2) 0)))(is-positive-even? 4) ; #t
(is-positive-even? 3) ; #f
(is-positive-even? -2) ; #f
例子 10:利用短路特性避免错误
; 假设我们有一个函数只在数字大于 0 时才安全调用
; 如果直接写 (and (> n 0) (danger-func n))
; 当 n <= 0 时,danger-func 根本不会执行,程序不会崩。
(define (safe-divide a b)
(if (and (number? b) (not (= b 0)))
(/ a b)
“Error: Div by zero or not a number”))(safe-divide 10 2) ; 5.0
(safe-divide 10 0) ; “Error…”
📝 练习题 (仅限前两天知识)
请尝试在不使用列表(List)、递归或循环(明天学)的情况下完成。
1. 三角形类型判断器
编写函数triangle-type,接收三个边长a,b,c。
- 如果任意两边之和不大于第三边,返回
"Impossible"(构不成三角形)。 - 如果三边相等,返回
"Equilateral"(等边)。 - 如果只有两边相等,返回
"Isosceles"(等腰)。 - 否则返回
"Scalene"(不等边)。
提示:先用cond检查合法性,再检查相等性。
2. 简易计算器
编写函数simple-calc,接收三个参数:num1,op(符号),num2。
- 如果
op是'+',返回和。 - 如果
op是'-',返回差。 - 如果
op是'*',返回积。 - 如果
op是'/',且num2不为 0,返回商;否则返回字符串"DivByZero"。 - 其他操作符返回
"Unknown Op"。
测试:(simple-calc 10 '+ 5)-> 15,(simple-calc 10 '/' 0)-> “DivByZero”
3. 年龄组分类
编写函数age-group,接收年龄age。
- 0-12 岁:
"Child" - 13-19 岁:
"Teen" - 20-59 岁:
"Adult" - 60 岁以上:
"Senior" - 如果年龄小于 0 或大于 120,返回
"Invalid Age"。
4. 逻辑挑战:全真检测
编写函数all-true?,接收三个参数x,y,z。
只有当这三个参数全部为真(即都不等于#f)时,返回#t,否则返回#f。
注意:不要直接用and,尝试用if或cond实现逻辑,或者思考and本身的返回值特性(提示:题目要求返回严格的布尔值#t或#f,而and可能返回具体的值如 0 或字符串,你需要处理一下)。
⚠️ 常见坑
cond的括号地狱:- 错误:
(cond (条件 1 结果 1) (条件 2 结果 2))-> 这是错的!条件本身也要括号。 - 正确:
(cond ((条件 1) 结果 1) ((条件 2) 结果 2)) - 记忆法:
cond的每一项都是(测试 动作),而测试本身通常是个表达式如(> x 0),所以看起来是((> x 0) ...)。
- 错误:
else的位置:else必须放在cond的最后一项,且前面不能加条件判断,直接写(else 结果)。如果在else前加了条件(else (> x 0))是语法错误。
相等性判断的混淆:
- 数字比较用
=。 - 符号(如
'red)比较最好用eq?。 - 字符串或复杂结构比较用
equal?。 - 坑:
(= 'a 'a)会报错,因为=只能用于数字!
- 数字比较用
if缺少分支:- Scheme 的
if必须有两个分支(真和假),除非你确定永远为真。 - 错误:
(if (> x 0) "Positive")-> 如果 x 是负数,返回未定义值,可能导致后续计算出错。 - 补救:
(if (> x 0) "Positive" #f)或者直接用cond。
- Scheme 的
逻辑运算的返回值:
and和or返回的不是单纯的#t或#f,而是导致结果的那个值。(and 1 2 3)返回3。(or #f 0 "hi")返回0(因为 0 是真,且是第一个真值)。- 如果你需要严格的布尔值,记得最后包一层判断,比如
(if (or a b) #t #f)。
🚀 下一步预告
明天我们将进入 Scheme 最核心、最性感的部分:递归与列表 (Recursion & Lists)。
我们将抛弃变量赋值的思维,学习如何用“头 (car)"和“尾 (cdr)"像剥洋葱一样处理数据,并用递归代替for循环。准备好让你的大脑升级了吗?
现在,去挑战上面的练习题吧!如果有报错,记得看括号是不是又没数对~