ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【Rust入门知识点学与练】第14课:模式匹配 Pattern Matching

【Rust入门知识点学与练】第14课:模式匹配 Pattern Matching 知识点1match 穷举匹配match 是 Rust 最强大的控制流工具必须穷举所有可能的情况fndescribe_number(n:i32)-String{matchn{0String::from(零),1..9String::from(个位数),// 1到910..99String::from(两位数),// 10到99100..999String::from(三位数),// 100到999_String::from(很大的数),// _ 匹配所有其他情况}}fnmain(){println!({},describe_number(0));// 零println!({},describe_number(7));// 个位数println!({},describe_number(42));// 两位数println!({},describe_number(500));// 三位数println!({},describe_number(1000));// 很大的数}知识点2match 解构枚举和结构体enumCommand{Echo(String),Move{x:i32,y:i32},Quit,}structPoint{x:i32,y:i32,}fnmain(){// 匹配枚举letcmdCommand::Move{x:10,y:20};matchcmd{Command::Echo(msg)println!(回声: {},msg),Command::Move{x,y}println!(移动到 ({}, {}),x,y),Command::Quitprintln!(退出),}// 匹配结构体letpPoint{x:3,y:7};matchp{Point{x:0,y}println!(在Y轴上, y{},y),Point{x,y:0}println!(在X轴上, x{},x),Point{x,y}println!(普通点 ({}, {}),x,y),}// 匹配元组letpair(1,2);matchpair{(0,_)println!(第一个是0),(_,0)println!(第二个是0),(x,y)println!(两个数: {} 和 {},x,y),}}知识点3匹配守卫Match Guard在模式后面加 if 条件进一步过滤fnclassify(n:i32)-staticstr{matchn{xifx0负数,0零,xifx0x100小的正数,xifx100大的正数,_其他,}}fnmain(){println!({},classify(-5));// 负数println!({},classify(0));// 零println!({},classify(42));// 小的正数println!({},classify(200));// 大的正数}知识点4if let 和 while let当你只关心一种匹配情况时用 if let 更简洁fnmain(){letvalue:Optioni32Some(42);// 用 match啰嗦matchvalue{Some(v)println!(值是 {},v),_{}}// 用 if let简洁ifletSome(v)value{println!(值是 {},v);}// if let 也可以加 elseletempty:Optioni32None;ifletSome(v)empty{println!(值是 {},v);}else{println!(没有值);}// while let循环直到匹配失败letmutstackvec![1,2,3,4,5];whileletSome(top)stack.pop(){println!(弹出: {},top);}// 输出: 5, 4, 3, 2, 1逐个弹出直到栈空}知识点5let else 和 绑定fnmain(){// let else匹配失败时执行 else 分支必须 divergereturn/break/panicletvalue:Optioni32Some(42);letSome(v)valueelse{println!(没有值提前返回);return;};println!(拿到值: {},v);// 42// 绑定匹配的同时把整个值绑定到一个变量letmsgString::from(hello);matchmsg.len(){n1..5println!(短字符串长度{},n),n6..10println!(中等字符串长度{},n),nprintln!(长字符串长度{},n),}// 绑定在枚举中letvalue:Optioni32Some(42);matchvalue{Some(n1..100)println!(1到100之间的数: {},n),Some(n)println!(其他数: {},n),Noneprintln!(没有值),}}核心规则概念 写法穷举匹配 match value { pattern expr, … }通配符 _ …范围匹配 1…9匹配守卫 x if x 0 …单分支匹配 if let Pattern value { … }循环匹配 while let Pattern value { … }匹配失败分支 let Pattern value else { … };绑定匹配值 n pattern …解构结构体 Struct { field1, field2 }解构枚举 Enum::Variant(data)动手试试补全下面的代码enumExpr{Num(f64),Add(BoxExpr,BoxExpr),Sub(BoxExpr,BoxExpr),Mul(BoxExpr,BoxExpr),Div(BoxExpr,BoxExpr),}// 补全实现 eval 函数递归计算表达式的值// 除法遇到除数为0时返回 Nonefneval(expr:Expr)-Optionf64{// 补全这里// 提示用 match 匹配每种情况递归调用 eval// Add: a b// Sub: a - b// Mul: a * b// Div: a / bb为0返回None// 用 if let 或 match 处理递归结果中可能出现 None 的情况todo!()}// 补全实现 describe 函数返回表达式的字符串描述// 例如: Add(Num(1), Num(2)) (1 2)// Mul(Num(3), Add(Num(4), Num(5))) (3 * (4 5))fndescribe(expr:Expr)-String{// 补全这里todo!()}fnmain(){// 表达式: (3 4) * 2letexprExpr::Mul(Box::new(Expr::Add(Box::new(Expr::Num(3.0)),Box::new(Expr::Num(4.0)),)),Box::new(Expr::Num(2.0)),);// 补全打印表达式描述和计算结果// 期望输出:// 表达式: ((3 4) * 2)// 结果: Some(14)// 表达式: 10 / (5 - 5)letdiv_zeroExpr::Div(Box::new(Expr::Num(10.0)),Box::new(Expr::Sub(Box::new(Expr::Num(5.0)),Box::new(Expr::Num(5.0)),)),);// 补全打印表达式描述和计算结果// 期望输出:// 表达式: (10 / (5 - 5))// 结果: None}期望输出表达式: ((3 4) * 2)结果: Some(14)表达式: (10 / (5 - 5))结果: None写好了贴过来没问题就进入第15课泛型 Generics 贴代码下面给出完整可运行代码 eval 和 describe 全部补全并附带讲解。enumExpr{Num(f64),Add(BoxExpr,BoxExpr),Sub(BoxExpr,BoxExpr),Mul(BoxExpr,BoxExpr),Div(BoxExpr,BoxExpr),}// 递归计算表达式除数为0返回None子表达式出错也返回Nonefneval(expr:Expr)-Optionf64{matchexpr{Expr::Num(n)Some(*n),Expr::Add(a,b){letvaeval(a)?;letvbeval(b)?;Some(vavb)}Expr::Sub(a,b){letvaeval(a)?;letvbeval(b)?;Some(va-vb)}Expr::Mul(a,b){letvaeval(a)?;letvbeval(b)?;Some(va*vb)}Expr::Div(a,b){letvaeval(a)?;letvbeval(b)?;ifvb0.0{None}else{Some(va/vb)}}}}// 生成带括号的表达式字符串fndescribe(expr:Expr)-String{matchexpr{Expr::Num(n)format!({},n),Expr::Add(a,b)format!(({} {}),describe(a),describe(b)),Expr::Sub(a,b)format!(({} - {}),describe(a),describe(b)),Expr::Mul(a,b)format!(({} * {}),describe(a),describe(b)),Expr::Div(a,b)format!(({} / {}),describe(a),describe(b)),}}fnmain(){// 表达式: (3 4) * 2letexprExpr::Mul(Box::new(Expr::Add(Box::new(Expr::Num(3.0)),Box::new(Expr::Num(4.0)),)),Box::new(Expr::Num(2.0)),);println!(表达式: {},describe(expr));println!(结果: {:?},eval(expr));// 表达式: 10 / (5 - 5)letdiv_zeroExpr::Div(Box::new(Expr::Num(10.0)),Box::new(Expr::Sub(Box::new(Expr::Num(5.0)),Box::new(Expr::Num(5.0)),)),);println!(表达式: {},describe(div_zero));println!(结果: {:?},eval(div_zero));}运行输出plaintext表达式: ((3 4) * 2)结果: Some(14.0)表达式: (10 / (5 - 5))结果: None重点知识点拆解递归枚举 BoxAdd(BoxExpr,BoxExpr)Expr 递归包含自身Rust 无法在栈上直接存放无限大小的递归类型必须用 Box堆指针。Box 把 T 放到堆栈上只存一个指针解决递归类型大小不确定问题。? 运算符问号letvaeval(a)?;eval(a)返回Optionf64。如果是 Some(v) → 把 v 赋值给 va如果是 None → 整个 eval 函数立刻提前返回 None。? 是错误传播语法糖等价于letvamatcheval(a){Some(v)v,NonereturnNone,};⚠️ ? 只能用在返回 Option / Result 的函数内部main 里面不能直接写。describe 递归打印每一个二元运算符都包一层括号不管运算符优先级简单粗暴保证结构清晰Add(Num(3),Num(4))→(34)拓展思考进阶当前方案不做运算符优先级所以 (3 4) * 2 输出 ((3 4) * 2) 是正确的但如果表达式是 Add(Mul(Num(3),Num(4)),Num(5)) 现在输出 ((3 * 4) 5) 数学上其实可以省去外层括号 (3 * 4) 5 。想实现智能括号根据优先级决定要不要加括号就是一道经典的中级Rust练习题。
返回列表