Rust简单计算器:模式匹配与表达式求值

举报
数字扫地僧 发表于 2025/06/10 18:44:22 2025/06/10
【摘要】 引言在编程的世界里,计算器程序堪称初学者的“练手神器”。而对于Rust这门以安全性和性能著称的编程语言来说,打造一个简单计算器,不仅能让我们熟悉语法,更是深入理解其独特特性的绝佳契机。本文就带大家从零开始,一步步构建一个基于Rust的简单计算器。我们将重点聚焦在模式匹配与表达式求值上,这两项技术堪称Rust编程的“看家本领”,学好了,你在Rust的世界里就能横着走。 I.模式匹配的奥秘 模...

引言

在编程的世界里,计算器程序堪称初学者的“练手神器”。而对于Rust这门以安全性和性能著称的编程语言来说,打造一个简单计算器,不仅能让我们熟悉语法,更是深入理解其独特特性的绝佳契机。本文就带大家从零开始,一步步构建一个基于Rust的简单计算器。我们将重点聚焦在模式匹配与表达式求值上,这两项技术堪称Rust编程的“看家本领”,学好了,你在Rust的世界里就能横着走。

I.模式匹配的奥秘

模式匹配是什么

模式匹配是Rust里处理数据的一种强大方法,它允许我们根据不同的模式来执行不同的代码。这就好比给数据做“人脸识别”,一旦匹配成功,就触发相应的操作。

为什么模式匹配很重要

在编程里,我们经常会遇到需要根据不同情况做不同处理的场景。比如说,判断一个数的正负、处理用户输入的不同类型等等。模式匹配能让这些操作变得既清晰又安全,还能有效避免遗漏情况。

Rust里的模式匹配

Rust提供了两种主要的模式匹配机制:match表达式和if let/while let语句。

match表达式

match表达式是最常用的模式匹配方式。它的基本语法如下:

match 变量 {
    模式1 => 表达式1,
    模式2 => 表达式2,
    // 更多模式...
    _ => 表达式n, // 通配符,用于捕获所有未匹配的模式
}

举个栗子🌰:我们来判断一个整数的正负。

fn main() {
    let num = -5;

    match num {
        n if n > 0 => println!("{} 是正数", n),
        0 => println!("零"),
        _ => println!("负数"),
    }
}

在这段代码里,我们用match num开始模式匹配。对于正数的情况,我们用n if n > 0这个模式,既匹配了正数,还能在打印时用到具体的数值。对于0,直接写0这个模式,打印“零”。最后用通配符_来匹配负数情况,输出“负数”。

if let 和 while let

有时候,我们只想匹配一种特定的模式,其他情况都不关心。这时候if letwhile let就派上用场啦。

let optional_value = Some(42);

if let Some(value) = optional_value {
    println!("有值:{}", value);
} else {
    println!("无值");
}

在这段代码里,if let Some(value) = optional_value表示只关心optional_valueSome的情况,并把里面的值提取出来赋给value变量。如果optional_valueNone,就走else分支。

mermaid 总结

模式匹配
match 表达式
语法格式
判断正负数示例
if let 和 while let
语法格式
处理 Option 示例

II.表达式求值的艺术

表达式求值是啥

表达式求值,简单来说,就是按照一定的规则计算出一个表达式的值。比如计算3 + 5 * 2,就得先算乘法再算加法。

Rust里的表达式求值

Rust里的表达式求值遵循常见的运算符优先级规则。比如乘法、除法的优先级高于加法、减法。但有时候,我们可能需要自定义复杂的表达式求值逻辑,这就需要用模式匹配来处理不同的运算符。

分析表达式结构

一个典型的算术表达式由操作数和运算符组成。比如3 + 5,3和5是操作数,+是运算符。我们可以把表达式抽象成不同的类型,比如加法表达式、减法表达式等等。

mermaid 总结

Lexical error on line 4. Unrecognized text. ...符优先级] C --> D[示例:3 + 5 * 2] A -- ----------------------^

III.动手实现简单计算器

项目搭建

创建新项目

打开终端,输入以下命令创建一个名为calculator的新项目:

cargo new calculator

Cargo会自动创建一个项目目录结构,包括Cargo.toml文件和src目录下的main.rs文件。

项目结构说明

项目目录结构如下:

calculator
├── Cargo.toml
└── src
    └── main.rs

Cargo.toml文件用于配置项目信息和依赖项。main.rs是程序的入口文件。

设计表达式数据结构

为了表示不同的算术表达式,我们定义一个枚举类型Expression

enum Expression {
    Number(i32),
    Addition(Box<Expression>, Box<Expression>),
    Subtraction(Box<Expression>, Box<Expression>),
    Multiplication(Box<Expression>, Box<Expression>),
    Division(Box<Expression>, Box<Expression>),
}

这个枚举类型有五种变体:

  • Number表示一个单独的数字。
  • Addition表示加法表达式,包含两个子表达式,用Box进行堆分配,以便于处理递归结构。
  • Subtraction表示减法表达式。
  • Multiplication表示乘法表达式。
  • Division表示除法表达式。

实现表达式求值函数

接下来,我们编写一个函数来计算Expression的值:

fn evaluate(expr: &Expression) -> f64 {
    match expr {
        Expression::Number(n) => *n as f64,
        Expression::Addition(a, b) => evaluate(a) + evaluate(b),
        Expression::Subtraction(a, b) => evaluate(a) - evaluate(b),
        Expression::Multiplication(a, b) => evaluate(a) * evaluate(b),
        Expression::Division(a, b) => evaluate(a) / evaluate(b),
    }
}

这个函数使用match表达式对expr进行模式匹配。对于数字,直接返回其值;对于复合表达式,递归计算子表达式的值,然后根据运算符执行相应的操作。

构建表达式示例

main函数中,我们构建一个表达式并计算其值:

fn main() {
    let expr = Expression::Addition(
        Box::new(Expression::Number(3)),
        Box::new(Expression::Multiplication(
            Box::new(Expression::Number(5)),
            Box::new(Expression::Number(2)),
        )),
    );

    let result = evaluate(&expr);
    println!("表达式:3 + 5 * 2");
    println!("结果:{}", result);
}

这段代码构建了一个表达式3 + 5 * 2,然后调用evaluate函数计算结果,并打印出来。

构建与运行

在项目根目录下,执行以下命令构建项目:

cargo build

构建成功后,运行生成的可执行文件(Windows系统上为.\target\debug\calculator.exe,macOS和Linux系统上为./target/debug/calculator)。程序会输出:

表达式:3 + 5 * 2
结果:13

mermaid 总结

实现简单计算器
项目搭建
创建新项目
项目结构说明
设计表达式数据结构
定义 Expression 枚举
实现表达式求值函数
编写 evaluate 函数
构建表达式示例
构建 3 + 5 * 2 表达式
计算并打印结果
构建与运行
执行 cargo build
运行可执行文件

IV.增强计算器功能:支持用户输入

从用户获取输入

为了使计算器更具实用性,我们需要支持用户输入表达式。在Rust里,可以用std::io模块来读取用户输入。

use std::io;

fn get_user_input() -> String {
    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("读取输入失败");
    input.trim().to_string()
}

这个函数首先创建一个空的String对象input。然后调用io::stdin().read_line(&mut input)读取用户输入,并将其存储在input中。trim()方法用于去除输入两端的空白字符,to_string()将其转换为String类型。

解析用户输入

用户输入的表达式需要解析成我们的Expression枚举类型,这样才能用evaluate函数计算。这里我们简单地根据输入的运算符来创建相应的表达式。

fn parse_expression(input: &str) -> Option<Expression> {
    if let Some(expr) = parse_addition_or_subtraction(input) {
        Some(expr)
    } else if let Some(expr) = parse_multiplication_or_division(input) {
        Some(expr)
    } else if let Some(n) = input.parse::<i32>().ok() {
        Some(Expression::Number(n))
    } else {
        None
    }
}

fn parse_addition_or_subtraction(input: &str) -> Option<Expression> {
    for (i, c) in input.chars().enumerate() {
        if c == '+' || c == '-' {
            let left = &input[0..i];
            let right = &input[i + 1..];
            if let (Some(left_expr), Some(right_expr)) = (parse_expression(left), parse_expression(right)) {
                return Some(if c == '+' {
                    Expression::Addition(Box::new(left_expr), Box::new(right_expr))
                } else {
                    Expression::Subtraction(Box::new(left_expr), Box::new(right_expr))
                });
            }
        }
    }
    None
}

fn parse_multiplication_or_division(input: &str) -> Option<Expression> {
    for (i, c) in input.chars().enumerate() {
        if c == '*' || c == '/' {
            let left = &input[0..i];
            let right = &input[i + 1..];
            if let (Some(left_expr), Some(right_expr)) = (parse_expression(left), parse_expression(right)) {
                return Some(if c == '*' {
                    Expression::Multiplication(Box::new(left_expr), Box::new(right_expr))
                } else {
                    Expression::Division(Box::new(left_expr), Box::new(right_expr))
                });
            }
        }
    }
    None
}

parse_expression函数尝试解析加减法、乘除法以及单独数字的情况。parse_addition_or_subtractionparse_multiplication_or_division函数分别处理加减法和乘除法的解析。它们遍历输入字符串,查找运算符的位置,然后递归解析左右两边的表达式。

更新主函数支持用户交互

修改main函数,使其支持用户输入并计算结果:

fn main() {
    loop {
        println!("请输入表达式(例如:3 + 5 * 2),输入 q 退出:");
        let input = get_user_input();
        if input == "q" {
            break;
        }
        match parse_expression(&input) {
            Some(expr) => {
                let result = evaluate(&expr);
                println!("结果:{}", result);
            }
            None => println!("无法解析的表达式"),
        }
    }
}

这个循环不断提示用户输入表达式。如果用户输入“q”,程序退出。否则,调用parse_expression函数解析输入,若解析成功则计算并打印结果,否则提示无法解析。

测试用户输入功能

重新构建并运行项目。在控制台输入不同的表达式进行测试,例如:

请输入表达式(例如:3 + 5 * 2),输入 q 退出:
10 + 20
结果:30
请输入表达式(例如:3 + 5 * 2),输入 q 退出:
100 / 10 - 5
结果:5
请输入表达式(例如:3 + 5 * 2),输入 q 退出:
q

mermaid 总结

支持用户输入
获取用户输入
使用 std::io 读取输入
解析用户输入
parse_expression 函数
parse_addition_or_subtraction 函数
parse_multiplication_or_division 函数
更新主函数
循环读取输入
判断退出条件
解析并计算表达式
测试功能
输入不同表达式
查看输出结果

V.异常处理与错误管理

为什么需要异常处理

在实际编程中,错误是无法避免的。比如用户输入的表达式格式不正确、除数为零等情况。为了使我们的计算器健壮可靠,必须妥善处理这些异常情况。

Rust里的错误处理机制

Rust主要通过ResultOption类型来处理错误。Result类型有两种变体:Ok表示操作成功,Err表示操作失败。Option类型有SomeNone两种变体,用于表示可能存在或不存在的值。

改进解析函数返回 Result 类型

修改parse_expression函数,使其返回Result<Expression, String>类型,以便传递错误信息:

fn parse_expression(input: &str) -> Result<Expression, String> {
    if input.is_empty() {
        return Err("空表达式".to_string());
    }
    if let Some(expr) = parse_addition_or_subtraction(input) {
        Ok(expr)
    } else if let Some(expr) = parse_multiplication_or_division(input) {
        Ok(expr)
    } else if let Some(n) = input.parse::<i32>().ok() {
        Ok(Expression::Number(n))
    } else {
        Err("无法解析的表达式".to_string())
    }
}

现在,如果解析成功,返回Ok(expr);如果失败,返回Err携带错误信息。

在主函数中处理错误

更新main函数,处理解析过程中可能出现的错误:

fn main() {
    loop {
        println!("请输入表达式(例如:3 + 5 * 2),输入 q 退出:");
        let input = get_user_input();
        if input == "q" {
            break;
        }
        match parse_expression(&input) {
            Ok(expr) => {
                let result = evaluate(&expr);
                println!("结果:{}", result);
            }
            Err(e) => println!("错误:{}", e),
        }
    }
}

现在,当解析失败时,程序会打印具体的错误信息,而不是简单地显示“无法解析的表达式”。

处理除零错误

进一步改进evaluate函数,处理除法中的除零错误:

fn evaluate(expr: &Expression) -> Result<f64, String> {
    match expr {
        Expression::Number(n) => Ok(*n as f64),
        Expression::Addition(a, b) => {
            let a_val = evaluate(a)?;
            let b_val = evaluate(b)?;
            Ok(a_val + b_val)
        }
        Expression::Subtraction(a, b) => {
            let a_val = evaluate(a)?;
            let b_val = evaluate(b)?;
            Ok(a_val - b_val)
        }
        Expression::Multiplication(a, b) => {
            let a_val = evaluate(a)?;
            let b_val = evaluate(b)?;
            Ok(a_val * b_val)
        }
        Expression::Division(a, b) => {
            let a_val = evaluate(a)?;
            let b_val = evaluate(b)?;
            if b_val == 0.0 {
                Err("除数不能为零".to_string())
            } else {
                Ok(a_val / b_val)
            }
        }
    }
}

在这个改进版中,evaluate函数也返回Result<f64, String>类型。对于除法操作,先计算分母的值b_val,如果b_val为零,返回Err错误信息。否则,正常执行除法操作并返回结果。

同时,更新main函数中的表达式计算部分:

match parse_expression(&input) {
    Ok(expr) => match evaluate(&expr) {
        Ok(result) => println!("结果:{}", result),
        Err(e) => println!("错误:{}", e),
    },
    Err(e) => println!("错误:{}", e),
}

现在,程序能正确处理除零错误并给出提示。

测试错误处理功能

重新构建并运行项目。尝试输入以下表达式进行测试:

请输入表达式(例如:3 + 5 * 2),输入 q 退出:
5 / 0
错误:除数不能为零
请输入表达式(例如:3 + 5 * 2),输入 q 退出:
abc
错误:无法解析的表达式
请输入表达式(例如:3 + 5 * 2),输入 q 退出:
q

mermaid 总结

异常处理与错误管理
错误处理的重要性
Rust 的错误处理机制
Result 类型
Option 类型
改进解析函数
返回 Result 类型
主函数错误处理
处理解析错误
处理除零错误
测试错误处理功能
输入错误表达式
查看错误提示

VI.性能优化与代码改进

性能优化的必要性

随着计算器功能的增强,处理复杂的表达式可能会消耗较多计算资源。为了提升用户体验,性能优化变得至关重要。

使用更高效的数据结构

目前我们用递归的方式构建和解析表达式,这在处理复杂表达式时可能会比较低效。我们可以考虑使用更高效的数据结构,比如直接用元组来表示表达式。

type Expression = (Operator, Box<Expression>, Box<Expression>);
enum Operator {
    Add,
    Subtract,
    Multiply,
    Divide,
    Number(i32),
}

不过,这种改变需要对现有代码进行较大调整,在实际项目中,我们需要权衡性能提升与代码重构成本。

代码改进:减少内存分配

在当前实现中,我们大量使用Box进行堆分配,这虽然方便处理递归结构,但也增加了内存分配的开销。可以通过减少不必要的堆分配来优化性能。例如,对于简单的数字表达式,可以将其直接存储在栈上,而不是用Box包装。

代码改进:提升用户输入解析效率

优化parse_expression函数,减少不必要的字符串操作和递归调用。可以预先对输入字符串进行一次扫描,确定运算符优先级,然后按照优先级进行解析。

mermaid 总结

性能优化与代码改进
性能优化必要性
高效数据结构
使用元组表示表达式
代码改进
减少内存分配
提升用户输入解析效率
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。