[TOC]
Rust流程控制
表达式的多种形式
语句?表达式?
语句在英文中是 statement,表达式则是 expression。我们可能常常听说过“赋值语句”或者“算数表达式”这些名词,但是你有想过为什么不是“赋值表达式”吗?语句和表达式有一个重要的区别在于,表达式总是返回一个值,而语句不会。例如:
1 + 1; // 这是表达式
let a = 1; // 这是语句
Rust 是一个基于表达式的语言,这意味着它的大多数代码都有一个返回值。除了以下几种语法:
- 变量声明
- 模块声明
- 函数声明
- 结构体声明
- 枚举声明
- …
你可能会奇怪为什么 if…else… 不在上面的列表中,事实上,在 Rust 中,条件与循环并不是语句,而是表达式,这意味着它可以有返回值!这可能是你首先会疑惑的地方:这看起来和 C 不太一样!
if 表达式,实现类似 C 语言中的三元表达式的功能:
let cond = true;
let a = if cond {
42
} else {
24
};
loop 表达式的 break 语句后可跟着一个返回值返回:
let mut s = 0;
let mut n = 10;
let a = loop {
if n < 0 {
break s;
}
s += n;
n -= 1;
};
println!("{:?}", a);
if-else选择结构
Rust 中的 if-else 语法与其他语言类似,与许多语言不同,if 后的布尔条件不需要用括号括起来。
如果使用 if-else 返回一个值,则所有分支必须返回相同的类型:
fn main() {
let n = 5;
if n < 0 {
print!("{} is negative", n);
} else if n > 0 {
print!("{} is positive", n);
} else {
print!("{} is zero", n);
}
let m = if n < 0 {
2.0
} else {
3.0
};
println!("{}", m);
}
使用loop循环
Rust 提供了一个 loop 关键字来表示无限循环。
break 语句可用于随时退出循环,而 continue 语句可用于跳过其余的迭代并开始新的循环:
// 计算 1 + 2 + ... + 100
fn main() {
let mut sum = 0;
let mut n = 0;
loop {
sum += n;
n += 1;
if n > 100 {
break
}
}
println!("{}", sum);
}
break 后可带上一个值,返回给一个变量,例如:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
assert_eq!(result, 20);
}
上面这种写法一般用于重试操作。
使用while循环
while 是带循环条件的 loop。当条件为假时,结束循环。我们使用一个例子介绍 while 的语法。
fizzbuzz 是一个非常简单的编程任务,它的描述是:编写一个程序,打印从 1 到 100 的数字,对于 3 的倍数,打印 Fizz 而不是数字,对于 5 的倍数,打印 Buzz。
例如:
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14,
Fizz Buzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26,
Fizz, 28, 29, Fizz Buzz, 31, 32, Fizz, 34, Buzz, Fizz, ...
代码示例:
fn main() {
// A counter variable
let mut n = 1;
// Loop while `n` is less than 101
while n < 101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
// Increment counter
n += 1;
}
}
使用for_range进行迭代
Rust 中的 for … in … 语法可以用来遍历一个迭代器。有多种方式可以创建一个迭代器,最简单也是最常用的方式如下所示:
-
a..b
:这将创建一个包含 a 而不包含 b,步长为 1 的迭代器。 -
a..=b
:这将创建一个包含 a 且包含 b,步长为 1 的迭代器。
fn main() {
// 下面的代码将打印出 0, 1, 2, 3, 4
for i in 0..5 {
println!("{}", i);
}
// 下面的代码将打印出 0, 1, 2, 3, 4, 5
for i in 0..=5 {
println!("{}", i);
}
}
for … in … 语法的第二个重要使用场景是遍历数组,但这需要我们首先将数组转换为一个迭代器,这可以通过 .iter()
或 .iter_mut()
实现,区别在于后者是可变的。
fn main() {
let mut myarray = [1, 2, 3];
for i in myarray.iter() {
println!("{}", i);
}
for i in myarray.iter_mut() {
*i *= 2;
}
for i in myarray.iter() {
println!("{}", i);
}
}
Rust中的match
match 是 Rust 中的模式匹配语法,它允许开发者将一个值与一系列模式进行比较,然后根据模式匹配的结果执行特定的代码。它与其它语言中的 switch … case … 语法相近,但显然更加强大。在先前的课程中,我们已经知道 match 语法可以配合 enum 一起使用。
enum Alphabet {
A,
B,
}
fn main() {
let letter = Alphabet::A;
match letter {
Alphabet::A => {
println!("It's A");
}
Alphabet::B => {
println!("It's B")
}
}
}
另一方面,match 也经常用来匹配整型数据,例如当我们想知道一个 u8 整数是否是某几个特殊数字时:
fn main() {
let n: u8 = 42;
match n {
42 => {
println!("bingo!")
}
_ => {
println!("{}", n);
}
}
}
if_let语法糖
if let 是 Rust 中的一个语法糖,它主要简化了 match 操作。如果我们仅仅想当匹配发生时做某些操作,那么就可以使用 if let 替代 match。
例如当我们只想要变量 letter 为 A 时,打印消息,而忽略所有其它选项。可分别使用 match 或 if let 实现。
enum Alphabet {
A,
B,
}
fn main() {
let letter = Alphabet::A;
match letter {
Alphabet::A => {
println!("It's A");
}
_ => {}
}
if let Alphabet::A = letter {
println!("It's A");
}
}
if let 同样可以匹配带参数的枚举
enum Symbol {
Char(char),
Number,
}
fn main() {
let symbol = Symbol::Char('A');
if let Symbol::Char(char) = symbol {
println!("{:?}", char);
}
}
while_let语法糖
与 if let 相似的还有一个 while let 语法糖,只是 while let 语法糖很少被使用:
enum Alphabet {
A,
B,
}
fn main() {
let mut letter = Alphabet::A;
while let Alphabet::A = letter {
println!("It's A");
letter = Alphabet::B;
}
}
函数与方法
函数
函数的定义以 fn 开始,它的参数是带类型注释的,就像变量一样,如果函数返回值,则必须在箭头 ->
之后指定返回类型。例如如下的斐波那契函数:
fn fibonacci(n: u64) -> u64 {
if n < 2 {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
方法
方法是附加到对象的函数,这些方法可以通过 self 关键字访问对象及其其他方法的数据。方法在 impl 块下定义。访问对象中的方法有两种方式,如果方法带 self 参数,使用 .
,否则使用 ::
。示例:
#[derive(Debug)]
struct Point {
x: u64,
y: u64,
}
impl Point {
fn new(x: u64, y: u64) -> Point {
Point { x, y }
}
fn get_x(&self) -> u64 {
return self.x;
}
// 如果需要修改结构体中的数据, self 前面需要带上 mut
fn set_x(&mut self, x: u64) {
self.x = x;
}
}
fn main() {
let mut p = Point::new(1, 2);
println!("{:?}", p);
println!("{:?}", p.get_x());
p.set_x(3);
println!("{:?}", p.get_x());
}
函数与闭包
Rust 的闭包是一种匿名函数,它可以从它的上下文中捕获变量的值。闭包使用 || ->
语法定义。闭包可以被保存在变量中:
fn main() {
let myclosures = |n: u32| -> u32 { n * 3 };
println!("{}", myclosures(1))
}
move 关键字可以从闭包的运行环境中捕获值,它最常用的场景是将主线程中的一个变量传递到子线程中,如下所示:
use std::thread;
fn main() {
let hello_message = "Hello World!";
thread::spawn(move || println!("{}", hello_message)).join();
}
作业: 斐波那契数列
斐波那契数列(Fibonacci)在数学上以递归的方法来定义:
F(0) = 0
F(1) = 1
F(n) = F(n - 1) + F(n - 2)
用文字来说,就是斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。首几个斐波那契数是:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233
请编写程序,程序可输出斐波那契的第任意项数字。
高阶函数
在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入
- 输出一个函数
在数学中它们也叫做算子(运算符)或泛函。高阶函数是函数式编程中非常重要的一个概念。
将函数作为参数传递:
fn calc(method: fn(u32, u32) -> u32, a: u32, b: u32) -> u32 {
method(a, b)
}
fn add(a: u32, b: u32) -> u32 {
a + b
}
fn sub(a: u32, b: u32) -> u32 {
a - b
}
fn main() {
println!("{}", calc(add, 10, 20));
println!("{}", calc(sub, 20, 10));
}
将函数作为返回值:
fn calc(method: &str) -> fn(u32, u32) -> u32 {
match method {
"add" => add,
"sub" => sub,
_ => unimplemented!(),
}
}
fn add(a: u32, b: u32) -> u32 {
a + b
}
fn sub(a: u32, b: u32) -> u32 {
a - b
}
fn main() {
println!("{}", calc("add")(10, 20));
println!("{}", calc("sub")(20, 10));
}
发散函数
发散函数永远不会被返回,它们的返回值被标记为 !
,这是一个空类型。
fn foo() -> ! {
panic!("This call never returns.");
}
发散函数与空返回值函数不同,空返回值函数可以被返回:
fn some_fn() {
()
}
fn main() {
let a: () = some_fn();
println!("This function returns and you can see this line.")
}
发散函数最大的用处是通过 Rust 的类型检查系统,例如,在前面的小节中我们知道 Rust 的 if-else 表达式必须返回相同的类型, 但是如果使用发散函数,下面的代码也是能通过编译的:
fn foo() -> ! {
panic!("This call never returns.");
}
fn main() {
let a = if true {
10
} else {
foo()
};
println!("{}", a);
}
实践:猜数字游戏
这道题来自 Rust 的官方文档。我们将实现一个经典的初学者编程问题:猜谜游戏。它的工作原理是:程序将生成一个介于 1 和 100 之间的随机整数,然后提示玩家输入猜测。输入猜测后,程序将指示猜测是过低还是过高。如果猜测正确,游戏将打印一条祝贺信息并退出。
需要使用到的一些工具:
- 使用
rand
库生成随机数 - 使用
std::io
标准库获取用户输入
课程代码:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..101);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}