【Rust笔记】04-表达式

04 - 表达式

4.1 - 表达式语言

  • 在 C 语言中,表达式有值,而语句没有。

    // 表达式
    5 * (fahr - 32/ 9
    
    // 语句
    for (; begin != end; ++begin) {
        if (*begin == target)
            break;
    }
    
  • Rust 是表达式语言

    • ifmatch 可以产生值

      pixels[r * bounds.0 + c] = 
          match escapes(Complex { re: point.0, im: point.1 }, 255) {
              None => 0,                         // 如果匹配None,则值为0
              Some(count) => 255 - count as u8   // 如果匹配Some(count),则值为u8类型的数值
          };
      
    • if 表达式可以用来初始化变量

      let status = 
          if cpu.temperature <= MAX_TEMP {
              // 条件成立,那么赋值为HttpStatus::Ok
              HttpStatus::Ok
          } else {
              // 条件不成立,那么赋值为HttpStatus::ServerError
              HttpStatus::ServerError // 服务器错误
          };
      
    • match 表达式可以作为参数传递给函数或宏

      println!("Inside the vat, you see {}.",
          match vat.contents {
              Some(brain) => brain.desc(),
              None => "nothing of interest"
          }
      );
      
    • Rust 没有 C 语言的三元操作符(expr1 ? expr2 : expr3),完全可以通过上述实现代替。

4.2 - 块与分号

  • Rust 的代码块,同样也是表达式,可以产生值:在代码块的最后一行省略分号,就会让这个块产生一个值

    let display_name = match post.author() {
        Some(author) => author.name(),
        None => {
            let network_info = post.get_network_metadata()?;
            let ip = network_info.client_address();
            ip.to_string()   // 结尾没有分号
        }
    };
    
  • 在 Rust 中,分号是有特殊意义的:

    • 分号可以使得代码块既可以包含声明,又可以在末尾产生值
    let msg = {
        // let声明:分号是必须有的
        let dandelion_control = puffball.open();
    
        // 表达式 + 分号:方法调用,返回值被清除
        dandelion_control.release_all_seeds(launch_codes);
    
        // 表达式不带分号:方法被调用,返回值保存于msg中
        dandelion_control.get_status()
    };
    
    • 技巧:当编译时,只要看到 expected type '() ,',首先看看是不是漏掉了分号。

    • 空语句,就是一个孤立的分号:

      loop {
          work();
          paly();
          ;  // 空语句
      }
      

4.3 - 声明

  • let 常用于声明变量,语法为:

    let name: type = expr;
    // 类型type和初始值expr是可选的,分号是必须的。
    
  • let 声明可以只声明变量而不初始化它。之后可以通过赋值来初始化变量。这种情况常用于控制流结构中,可以在中间操作初始化变量:

    let name;  // 只声明了变量
    if user.has_nickname() {     // 通过if条件语句,为变量赋值初始化
        name = user.nickname();
    } else {
        name = generate_unique_name();
        user.register(&name);
    }
    
  • 在初始化之前使用变量是错误的。

  • Rust 支持重新声明一个已有变量:

    for line in file.lines() {
        let line = line?;
    }
    
    • 上述代码等价于:

      for line_result in file.lines() {
          let line = line_result?;  // ?用于检查可能失败的函数调用
      }
      // _result作为后缀,使得变量的类型是Result<String, io::Error>
      // 而第二个变量line的类型是String
      
  • 块里也可以包含特性项声明:可以在程序或模块的全局中出现的声明,比如 fnstructuse

4.4-if 与 match

4.4.1-if

  • if 表达式

    • 条件 condition 必须是一个 bool 型的表达式
    • 条件 condition 可以不带圆括号。
    if condition1 {
        block1
    } else if condition2 {
        block2
    } else {
        block_n
    }
    
  • if 表达式中,所有的块 block 都必须产生相同类型的值

4.4.2-match

  • match 表达式,等同于 C 中的 switch 语句

    • 可以有多个表达式分支,但是只有 1 个会执行

      match code {
          0 => println!("OK"),
          1 => println!("Wires Tangled"),
          2 => println!("User Asleep"),
          _ => println!("Unrecognized Error {}", code)
      }
      
    • _表示通配模式,可以匹配任何值,相当于 C 语言 switch 语句的 default 条件。

  • match 表达式支持模式,常用于:

    • 解包(unpack)元组
    • 匹配结构体的个别字段
    • 追索引用
    • 借用一个值的某一部分
  • match 表达式的通用语法形式:

    match value {
        pattern => expr,
        ...
    }
    
    • 如果 expr 是一个块,后面的逗号可以去掉
    • 所有模式中必须至少有一个匹配项
    • 所有分支都必须返回相同类型的值

4.4.3-if let

  • if let 表达式:

    if let pattern = expr {
        block1
    } else {
        block2
    }
    
    • 实现了一种模式匹配,expr 要么匹配 pattern,然后运行 block1

    • 要么不匹配 pattern,而运行 block2

    • 常用于从 OptionResult 中取得数据:

      if let Some(cookie) = request.session_cookie {
          return restore_session(cookie);
      }
      
      if let Err(err) = present_cheesy_anti_robot_task() {
          log_robot_attempt(err);
          politely_accuse_user_of_being_a_robot();
      } else {
          session.mark_as_human();
      }
      
  • if let 表达式是对只有一个模式的 match 表达式的简写:

    match expr {
        pattern => { block1 }
        _ => { block2 }
    }
    

4.5 - 循环

  • 循环在 Rust 中是表达式,但它不会产生有意义的值。
  • 循环的值是基元 ()

4.5.1-while

  • 语法:

    while codition {
        block
    }
    
  • codition 必须是 bool 类型。

4.5.2-while let

  • 语法:

    while let pattern = expr {
        block
    }
    
  • if let 类似,在每次循环开始时,expr 的值要先匹配给定的 pattern,再运行后面的块;如果不匹配,那么会退出循环。

4.5.3-loop

  • 语法:

    loop {
        block
    }
    
  • 用于编写无穷循环

    • block 会永远重复执行
    • 直到遇到一个退出条件:
      • break
      • return
      • 线程诧异

4.5.4-for

  • 语法:

    for pattern in collection {
        block
    }
    
  • 先对 collection 表达式求值,然后该集合中每个值必须对 block 求值

  • 支持的集合有很多种:

    • .. 操作符:产生一个范围(range),即一个拥有两个字段(start 和 end)的简单结构体。

      for i in 0..20 {
          println!("{}", i);
      }
      // 0..20等价于std::ops::Range { start: 0, end: 20 }
      // Range是可迭代类型
      
    • 标准的集合,如数组和切片,都是可迭代的:每迭代一个值就用掉一个值

      let strings: Vec<String> = error_message();
      for s in strings {
          println!("{}", s);
      }
      println!("{} error(s)", strings.len());
      
    • 迭代对集合的引用:循环变量就是对集合中每一项的引用

      for rs in &strings {
          println!("String {:?} is at address {:p}.", *rs, rs);
      }
      // &strings的类型是&Vec<String>
      // rs的类型是&String。
      
    • 迭代 mut 引用:循环变量拿到的也是 mut 引用

      for rs in &mut strings {  // rs的类型是&mut String
          rs.push('\n');        // 给每个字符串添加一个换行符
      }
      

4.5.5-break

  • break 表达式用于退出闭合循环。
  • 只能在循环中使用,match 表达式中用不到 break

4.5.6-continue

  • continue 表达式用于跳到循环的下一次迭代:

    // 读取数据,每次读一行
    for line in input_lines {
        let trimmed = trim_comments_and_whitespace(line);
        if trimmed.is_empty() {
            // 跳到循环顶部,取得输入的下一行
            continue;
        }
        ...
    }
    
  • for 循环中:

    • continue 会前进到集合中的下一个值。
    • 如果没有值了,则退出循环。
  • while 循环中:

    • continue 会再次检查循环条件。
    • 如果为假,则退出循环。

4.5.7 - 循环的生命期

  • 循环可以加上生命期标签

    'search:  // 外部for循环的生命期标签
    for room in apartment {
        for spot in room.hiding_spots() {
            if spot.contains(keys) {
                println!("Your keys ar {} in the {}.", spot, room);
                break 'search;  // 此处表示break会退出外部循环,而不是内部循环。
            }
        }
    }
    
  • 生命期标签也可以与 continue 一起使用

4.6-return 表达式

  • return 表达式可以退出当前函数,并向调用者返回一个值。

  • 无值 return 表达式是 return() 函数的简写:

    fn f() {       // 省略返回类型:默认为()
        return;    // 省略返回值:默认为()
    }
    
  • return 也可以结束当前的工作,等同于 break

  • ? 操作符:检查可能失败的函数调用。

    let output = File::create(filename)?;
    
    • 等价于如下 match 表达式代码:

      let output = match File::create(filename) {
          Ok(f) => f,    // 匹配后,f会保存在output中
          Err(err) => return Err(err)
      };
      

4.7-Rust 的循环特点

  • Rust 编译器分析控制流的机制 —— 流敏感(flow-sensitive)分析

    • 检查贯穿函数的每条路径,确保返回值为正确类型;
    • 检查局部变量永远不会在未初始化时被使用;
    • 对无法抵达的代码给出警告。
  • 不正常结束的表达式,会被指定为特殊类型 !,它们不受其他类型需要尊总的规则约束。如 std::process::exit() 的函数签名中可以看到 !

    fn exit(code: i32) -> !
    
    • ! 意味着 exit() 永远不会返回,它是一个发散函数(divergent function)

4.8 - 函数与方法调用

  • 静态方法与非静态方法的区别:

    • 静态方法通过类型调用,如 Vec::new()
    • 非静态方法通过值调用,如 my_vec.len()
  • 方法可以链式调用:

    Iron::new(router).http("localhost:3000").unwrap();
    
  • 用于函数调用或方法调用的语法,不能用于泛型 Vec<T>

    • < 是一个小于操作符:
    return Vec<i32>::with_capacity(1000); // 错误:需要进行链式比较
    let ramp = (0..n).collection<Vec<i32>>();  // 错误,同上
    
    • 针对此种情况,Rust 建议使用::<T>,而不是 <T>:

      return Vec::<i32>::with_capacity(1000);
      let ramp = (0..n).collection::<Vec<i32>>();
      
    • ::<...> 称为极速鱼(turbofish)

    • 也可以省略类型参数,让 Rust 编译器自行推断【推荐方法】:

      return Vec::with_capacity(10);
      let ramp: Vec<i32> = (0..n).collection();
      

4.9 - 字段与元素

  • . 操作符左侧的值,如果是一个引用或智能指针类型,那么它就会跟方法调用一样自动解引用。

  • 方括号常用于访问数组、切片或向量中的元素:

    pieces[i]  // 数组元素
    
    • 方括号左侧的值会自动解引用。
  • .. 范围操作符允许省略两侧的操作数。

    ..   // 表示全部范围;
    a..  // 表示起始于a:{start: a}
    .. b  // 表示终止与b-1:{end: b}
    a.. b // 表示范围>=a,<b:{start: a, end: b}
    
    • 返回是 ** 半开口(half-open)** 的,包含起始值,不包含结尾值。
    • 只有包含起始值的范围才是可迭代的,因为循环必须从某个地方开始。
  • 采用经典分治法实现快速排序

    fn quicksort<T: ord>(slice: &mut [T]) {
        if slice.len() <= 1 {
            return;
        }
    
        // 将切片分成前、后两部分
        let pivot_index = partition(slice);
    
        // 递归对slice的前半部分,进行排序
        quicksort(&mut slice[.. pivot_index]);
    
        // 递归对slice的后半部分,进行排序
        quicksort(&mut slice[pivot_index + 1 ..]);
    }
    

4.10 - 引用操作符

  • 取地址操作符:&&mut
  • 一元操作符 *:用于访问引用指向的值。只需读或写引用指向的整个值。
  • . 点操作符会自动跟踪引用去访问字段或方法。

4.11 - 其他操作符

4.11.1 - 算术

算数操作符说明
+
-
*
/
%取模,或取余

4.11.2 - 位

位操作符说明
&位与
|位或
^位异或
位非
<<左移
>>右移
  • 不能用 !n 表示 “n 是 0”,需要写成 n == 0
  • 位操作的优先级高于比较操作。

4.11.3 - 比较

比较操作符说明
==等于
!=不等于
<小于
>大于
<=小于等于
>=大于等于

4.11.4 - 逻辑

  • 短路逻辑操作符的操作数,必须都是 bool 类型。
短路逻辑操作符说明
&&逻辑与
||逻辑或
!逻辑非

4.11.5 - 赋值

  • = 赋值操作符,用于把值赋给 mut 变量,以及他们的字段或元素。

  • 变量默认是不可修改的。

  • 赋值会转移非可赋值类型的值。

  • Rust 支持复合赋值:

    +=
    -=
    *=
    /=
    %=
    <<=
    >>=
    &=
    ^=
    |=
    
  • Rust 不支持链式赋值,如 C 语言中的 a=b=3

  • Rust 没有 C 语言中的递增操作符 ++ 和递减操作符 --

4.12 - 类型转换

  • Rust 中,将一个值从一种类型,转换为另一种,需要做显示转换。

  • as 操作符,实现类型转换。

  • 允许的类型转换:

    • 数值可以从任何内置的数值类型转换为任意其他类型。
    • boolchar 或类 C 的 enum 类型的值,可以转换为任何整数类型。
    • 有些涉及不安全指针类型的转换是允许的。
  • mut 引用转换为非 mut 引用,会直接转换,不需要显示进行。

  • 以下是一些比较重要的

    自动转换

    ,被称为

    解引用强制转换(deref corecion)

    ,适用于实现内置的

    Deref
    

    特型的类型。

    • &String 类型的值会自动换换为 &str 类型。
    • &Vec<i32> 类型的值会自动转换为 &[i32]
    • &Box<Chessboard> 类型的值会自动转换为 &Chessboard

4.13 - 闭包

  • Rust 的闭包,类似轻量级函数,通常由参数列表(在两条竖线中给出)和表达式组成:

    let is_even = |x| x % 2 == 0;
    
  • Rust 会推断闭包的参数类型和返回类型。

  • 如果明确指定了返回类型,那么闭包体必须是一个代码块(用 {} 括起来)。

    let is_even = |x: u64| -> bool x % 2 == 0;     // 错误
    
    let is_even = |x: u64| -> bool {x % 2 == 0};   // 可以
    
  • 调用闭包,与调用函数的语法相同:

    assert_eq!(is_even(14), true);
    

4.14 - 优先级

  • 优先级列表,按照从高到低列出:

    表达式类型举例
    数组字面量[1, 2, 3]
    重复的数组字面量[0; 50]
    元组(6, "crullers")
    分组(2 + 2)
    {f(); g()}
    控制流表达式if ok {f();}
    宏调用println!("ok");
    路径std::f64::consts::PI
    结构体字面量Point {x: 0, y: 0}
    元组字段存取pair.0
    结构体字段存取point.x
    方法调用point.translate(50, 50)
    函数调用stdin()
    索引arr[0]
    错误检查create_dir("tmp")?
    逻辑 / 按位非!ok
    取反-num
    解引用*ptr
    借用&val
    类型转换x as u32
    n * 2
    n / 2
    取余(取模)n % 2
    n + 2
    n - 2
    左移n << 1
    右移n >> 1
    按位与n & 1
    按位异或n ^
    按位或`n
    小于n < 1
    小于等于n <= 1
    大于n > 1
    大于等于n >= 1
    等于n == 1
    不等于n != 1
    逻辑与x.ok && y.ok
    逻辑或`x.ok
    范围start.. stop
    赋值x = val
    复合赋值x += 1
    闭包`
  • 所有上述操作符,在链式操作时,都具有左关联性。

  • 比较操作符、赋值操作符和范围操作符.. 不能执行链式操作


详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第六章
链接地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

phial03

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值