rust学习 -- 第三章 语句和表达式

202 阅读7分钟

第三章 语句和表达式

表达式(Expression)语句(Statement)

语句和表达式的区分方式是后面带不带分号; . 如果带了分号, 意味着这是一条语句,它的类型是 (); 如果不带分号, 它的类型就是表达式的类型.

形式分号是否产生值
表达式不带分号产生值
语句带分号不产生值

语句

一个Rust程序, 是从main函数开始执行的. 而函数体内, 则是由一条条语句组成的.

表达式可以是语句的一部分, 反过来, 语句也可以是表达式的一部分. 一个表达式总是会产生一个, 因此它必然有类型; 语句不产生值, 它的类型永远是() . 如果把一个表达式加上分号, 那么它就变成了一个语句; 如果把语句放到一个语句块中包起来, 那么它就可以被当成一个表达式使用.

表达式

每种表达式都可以嵌入到另外一种表达式中, 组成更强大的表达式.

Rust的表达式包括字面量表达式方法调用表达式数组表达式索引表达式单目运算符表达式双目运算符表达式等.

Rust表达式又可以分为左值(lvalue) 和右值(rvalue) 两类. 左值接收值,右值产生值.

运算符符号说明
算术运算符+ - * / %
比较运算符== != < > <= >=不支持连续比较a==b==c
位运算符! &^ << >>
逻辑运算符&&!支持短路运算
赋值运算符=Rust规定, 赋值表达式的类型为unit, 即空的tuple()

不支持 ++ -- 运算符 使用 +=1 -=1代替

语句块表达式

// 语句块可以是表达式,注意后面有分号结尾,x的类型是()
let x : () = { println!("Hello."); };
// Rust将按顺序执行语句块内的语句,并将最后一个表达式类型返回,y的类型是 i32
let y : i32 = { println!("Hello."); 5 };
​
// 利用这样的特性来写返回值
fn my_func() -> i32 {
    // 各种语句
    100  // 返回100
}

if else

if xxx {}
else if xxx {}
else {}

if 后续的结果语句块要求一定要用大括号包起来, 不能省略, 以便明确指出该if语句块的作用范围. 这个规定是为了避免“悬空else”导致的bug.

  • if表达式的条件必须是bool类型的.不能是一个可以判定为真的值
  • if表达式中,与条件相关联的代码块叫做分支(arm)
  • 可选择在后面加上else表达式

if 也是一个表达式

fn main()
{
    let condition = true;
    let number = if condition { 5 } else { 6 }; // 注意这两个块中的数字,可以被返回
    println!("the value of number is: {}", number);
}

但是这种需要注意的是两个分支的表达式返回的类型必须一致,否则就会造成编译错误.如果else分支省略掉了, 那么编译器会认为else分支的类型默认为().

语句块中的变量也是有作用域的, 每个变量都只在自己的作用域内有效

let (max, min) = if number1 > number2 {
    (number1, number2)
} else {
    (number2, number1)
}
​
//如果像其他语言一样如下使用是不行的
let max = 0;
let min = 0;
if number1 > number2{
    max = number1;  // -->max和min改变的值只在此作用域内有效
    min = number2;  // -->
} else {
    max = number2;  // -->max和min改变的值只在此作用域内有效
    min = number1;  // -->
}
//使用max/min.  已经离开作用域

loop

使用loop表示一个无限死循环. 类似于while true

我们可以使用continuebreak控制执行流程. continu: 语句表示本次循环内, 后面的语句不再执行, 直接进入下一轮循环.break: 语句表示跳出循环, 不再继续.

另外, break语句和continue语句还可以在多重循环中选择跳出到哪一层的循环.

fn main() {
    // A counter variable
    let mut m = 1;
    let n = 1;
​
    'a: loop {
        if m < 100 {
            m += 1;
        } else {
            'b: loop {
                if m + n > 50 {
                    println!("break");
                    break 'a;
                } else {
                    continue 'a;
                }
            }
        }
    }
}

我们可以在loop while for循环前面加上“生命周期标识符”. 该标识符以单引号开头, 在内部的循环中可以使用break语句选择跳出到哪一层.

与if结构一样, loop结构也可以作为表达式的一部分.

在loop内部break的后面可以跟一个表达式, 这个表达式就是最终的loop表达式的值. 如果一个loop永远不返回, 那么它的类型就是“发散类型”.

fn main() {
    let v = loop {
        break 10;
    };
    println!("{}", v);
}
​
fn main() {
    let v = loop {};
    println!("{}", v);
}

while

while语句是带条件判断的循环语句. 其语法是while关键字后跟条件判断语句, 最后是结果语句块.

fn main() {
    let mut n = 1;
​
    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);
        }
        n += 1;
    }
}

while语句中也可以使用continue和break来控制循环流程.

loop{}和while true{}循环有什么区别?

loop和while true语句在运行时没有什么区别, 它们主要是会影响编译器内部的静态分析结果.

let x;
loop { x = 1; break; }
println!("{}", x)
// 以上语句在Rust中完全合理. 因为编译器可以通过流程分析推理出x=1; 必然在println! 之前执行过, 因此打印变量x的值是完全合理的.let x;
while true { x= 1; break; }
println!("{}", x);
// 上面的语句会有错误, while语句的执行跟条件表达式在运行阶段的值有关, 因此它不确定x是否一定会初始化, 于是它决定给出一个错误:use of possibly uninitialized variable, 也就是说变量x可能没有初始化.

for循环

rust的for循环不是 C的那种三段式的,而是像python那样的for循环.

for循环的主要用处是利用迭代器对包含同样类型的多个元素的容器执行遍历, 如数组、 链表、 HashMap、 HashSet等.

fn main () {
    let array = &[1,2,3,4,5];
​
    for i in array {
        println!("The number is {}", i);
    }
}

使用 for 时推荐使用集合的引用形式, 否则会导致数据的所有权移动到for语句块中.

使用方法等价方法所有权
for item in collectionfor item in IntoIterator::into_iter(collection)所有权转移
for item in &collectionfor item in collection.iter()不可变借用
for item in &mut collectionfor item in collection.iter_mut()可变借用

match

简单的match匹配

enum Direction {
    East, West, South, North
}
​
fn print(x: Direction)
{
    match x {
        Direction::East => {
            println!("East");
        }
        Direction::West => {
            println!("West");
        }
        Direction::South => {
            println!("South");
        }
        Direction::North => {
            println!("North");
        }
    }
}
​
fn main(){
    let x = Direction::East;
    print(x);
}

使用match匹配是要穷尽所有可能的, 但是我们可能把每一种可能都写出来,如果有很多可能性的话. 此时就需要使用 _ 分支

    match x {
        Direction::East => {
            println!("East");
        }
        Direction::West => {
            println!("West");
        }
        _ => {
            println!("other");
        }
    }

如果在 Direction又加入了项目, 之后引用Direction的代码会有问题, 所以有一个non_exhaustive的功能. 会强制要求使用Direction的代码使用_.

#[non_exhaustive]
enum Direction {
    East, West, South, North
}

match也可以变成表达式, 只要放在大括号中.

match 也可以匹配数值, 而且可以用 | 来匹配多个条件.

fn category(x: i32) {
    match x {
        -1 | 1 => println!("true"),
        0 => println!("false"),
        _ => println!("error"),
    }
}
fn main() {
    let x = 1;
    category(x);
}

还可以使用范围作为匹配条件, 使用..表示一个前闭后开区间范围, 使用..= 表示一个闭区间

let x = 'x';
match x {
    'a' ..= 'z' => println!("lowercase"),
    'A' ..= 'Z' => println!("uppercase"),
    _ => println!("something else"),
}

变量绑定

使用@符号绑定变量.@符号前面是新声明的变量,后面是需要匹配的模式:

let x = 1;
match x {
    e @ 1 ..= 5 => println!("got a range element {}", e), // 匹配到1-5 让后赋值给e
    _ => println!("anything"),
}

如果想要匹配更深的内容, 希望绑定上面一层的数据.

fn deep_match(v: Option<Option<i32>>) -> Option<i32> {
    match v {
        Some(r @ Some(1..=10)) => r,
        _ => None,
    }
}
​
fn main() {
    let x = Some(Some(5));
    println!("{:?}", deep_match(x));
​
    let y = Some(Some(100));
    println!("{:?}", deep_match(y));
}

如果在使用@的同时使用|,需要保证在每个条件上都绑定这个名字

let x = 5;
match x {
    e @ 1..5 | e @ 8..10 => println!("got a rang elemnt {}", e),
    _ => println!("anything"),
}

ref和mut

如果我们需要绑定的是被匹配对象的引用,则可以使用ref关键字:

let x = 5_i32;
match x {
    ref y => println!("Got a reference to {}", y), // 此时y的类型是 `&i32`
}

之所以在某些时候需要使用ref,是因为模式匹配的时候有可能发生变量的所有权转移,使用ref就是为了避免出现所有权转移.

let x =     5_i32;  // i32
let x =     &5_i32; // &i32
let ref x = 5_i32;  // &i32  ref 更像是放在赋值符号左边的  &
let ref x = &5_i32; // &&i32

ref 是模式的一部分 只能出现在赋值符号左边, & 是借用运算符, 是表达式的一部分, 只能出现在赋值符号右边.

mut

let mut x: &mut i32;
//   ^1     ^2

第1处mut,代表这个变量x本身可变,因此它能够重新绑定到另外一个变量上去, x 可以绑定到其他变量上.

第2处mut,修饰的是指针,代表这个指针对于所指向的内存具有修改能力,因此我们可以用*x=1;这样的语句,改变它所指向的内存的值(改变指针指向的值).

fn main() {
    let mut x: Option<String> = Some("hello".into());
    match x {   // => match &mut x {
        Some(i) => i.push_str("world"),  //编译报错. 因为i不可变.
        None => println!("None"),
    }
    println!("{:?}", x);
}

if let / while let

if let 是替代如下代码的简便模式.

match optVal {
    Some(x) => {
        doSomethingWith(x);
    }
    _  => {}
}
// ==============================
if let Some(x) = optVal {
    doSomethingWith(x);
}

其背后执行的代码与match表达式相比, 并无效率上的差别. 它跟match的区别是: match一定要完整匹配, if-let只匹配感兴趣的某个特定的分支.

while-let与if-let一样, 提供了在while语句中使用“模式解构”的能力.

if-let和while-let还支持模式的“或”操作.

enum E<T> {
    A(T), B(T), C, D, E, F
}
// 如果需要匹配 C 或者D
let  r = if let C | D = x{1} else {2};
// 这段代码等同于
let r = match x {
    C | D => 1,
    _ => 2,
}

在这个匹配过程中还可以有变量绑定, 比如:

while let A(x) | B(x) = expr {
    do_something(x);
}
// ===
match expr {
    A(x) | B(x) => do_something(x);
    _ => {},
}