25.模式和匹配

一、概念

  • 模式是 Rust 中特殊的语法,它用来匹配类型中的结构,一般由以下内容组成
    • 字面量、变量、通配符和占位符
    • 解构的数组、枚举、结构体或者元组

二、模式的位置

2.1 match分支

  • match 表达式由match 关键字用于匹配的值和一个或多个分支构成;
  • match 表达式必须是 穷尽(exhaustive) 的;
  • 通配符_可以匹配所有情况,它从不绑定任何变量;

2.2 if let表达式

  • if let可以对应一个可选的带有代码的else在if let中的模式不匹配时运行;
  • 下述示例代码组合并匹配 if let、else if 和 else if let 表达式,这比单一的match匹配更加灵活;
fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {}, as the background", color);
    } else if is_tuesday {
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}
  • 根据不同的条件设置背景色;

2.3 while let条件循环

  • 只要模式匹配就一直执行while的内部函数体;
fn main() {
    let mut stack = Vec::new();

    stack.push(1);
    stack.push(2);
    stack.push(3);

    while let Some(top) = stack.pop() {
        println!("{}", top);
    }
}
  • pop 方法取出vector的最后一个元素并返回 Some(value);
  • 如果 vector 是空的,它返回 None,一旦其返回 None,while 循环停止;
  • while循环只要pop返回Some就会一直运行其块中的代码;
    在这里插入图片描述

2.4 for循环

  • for循环的模式是for关键字直接跟随的值,例如for x in y 中的 x;
let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}

在这里插入图片描述

  • enumerate方法适配一个迭代器产生一个值和其在迭代器中的索引,位于一个元组中;

2.5 let语句

fn main() {
    let x = 5;
    let (x, y, z) = (1, 2, 3);
}

2.6 函数参数

  • 参数部分就是模式,例如下面的x
  • 可以在函数参数中匹配元组;
fn foo(x: i32) {
    // 代码
}

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
    foo(3);
}

三、模式是否会匹配失效

  • 模式有两种形式:refutable(可反驳的)和 irrefutable(不可反驳的);
  • 不可反驳的指能匹配任何传递的可能值的模式;
    • 函数参数、 let 语句和 for 循环只能接受该模式;
    • 例如let x = 5;
  • 可反驳的 指对某些可能的值进行匹配会失败的模式;
    • if letwhile let表达式被限制为只能接受该模式;
    • 例如if let Some(x) = a_value 表达式中的 Some(x);
      • 如果变量 a_value 中的值是None而不是Some,那么 Some(x) 模式不能匹配;
fn main() {
    let Some(x) = Some(5);
}
  • 编译时会报错:在要求不可反驳模式的地方使用可反驳模式;
    在这里插入图片描述
  • 使用if let修复上面的问题;
    • 如果模式不匹配,大括号中的代码将被忽略,其余代码保持有效;
fn main() {
    if let Some(x) = Some(5){
        println!("{}", x);
    }
}
  • 如果为if let提供了一个总是会匹配的模式,编译器会产生警告;
fn main() {
    if let t = 5 {
        println!("{}", t);
    }
}
  • 编译结果如下

在这里插入图片描述

四、模式语法

4.1 匹配字面量

  • 可以配置整数或字符的单模式;
  • 可以通过|匹配多个模式
  • 可以通过a..=b匹配值为 [a,b] 的范围(范围只能用数字或char);
fn main() {
    let x = 5;
    
    match x {
        1 => println!("one"),
        2 | 3 => println!("two or three"),
        4..=10 => println!("four --> ten"),
        _ => println!("anything"),
    }

    let y = 'j';
    match y {
        'a'..='i' => println!("a ---> i"),
        'j' => println!("j"),
        'k'..='z' => println!("k ---> z"),
        _ => println!("something else"),
    }
}

4.2 匹配命名变量

  • 命名变量是匹配任何值的不可反驳模式;
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {:?}", y),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {:?}", x, y);
}
  • 代码声明了一个值为 Some(5) 的变量 x 和一个值为 10 的变量 y;
  • 接着在值 x 上创建了一个 match 表达式;
  • 第一个匹配分支的模式并不匹配 x 中定义的值;
  • 第二个匹配分支中的模式引入了一个新变量 y,它会匹配任何 Some 中的值;
    • y在 match 表达式的新作用域中,是一个新变量,而必非y=10中的y;
    • Some(y)中的 y 会匹配任何 Some 中的值(在这里是 x 中的值);
    • 这个 y 绑定了 x 中 Some 内部的值,此分支会成功执行;
  • 如果 x 的值是 None则会匹配下划线;
    • 此时_println!中的x还是外部的x,因此会输出Default case, x = None
  • match执行完毕,内部变量的作用域都结束了,因此最后的 println! 会打印at the end: x = Some(5), y = 10

4.3 解构并分解值

  • 使用模式来解构结构体、枚举、元组和引用;

1)解构结构体

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;
    let Point { x, y } = p;
    
    println!("a = {}, b = {}", a, b);
    println!("x = {}, y = {}", x, y);

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y),
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}
  • let Point { x: a, y: b } = p创建了变量 a 和 b 来匹配结构体 p 中的 x 和 y 字段;
    • 模式中的变量名不必与结构体中的字段名一致;
  • let Point { x, y } = p创建了变量 x 和 y 来匹配结构体 p 中的 x 和 y 字段;
    • 模式中的变量名与结构体中的字段名一致 (与顺序无关),自动匹配值;
  • 使用字面量作为结构体模式的一部分进行解构;

在这里插入图片描述

2)解构枚举

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.")
        }
        Message::Move { x, y } => {
            println!(
                "Move in the x direction {} and in the y direction {}",
                x,
                y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
    }
}
  • Message::Quit没有任何数据的枚举成员,只能匹配字面量;
  • 类结构体枚举成员Message::Move,采用类似于匹配结构体的模式;
  • 包含一个或三个元素的Message::WriteMessage::ChangeColor类元组枚举成员,类似于解构元组的模式;

3)解构嵌套的结构体和枚举

enum Color {
   Rgb(i32, i32, i32),
   Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!(
                "Change the color to hue {}, saturation {}, and value {}",
                h,
                s,
                v
            )
        }
        _ => ()
    }
}

4)解构结构体和元组

  • 用复杂的方式来混合、匹配和嵌套解构模式;
struct Point{
    x: i32,
    y: i32,
}
 
 fn main() {
    let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
    println!("feet = {}", feet);
    println!("inches = {}", inches);
    println!("x = {}", x);
    println!("y = {}", y);
 }

运行结果
在这里插入图片描述

4.4 忽略模式中的值

1)使用_忽略整个值

fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

fn main() {
    foo(3, 4);
}

2)使用嵌套的_忽略部分值

  • 可以在一个模式内部使用 _ 忽略部分值;
fn main() {
    let mut setting_value = Some(5);
    let new_setting_value = Some(10);
    
    match (setting_value, new_setting_value) {
        (Some(_), Some(_)) => {
            println!("Can't overwrite an existing customized value");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }
    
    println!("setting is {:?}", setting_value);
}
  • match匹配第一个条会忽略所有的Some值,只有当其中某个为None时才会匹配到第二个分支;

也可以忽略部分特定值

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {}, {}, {}", first, third, fifth)
        },
    }
}
  • 忽略了numbers元组中的4和16;

3)在名字前以下划线开头忽略未使用的变量

  • 一般情况下创建一个变量却不在任何地方使用,编译器会有警告;
  • 如果变量以下划线开头,则编译器不会产生警告;
fn main() {
    let _x = 5;
    let y = 10;
}
  • 只有y变量收到了编译器的警告;

4)用..忽略剩余值

  • 对于有多个部分的值,可以使用..语法只使用部分并忽略其它值;
  • 同时可避免为每一个被忽略的值列出下划线;
  • 被忽略的部分不能有歧义,否则会报错;
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let origin = Point { x: 3, y: 4, z: 5 };
    
    match origin {
        Point { x, .. } => println!("x is {}", x),
    }

    match origin{
        Point{ z, ..} => println!("z is {}", z ),
    }

    let numbers = (1, 2, 3, 4, 5, 6);

    match numbers{
        (first,.., last) => {
            println!("Some numbers: {}, {}", first, last);
        }
    }

    // match numbers{
    //     (.., second, ..) => {
    //         println!("Some numbers: {}", second);
    //     }
    // }
}
  • 前两个match只分别想操作x和z的值而忽略其他值;
  • 第三、四个match显示了在元组中的应用:
    • 第三个只匹配的第一个和最后一个值,其他的使用..忽略;
    • 第四个被注释掉了,因此second前后的忽略值个数有歧义,因此编译器报错(如下图)

在这里插入图片描述

4.5 匹配守卫

1)引入额外条件

  • 匹配守卫(match guard) 是一个指定于match分支模式之后的额外 if 条件,它也必须被满足才能选择此分支;
  • 匹配守卫用于表达比单独的模式所能允许的更为复杂的情况;

fn main() {
    let num = Some(4);

    match num {
        Some(x) if x < 5 => println!("less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
}
  • 很好理解, 第一个分支就能执行成功;

2)解决模式中变量覆盖的问题

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {}", n),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {}", x, y);
}
  • 输出结果:Default case, x = Some(5)at the end: x = Some(5), y = 10
  • 第二个分支中的模式不会引入一个覆盖外部 y 的新变量 y,这意味着可以在匹配守卫中使用外部的 y;

3)在使用或运算符指定多个模式

fn main() {
    let x = 4;
    let y = false;

    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}
  • 匹配分支中y同时作用于4,5,6,三者是或的关系;
  • 即y为true时第一个分支才会进行判断;
  • 4 | 5 | 6 if y应该理解为(4 | 5 | 6) if y
  • 综合解释为:当y为true时,x为4,5或6时,打印yes;

4.6 at绑定

  • at运算符(@)允许在创建一个存放值的变量的同时测试其值是否匹配模式;
enum Message {
    Hello { id: i32 },
}
fn main() {
    let msg = Message::Hello { id: 5 };
    
    match msg {
        Message::Hello { id: id_variable @ 3..=7 } => {
            println!("Found an id in range: {}", id_variable)
        },
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        },
        Message::Hello { id } => {
            println!("Found some other id: {}", id)
        },
    }
}
  • 代码测试Message::Helloid字段是否位于3..=7范围内;
  • 同时将其值绑定到 id_variable 变量中以便此分支相关联的代码可以使用它;
  • 代码输出Found an id in range: 5
  • 第一个分支通过在3..=7之前指定id_variable @,捕获了任何匹配此范围的值并同时测试范围;
  • 第二个分支只在模式中指定了一个范围,没有将具体的值保存进一个变量,因此不能使用;
  • 第三个分支任务值都可以匹配;
  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

贱贱的剑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值