当前位置:网站首页>【Rust笔记】04-表达式

【Rust笔记】04-表达式

2022-06-22 05:45:00 phial03

04 - 表达式

4.1 - 表达式语言

  • 在 C 语言中,表达式有值,而语句没有。
// 表达式
5 * (fahr - 32/ 9

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

(1) if 和 match 可以产生值

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类型的数值
	};

(2) if 表达式可以用来初始化变量

let status = 
    if cpu.temperature <= MAX_TEMP {
    
        // 条件成立,那么赋值为HttpStatus::Ok
        HttpStatus::Ok
    } else {
    
        // 条件不成立,那么赋值为HttpStatus::ServerError
        HttpStatus::ServerError // 服务器错误
    };

(3) match 表达式可以作为参数传递给函数或宏

println!("Inside the vat, you see {}.",
    match vat.contents {
    
        Some(brain) => brain.desc(),
        None => "nothing of interest"
    }
);

(4) Rust 没有 C 语言的三元操作符(expr1 ? expr2 : expr3),完全可以通过上述实现代替。

4.2 - 块与分号

(1) 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()   // 结尾没有分号
    }
};

(2) 在 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 表达式

(1) 条件 condition 必须是一个 bool 型的表达式
(2) 条件 condition 可以不带圆括号。

if condition1 {
    
    block1
} else if condition2 {
    
    block2
} else {
    
    block_n
}
  • if 表达式中,所有的块 block 都必须产生相同类型的值

4.4.2-match

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

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

match code {
    
    0 => println!("OK"),
    1 => println!("Wires Tangled"),
    2 => println!("User Asleep"),
    _ => println!("Unrecognized Error {}", code)
}

(2) _表示通配模式,可以匹配任何值,相当于 C 语言 switch 语句的 default 条件。

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

(1) 解包(unpack)元组
(2) 匹配结构体的个别字段
(3) 追索引用
(4) 借用一个值的某一部分

  • match 表达式的通用语法形式:
match value {
    
    pattern => expr,
    ...
}

(1) 如果 expr 是一个块,后面的逗号可以去掉
(2) 所有模式中必须至少有一个匹配项
(3) 所有分支都必须返回相同类型的值

4.4.3-if let

  • if let 表达式:
if let pattern = expr {
    
    block1
} else {
    
    block2
}

(1) 实现了一种模式匹配,expr 要么匹配 pattern,然后运行 block1;

(2) 要么不匹配 pattern,而运行 block2。

(3) 常用于从 Option 或 Result 中取得数据:

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
}
  • 用于编写无穷循环。

(1) block 会永远重复执行
(2) 直到遇到一个退出条件:brea、return 或 线程诧异

4.5.4-for

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

  • 支持的集合有很多种:

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

for i in 0..20 {
    
    println!("{}", i);
}
// 0..20等价于std::ops::Range { start: 0, end: 20 }
// Range是可迭代类型

(2) 标准的集合,如数组和切片,都是可迭代的:每迭代一个值就用掉一个值

let strings: Vec<String> = error_message();
for s in strings {
    
    println!("{}", s);
}
println!("{} error(s)", strings.len());

(3) 迭代对集合的引用:循环变量就是对集合中每一项的引用

for rs in &strings {
    
    println!("String {:?} is at address {:p}.", *rs, rs);
}
// &strings的类型是&Vec<String>
// rs的类型是&String。

(4) 迭代 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 循环中:
    (1) continue 会前进到集合中的下一个值。
    (2) 如果没有值了,则退出循环。
  • 在 while 循环中:
    (1) continue 会再次检查循环条件。
    (2) 如果为假,则退出循环。

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) 分析:

(1) 检查贯穿函数的每条路径,确保返回值为正确类型;
(2) 检查局部变量永远不会在未初始化时被使用;
(3) 对无法抵达的代码给出警告。

  • 不正常结束的表达式,会被指定为特殊类型 !,它们不受其他类型需要尊总的规则约束。如 std::process::exit() 的函数签名中可以看到 !
fn exit(code: i32) -> !

! 意味着 exit() 永远不会返回,它是一个发散函数(divergent function)

4.8 - 函数与方法调用

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

(1) 静态方法通过类型调用,如 Vec::new();
(2) 非静态方法通过值调用,如 my_vec.len()。

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

(1) < 是一个小于操作符:

return Vec<i32>::with_capacity(1000); // 错误:需要进行链式比较

let ramp = (0..n).collection<Vec<i32>>();  // 错误,同上

(2) 针对此种情况,Rust 建议使用::<T>,而不是 <T>::

return Vec::<i32>::with_capacity(1000);

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

(3) ::<...> 称为极速鱼(turbofish)

(4) 也可以省略类型参数,让 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}

(1) 返回是 半开口(half-open) 的,包含起始值,不包含结尾值。
(2) 只有包含起始值的范围才是可迭代的,因为循环必须从某个地方开始。

  • 采用经典分治法实现快速排序:
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 操作符,实现类型转换。
  • 允许的类型转换:
    (1) 数值可以从任何内置的数值类型转换为任意其他类型。
    (2) boolchar 或类 C 的 enum 类型的值,可以转换为任何整数类型。
    (3) 有些涉及不安全指针类型的转换是允许的。
  • 把 mut 引用转换为非 mut 引用,会直接转换,不需要显示进行。
  • 以下是一些比较重要的自动转换,被称为解引用强制转换(deref corecion),适用于实现内置的 Deref 特型的类型。
    (1) &String 类型的值会自动换换为 &str 类型。
    (2) &Vec<i32> 类型的值会自动转换为 &[i32]
    (3) &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 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第六章
链接地址

原网站

版权声明
本文为[phial03]所创,转载请带上原文链接,感谢
https://blog.csdn.net/feiyanaffection/article/details/125390379