讲动人的故事,写懂人的代码
2.9 故事7: 玩家输入的数字若越界则继续猜
贾克强:“我们终于要一起写这个游戏的最后一个故事啦!游戏中,你需要猜两个骰子的点数之和,因此你猜的数字应该在2到12之间。我们可以在代码中加入一些判断逻辑,如果你猜的数字超出了这个范围,游戏会友好地提醒你,并请你继续猜。这个故事中的Rust代码又有一个新的知识点,所以我来帮你们写吧。“
2.9.1 Rust版故事7
@@ -22,7 +22,13 @@ fn main() {
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
- Ok(num) => num,
+ Ok(num) => {
+ if num < 2 || num > 12 {
+ println!("Please type a number between 2 and 12!");
+ continue;
+ }
+ num
+ }
Err(_) => {
println!("Please type a number!");
continue;
贾克强:“同学们,要判断玩家输入的数字是否超出范围,我们需要找到获取玩家数字输入的地方,也就是这个num
。”
- Ok(num) => num,
贾克强:“朋友们,我们现在需要判断num
是否越界,所以我们得用上花括号,创建一个代码块,在这个代码块里加上判断语句。”
“接下来,在 Ok(num)
这个分支里,我们新加了一个条件判断 if num < 2 || num > 12
。这个判断就是在检查 num
是否在 2 和 12 之间。”
“那么如果 num
小于 2 或者大于 12,代码就会友好地提示玩家"Please type a number between 2 and 12!"
,并通过 continue
让循环重新开始,让玩家有机会重新输入。而如果 num
在 2 和 12 之间,那么就说明玩家输入是有效的,num
就会被赋值给 guess
。”
席双嘉:“贾老师能解释一下那个孤零零的num
吗?”
贾克强:“铛铛!现在是一起来探索Rust的新知识点的时间!”
2.9.2 末尾不带分号的表达式即代码块返回值:更简洁的语法
贾克强:“让我们先把结果说出来,那个num
是代码块(block)中最后一个表达式,它没有分号,也就决定了整个代码块的返回值。这样的话,如果玩家输入的数字没有超过界限,那么这个num
就会成为match
表达式的返回值,然后绑定到guess
变量上。”
“这种设计真的非常简洁,不需要显式的 return
语句,减少了冗余的语法,让代码看起来更清晰,读起来也更顺畅。”
“相对来说,Java 和 C++ 通常都需要显式的 return
语句,即使在代码块的最后,也需要写上 return
,这就让代码变得更长。”
let guess: u32 = match guess.trim().parse() {
+ Ok(num) => {
+ if num < 2 || num > 12 {
+ println!("Please type a number between 2 and 12!");
+ continue;
+ }
+ num
+ }
“嘿,这里的 num
是 match
表达式中 Ok(num)
分支的一个临时变量,它代表成功解析出的数字。这个数字会在 Ok
分支内被我们仔细检查一下,确保它在指定的范围内哦。”
“在 Rust 中,块(block)就是用花括号 {}
包围的一段代码。这个块可以包含很多表达式或语句,但最后一个没有分号的表达式的值会作为整个块的返回值。理解这一点对于学习 Rust 的返回机制非常重要哦。记住是‘没有分号’的表达式哦。如果你在 num
后加了分号,编译器会报错的,会说没有找到 u32
的返回值。”
赵可菲:“那个分号在Rust里头是干嘛的啊?”
贾克强:“分号在 Rust 中可说是个小助手。它自己不算是个表达式,但作为一种语法符号,它能把表达式变成语句,在语法结构上帮我们标记语句结束的地方,还能帮我们控制代码块的执行顺序。“
”当表达式以分号做结尾时,Rust 编译器会把它看作是一条语句。 由于语句没有返回值,所以Rust 编译器会按照语法规则把以分号结尾的语句的代码块,处理为返回 ()
,也就是 Rust 的单元类型,这是一种没有实际数据的返回值类型。”
席双嘉:“在C++和Java里,我们可以自信地说,没有类似的块返回值的特性。在 C++ 和 Java 的常规代码块(如函数)中,返回值必须使用明确的 return
语句。在 lambda 表达式中,Java 只在单行 lambda 情况下支持隐式返回值,这时返回值可以不需要明确的 return
语句;但在多行 lambda 表达式中,必须使用明确的 return
语句来返回值。C++ 的 lambda 表达式,无论是单行还是多行,返回值都需要明确的 return
语句,没有隐式返回最后一个表达式的机制。”
贾克强:“嗯嗯。这就展示了三种语言不同的设计理念。Rust更注重表达式,而Java和C++则更看重语句。在Rust中,大部分结构都是表达式,它们可以返回值,这让代码变得更简洁,更灵活。而Java和C++则更注重语句,代码逻辑需要更明确地控制。”
艾极思用表格,记录了他们对于三种语言关于代码块返回值的讨论。
代码块返回值 | Rust | Java | C++ |
---|---|---|---|
块的定义 | 块 {} 是表达式,最后一个无分号的表达式决定块的返回值 | 块 {} 是语句,不返回值。返回值必须显式通过 return 语句 | (同左) |
返回值的机制 | 最后一个无分号的表达式的值成为块的返回值 | 块内返回值需使用 return 语句显式指定 | (同左) |
代码示例 | let x = { let y = 5; y + 1 }; // x = 6 | public int calculate() { int y = 5; return y + 1; } | int calculate() { int y = 5; return y + 1; } |
表达式 vs 语句 | 大多数结构都是表达式,可以返回值 | 主要是语句,返回值需通过 return 语句 | (同左) |
类似的特性 | 块的返回值可以直接用在变量赋值或其他表达式中 | Java 只在单行 lambda 情况下支持隐式返回值,这时返回值可以不需要明确的 return 语句;但在多行 lambda 表达式中,必须使用明确的 return 语句来返回值 | C++ 的 lambda 表达式,无论是单行还是多行,返回值都需要明确的 return 语句,没有隐式返回最后一个表达式的机制。 |
Lambda 表达式示例 | Rust 没有传统意义上的 lambda 表达式,而是提供了一种类似功能的构造,称为闭包(closure) | Function<Integer, Integer> addOne = (y) -> y + 1; | auto addOne = [](int y) { return y + 1; }; |
语法风格 | 允许块作为表达式,返回值可以直接使用。 | 需要显式返回,代码逻辑更为显式。 | (同左) |
代码简洁性 | 通过块的返回值特性,代码可以更简洁 | 返回值需要通过 return,显得更为冗长 | (同左) |
2.9.3 Java版故事7
赵可菲写好了Java版故事7。
@@ -30,6 +30,11 @@ public class App {
continue;