第2章:变量、数据类型与控制流
学习目标
- 掌握 Rust 中变量声明和使用的基本语法
- 理解各种数据类型的特性和使用场景
- 熟练使用控制流语句进行逻辑控制
- 学会定义和调用函数
2.1 变量与可变性
2.1.1 基本变量声明
在 Rust 中,变量通过 let 关键字声明,默认是不可变的(immutable)。这意味着一旦变量被绑定到一个值,你就不能再修改它。这种设计是为了确保代码的安全性和可预测性,避免意外的副作用。Rust 的类型系统会自动推断变量的类型,但你也可以显式指定(如 let x: i32 = 5;)。
#![allow(unused)] fn main() { // 基本变量声明(不可变) fn variable_basics() { let x = 42; // 整数 let y = 3.14; // 浮点数 let name = "Rust"; // 字符串字面量 let is_rust_awesome = true; // 布尔值 println!("整数: {}", x); println!("浮点数: {}", y); println!("字符串: {}", name); println!("布尔值: {}", is_rust_awesome); // 变量遮蔽(shadowing) let x = x + 10; // 创建新的 x,原 x 被隐藏 { let x = "shadowed"; // 创建新的 x,原 x 被隐藏 println!("遮蔽中的 x: {}", x); } println!("遮蔽后的 x: {}", x); } }
Result:
整数: 42
浮点数: 3.14
字符串: Rust
布尔值: true
遮蔽中的 x: shadowed
遮蔽后的 x: 52
关键点:
- 使用
let绑定值。 - 变量名区分大小写,遵循蛇形命名法(snake_case)。
- 在函数或块作用域内有效,超出作用域后自动释放(所有权系统)。
2.1.2 可变变量
Rust 的默认不可变性很严格,但如果你需要修改变量,可以使用 mut 关键字声明为可变的(mutable)。这允许你重新赋值,但必须在声明时就指定,以明确意图。注意,可变性是作用域内的:一个变量在整个作用域内要么可变,要么不可变,不能中途改变。
#![allow(unused)] fn main() { fn mutable_variables() { // 可变变量声明 let mut counter = 0; println!("初始值: {}", counter); // 修改可变变量 counter += 1; counter *= 2; println!("修改后: {}", counter); // 可变变量的典型用途 let mut sum = 0; // 初始化为 0 let numbers = vec![1, 2, 3, 4, 5]; // 创建一个向量 for num in numbers { sum += num; // 在循环中累积计算 } println!("数列和: {}", sum); } }
Result:
初始值: 0
修改后: 2
数列和: 15
关键点:
- 使用
let mut声明可变变量。 - 可变变量可以在声明后重新赋值。
- 可变变量在循环或函数中常用,以累积计算结果。
- 可变变量在 Rust 中是谨慎使用的,以避免意外修改。
- 只在需要时使用
mut,以最小化可变性(减少 bug 风险)。 - 在借用规则下,可变借用(&mut)有严格限制,确保线程安全。
- 作用域内,变量要么可变,要么不可变,不能中途改变。
可变变量是 Rust 灵活性的关键,但结合所有权和借用检查器,使用时需小心避免编译错误。
2.1.3 常量声明
常量通过 const 关键字声明,是不可变的,且必须在编译时确定值(不能是运行时计算的)。常量是全局可见的(如果在模块顶层声明),并在整个程序生命周期内有效。它们适合定义不变的配置或数学常数。类型必须显式指定,且不能是 mut。
#![allow(unused)] fn main() { // 常量声明(always immutable, must have type annotation) fn constants_example() { const PI: f64 = 3.14159265359; // 浮点数常量 const MAX_SIZE: usize = 1000; // 无符号整数常量 const GREETING: &str = "Hello, World!"; // 字符串常量 println!("PI = {}", PI); println!("最大尺寸: {}", MAX_SIZE); println!("问候语: {}", GREETING); // 常量表达式 const AREA: f64 = PI * 10.0 * 10.0; // 圆面积公式 println!("圆面积: {}", AREA); } }
Result:
PI = 3.14159265359
最大尺寸: 1000
问候语: Hello, World!
圆面积: 314.15926535899996
关键点:
- 使用
const NAME: Type = value;格式,NAME遵循大写蛇形命名(SCREAMING_SNAKE_CASE)。 - 值必须是常量表达式(如字面量、简单计算),不能依赖运行时输入。
- 静态常量(
static)类似,但有额外生命周期考虑;const更常见用于简单值。 - 常量在编译时确定,提升代码的可维护性和性能。
- 常量不可变,适用于定义不变的事实或配置。
常量提升了代码的可维护性,因为它们是不可变的“事实”,并在编译时优化。
2.2 基础数据类型
Rust 的基础数据类型分为标量类型(scalar types)和复合类型(compound types)。标量类型表示单一值,复合类型可包含多个值。这些类型在编译时确定大小,确保内存安全和性能。
标量类型(Scalars)
- 整数(Integers):有符号(i8, i16, i32, i64, isize)和无符号(u8, u16, u32, u64, usize)。默认 i32。
- 浮点数(Floats):f32(单精度)和 f64(双精度,默认)。
- 布尔(Boolean):bool,仅 true 或 false。
- 字符(Character):char,Unicode 标量值(如 'a')。
复合类型(Compounds)
- 元组(Tuples):固定大小的异构集合,如
(i32, bool)。 - 数组(Arrays):固定长度、同构元素,如
[i32; 5](5 个 i32)。
2.2.1 整数类型
#![allow(unused)] fn main() { fn integer_types() { // 有符号整数 let i8_val: i8 = -128; // 范围: -128 到 127 let i16_val: i16 = -32768; // 范围: -32768 到 32767 let i32_val: i32 = -2147483648; // 默认的整数类型 let i64_val: i64 = -9223372036854775808; let i128_val: i128 = -170141183460469231731687303715884105728; // 无符号整数 let u8_val: u8 = 255; // 范围: 0 到 255 let u16_val: u16 = 65535; let u32_val: u32 = 4294967295; let u64_val: u64 = 18446744073709551615; let u128_val: u128 = 340282366920938463463374607431768211455; // 平台相关整数类型 let isize: isize = -1; // 平台相关大小 let usize: usize = 1; // 平台相关大小 // 数值字面量 let decimal = 98_222; // 十进制(可用下划线分隔) let hex = 0xff; // 十六进制 let octal = 0o77; // 八进制 let binary = 0b1111_0000; // 二进制 let byte = b'A'; // 字节字符(仅 u8) println!("整数值: {}, {}, {}, {}", decimal, hex, octal, binary); } }
Result:
整数值: 98222, 255, 63, 240
关键点:
- Rust 中的整数类型有符号和无符号,有符号整数可以表示负数,无符号整数只能表示非负数。
- Rust 提供了多种整数类型,包括 8 位、16 位、32 位、64 位和 128 位的有符号和无符号整数。
- Rust 还提供了平台相关的整数类型,如
isize和usize,它们的大小取决于运行程序的计算机架构。 - Rust 支持数值字面量的多种表示方式,包括十进制、十六进制、八进制和二进制。
- Rust 中的整数类型都有固定的范围,超过范围会导致溢出错误。
- Rust 提供了
std::num模块中的函数来处理整数溢出,如wrapping_add、wrapping_sub、wrapping_mul等。 - ...
2.2.2 浮点类型
#![allow(unused)] fn main() { fn float_types() { let f32_val: f32 = 3.141592653589793; // 32位浮点 let f64_val: f64 = 3.141592653589793; // 64位浮点(默认) // 特殊值 let infinity = f32::INFINITY; let neg_infinity = f32::NEG_INFINITY; let not_a_number = f32::NAN; println!("f32: {}", f32_val); println!("f64: {}", f64_val); println!("无穷大: {}", infinity); println!("负无穷大: {}", neg_infinity); println!("非数字: {}", not_a_number); // 数学运算 let result = f32::sqrt(2.0); println!("√2 = {}", result); // 比较运算 let x: f64 = 1.0; //必须显式声明类型,否则编译器无法推断 let y: f64 = 0.1 + 0.1 + 0.1 + 0.1 + 0.1; //必须显式声明类型,否则编译器无法推断 println!("x == y: {}", x == y); // 避免直接比较浮点数 println!("(x - y).abs() < 1e-10: {}", (x - y).abs() < 1e-10); } }
Result:
f32: 3.1415927
f64: 3.141592653589793
无穷大: inf
负无穷大: -inf
非数字: NaN
√2 = 1.4142135
x == y: false
(x - y).abs() < 1e-10: false
关键点:
- Rust 提供了两种浮点类型:f32 和 f64,分别占用 32 位和 64 位内存。
- 浮点数可以表示特殊值,如无穷大、负无穷大和非数字(NaN)。
- 浮点数运算可能会产生不精确的结果,因此建议避免直接比较浮点数,而是比较它们的差值是否在可接受范围内。
- 浮点数运算符包括加法(+)、减法(-)、乘法(*)、除法(/)和取余(%)。
- 浮点数函数包括平方根(sqrt)、指数(exp)、对数(log)、三角函数等。
- 浮点数类型可以与整数类型进行混合运算,但结果类型将自动提升为浮点数类型。
- 浮点数类型可以与布尔类型进行混合运算,但结果类型将自动提升为布尔类型。
- 浮点数类型可以与字符串类型进行混合运算,但结果类型将自动提升为字符串类型。
- ...
2.2.3 布尔类型
#![allow(unused)] fn main() { fn boolean_types() { let is_learning_rust = true; let is_difficult = false; // 条件表达式 let message = if is_learning_rust { "Keep going!" } else { "Try harder!" }; // 布尔逻辑 let both_true = is_learning_rust && !is_difficult; let either_or = is_learning_rust || is_difficult; println!("{} {}", message, both_true); println!("Either learning or difficult: {}", either_or); // 模式匹配中的布尔 match (is_learning_rust, is_difficult) { (true, false) => println!("Perfect learning situation!"), (true, true) => println!("Challenging but rewarding!"), (false, _) => println!("Maybe try something else?"), } } }
Result:
Keep going! true
Either learning or difficult: true
Perfect learning situation!
关键点:
- 布尔类型只有两个值:true 和 false。
- 布尔类型可以与整数类型进行混合运算,但结果类型将自动提升为整数类型。
- 布尔类型可以与字符串类型进行混合运算,但结果类型将自动提升为字符串类型。
2.2.4 字符类型
Rust 中的“字符”指的是 Unicode 标量值(Unicode scalar value),通过 chars() 方法获取。它不是固定大小的字节,而是逻辑上的字符单位。
字符需要使用单引号包裹,并且只能表示一个字符。 字符串底层是字节数组([u8]),可以通过 as_bytes() 获取视图。
注意 :字节操作高效,但需小心:切片字节可能截断字符,导致无效 UTF-8。
#![allow(unused)] fn main() { fn character_types() { let c1 = 'z'; // 单个字符 let c2 = 'ℤ'; // Unicode 字符 let c3 = '😊'; // 表情符号 println!("字符: {}, {}, {}", c1, c2, c3); // 转义字符 let newline = '\n'; let tab = '\t'; let quote = '\''; let backslash = '\\'; // 字符串中的字符 let string = "Hello, 世界! 🌍"; for (index, char) in string.chars().enumerate() { println!("字符 {}: {}", index, char); } // 获取字节 let bytes = string.as_bytes(); println!("字符串长度(字节): {}", bytes.len()); } }
Result:
字符: z, ℤ, 😊
字符 0: H
字符 1: e
字符 2: l
字符 3: l
字符 4: o
字符 5: ,
字符 6:
字符 7: 世
字符 8: 界
字符 9: !
字符 10:
字符 11: 🌍
字符串长度(字节): 19
关键点:
- 字符类型是 Unicode 标量值,可以表示任何字符。
- 字符串是字节数组,可以通过 as_bytes() 获取字节视图。
- 字符串中的字符可以通过 chars() 方法获取,并使用 enumerate() 方法遍历。
- 字符串长度是字节长度,而不是字符长度。
- 字符串中的字符可能被截断,导致无效 UTF-8。
- 字符串中的字符可以通过 chars() 方法获取,并使用 enumerate() 方法遍历。
- ...
2.3 复合数据类型:元组和数组
2.3.1 元组(Tuple)
元组是固定长度的有序集合,可以包含不同类型的元素。创建后长度不可变,但内部元素可以是非同质的。
元组基础操作
#![allow(unused)] fn main() { fn tuple_basics() { // 创建元组 let tup: (i32, f64, u8) = (500, 6.4, 1); let tup2 = (42, "Hello", true); // 访问元组元素(使用索引) let x = tup.0; // 500 let y = tup.1; // 6.4 let z = tup.2; // 1 println!("元组值: ({}, {}, {})", x, y, z); // 解构赋值(模式匹配) let (a, b, c) = tup; println!("解构后的值: a={}, b={}, c={}", a, b, c); // 单个元素的元组(注意逗号) let single_tuple: (i32,) = (5,); let single_value = single_tuple.0; println!("单个元素元组: ({}, {})", single_value, single_tuple.0); } }
Result:
元组值: (500, 6.4, 1)
解构后的值: a=500, b=6.4, c=1
单个元素元组: (5, 5)
关键点:
- 元组是固定长度的有序集合,可以包含不同类型的元素。
- 元组可以通过索引访问元素,索引从 0 开始。
- 元组可以使用解构赋值将元素赋值给变量。
- 单个元素的元组需要使用逗号,否则会被解析为表达式。
- 元组可以用于函数返回多个值。
- 元组可以用于模式匹配,将元组中的元素赋值给变量。
- ...
实用元组示例
#![allow(unused)] fn main() { fn practical_tuples() { // 函数返回多个值 let result = divide_and_remainder(17, 5); let (quotient, remainder) = result; println!("17 除以 5 的商和余数: {}, {}", quotient, remainder); // 使用解构直接获取结果 let (sum, product) = calculate_sum_product(10, 20); println!("和: {}, 积: {}", sum, product); // 存储混合类型的数据 let person_info = ("张三", 25, 175.5, true); let (name, age, height, is_student) = person_info; println!("{}今年{}岁,身高{:.1}cm,状态:{}", name, age, height, if is_student { "学生" } else { "非学生" }); // 嵌套元组 let nested_tuple = (1, (2, 3), 4); let inner_tuple = nested_tuple.1; let first_inner = inner_tuple.0; // 2 println!("嵌套元组中的值: {}", first_inner); } // 返回元组的函数示例 fn divide_and_remainder(dividend: i32, divisor: i32) -> (i32, i32) { let quotient = dividend / divisor; let remainder = dividend % divisor; (quotient, remainder) } fn calculate_sum_product(a: i32, b: i32) -> (i32, i32) { (a + b, a * b) } }
Result:
17 除以 5 的商和余数: 3, 2
和: 30, 积: 200
张三今年25岁,身高175.5cm,状态:学生
嵌套元组中的值: 2
元组在模式匹配中的应用
#![allow(unused)] fn main() { fn tuple_pattern_matching() { let coordinates = (10, 20); match coordinates { (0, 0) => println!("原点"), (x, 0) => println!("在X轴上,X坐标: {}", x), (0, y) => println!("在Y轴上,Y坐标: {}", y), (x, y) => println!("坐标点: ({}, {})", x, y), } // 包含条件守卫的模式 let point = (15, 30); match point { (x, y) if x == y => println!("在对角线上: ({}, {})", x, y), (x, y) if x + y == 45 => println!("坐标和为45: ({}, {})", x, y), (x, y) => println!("一般坐标: ({}, {})", x, y), } // 解构函数参数 let (name, age) = get_person_info(); println!("个人信息: {},{}岁", name, age); } fn get_person_info() -> (&'static str, u32) { ("李四", 30) } }
Result:
坐标点: (10, 20)
坐标和为45: (15, 30)
个人信息: 李四,30岁
关键点:
- 元组可以包含不同类型的元素。
- 元组可以用于模式匹配,以提取和操作元组中的值。
- 元组可以用于函数返回值,以返回多个值。
- 元组可以用于解构赋值,以将元组中的值分配给多个变量。
- 元组可以用于模式匹配中的条件守卫,以根据条件执行不同的代码块。
- 元组可以用于函数参数,以传递多个值给函数。
- 元组可以用于解构函数参数,以将函数参数中的值分配给多个变量。
- 元组可以用于模式匹配中的条件守卫,以根据条件执行不同的代码块。
- 元组可以用于函数返回值,以返回多个值给调用者。
- ...
2.3.2 数组(Array)
数组是固定长度的相同类型元素的集合。数组长度在编译时确定,不能动态增长。
数组基础操作
#![allow(unused)] fn main() { fn array_basics() { // 数组声明和初始化 let numbers: [i32; 5] = [1, 2, 3, 4, 5]; let floats = [3.14, 2.71, 1.41, 1.73]; // 类型推导 let chars = ['R', 'u', 's', 't']; // 字符数组 // 访问数组元素 let first = numbers[0]; let last = numbers[4]; println!("第一个元素: {}, 最后一个元素: {}", first, last); // 数组长度 println!("numbers数组长度: {}", numbers.len()); // 初始化相同值的数组 let repeated = [0; 10]; // 长度为10的数组,所有元素都是0 println!("重复值数组长度: {}", repeated.len()); // 遍历数组 for (index, &value) in numbers.iter().enumerate() { println!("numbers[{}] = {}", index, value); } } }
Result:
第一个元素: 1, 最后一个元素: 5
numbers数组长度: 5
重复值数组长度: 10
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
numbers[3] = 4
numbers[4] = 5
关键点:
- 数组长度在编译时确定,不能动态增长。
- 数组元素类型相同。
- 数组元素可以通过索引访问。
- 数组长度可以通过
len()方法获取。 - 数组可以初始化为相同值。
- 数组可以遍历。
- 数组元素可以通过
iter()方法获取迭代器,然后使用enumerate()方法获取索引和值。 - 数组元素可以通过
&符号获取引用,以避免所有权转移。 - 数组元素可以通过
iter_mut()方法获取可变引用,以修改数组元素。 - 数组元素可以通过
get()方法获取可变引用,以修改数组元素。 - 数组元素可以通过
get_mut()方法获取可变引用,以修改数组元素。 - ...
数组与循环
#![allow(unused)] fn main() { fn array_loops() { let arr = [10, 20, 30, 40, 50]; let mut sum = 0; // 方法1: 使用索引循环 let len = arr.len(); for i in 0..len { sum += arr[i]; println!("添加 arr[{}] = {}, 当前总和: {}", i, arr[i], sum); } println!("数组总和: {}", sum); // 方法2: 直接遍历元素(更安全) let mut sum2 = 0; for &value in &arr { sum2 += value; println!("元素值: {}", value); } println!("重新计算的总和: {}", sum2); // 方法3: enumerate遍历 for (i, &value) in arr.iter().enumerate() { println!("索引 {}: 值 {}", i, value); } } }
Result:
添加 arr[0] = 10, 当前总和: 10
添加 arr[1] = 20, 当前总和: 30
添加 arr[2] = 30, 当前总和: 60
添加 arr[3] = 40, 当前总和: 100
添加 arr[4] = 50, 当前总和: 150
数组总和: 150
元素值: 10
元素值: 20
元素值: 30
元素值: 40
元素值: 50
重新计算的总和: 150
索引 0: 值 10
索引 1: 值 20
索引 2: 值 30
索引 3: 值 40
索引 4: 值 50
关键点:
- 使用
&符号获取不可变引用,以遍历数组元素。 - 使用
iter()方法获取不可变引用,以遍历数组元素。 - 使用
iter_mut()方法获取可变引用,以修改数组元素。 - 使用
enumerate()方法获取索引和值,以遍历数组元素。 - ...
多维数组
#![allow(unused)] fn main() { fn multidimensional_arrays() { // 二维数组 let matrix: [[i32; 3]; 2] = [ [1, 2, 3], [4, 5, 6], ]; println!("矩阵内容:"); for (i, row) in matrix.iter().enumerate() { for (j, &value) in row.iter().enumerate() { print!("matrix[{}][{}] = {} ", i, j, value); } println!(); } // 访问二维数组元素 let element = matrix[1][2]; // 第二行第三列的值: 6 println!("matrix[1][2] = {}", element); // 三维数组示例 let three_d: [[[i32; 2]; 2]; 2] = [ [[1, 2], [3, 4]], [[5, 6], [7, 8]], ]; println!("三维数组内容:"); for (i, depth) in three_d.iter().enumerate() { for (j, row) in depth.iter().enumerate() { for (k, &value) in row.iter().enumerate() { print!("[{}][{}][{}] = {} ", i, j, k, value); } println!(); } } } }
Result:
矩阵内容:
matrix[0][0] = 1 matrix[0][1] = 2 matrix[0][2] = 3
matrix[1][0] = 4 matrix[1][1] = 5 matrix[1][2] = 6
matrix[1][2] = 6
三维数组内容:
[0][0][0] = 1 [0][0][1] = 2
[0][1][0] = 3 [0][1][1] = 4
[1][0][0] = 5 [1][0][1] = 6
[1][1][0] = 7 [1][1][1] = 8
关键点:
- 使用
get方法安全地访问数组元素,避免越界错误。 - 使用
iter方法遍历数组,并使用enumerate方法获取索引。 - 使用
match表达式检查get方法返回的Option类型,以处理可能越界的情况。 - 使用多维数组时,可以嵌套使用
iter和enumerate方法来遍历每个维度。 - 使用
print!和println!宏来格式化输出。 - ...
数组越界检查
#![allow(unused)] fn main() { fn array_bounds_checking() { let arr = [10, 20, 30]; // 安全的访问 if let Some(&value) = arr.get(1) { println!("arr[1] = {}", value); } // 检查是否越界 match arr.get(5) { Some(value) => println!("arr[5] = {}", value), None => println!("数组越界! 最大索引: {}", arr.len() - 1), } // 数组切片(引用数组的一部分) let slice = &arr[0..2]; // 包含索引0到1 println!("切片内容: {:?}", slice); let slice_to_end = &arr[1..]; // 从索引1到末尾 println!("从索引1开始的切片: {:?}", slice_to_end); let slice_from_start = &arr[..2]; // 从开头到索引2(不包含2) println!("从开头到索引2的切片: {:?}", slice_from_start); let full_slice = &arr[..]; // 整个数组的切片 println!("完整切片: {:?}", full_slice); } }
Result:
arr[1] = 20
数组越界! 最大索引: 2
切片内容: [10, 20]
从索引1开始的切片: [20, 30]
从开头到索引2的切片: [10, 20]
完整切片: [10, 20, 30]
关键点:
- 使用
get方法安全地访问数组元素,避免越界错误。 - 使用
match表达式检查get方法返回的Option类型,以处理可能越界的情况。 - 使用数组切片(引用数组的一部分)来访问数组的一部分,避免越界错误。
- ...
实用数组操作
#![allow(unused)] fn main() { fn array_operations() { let mut numbers = [64, 34, 25, 12, 22, 11, 90]; println!("原始数组: {:?}", numbers); // 查找最大值和最小值 let max = numbers.iter().max().unwrap(); let min = numbers.iter().min().unwrap(); println!("最大值: {}, 最小值: {}", max, min); // 计算数组总和和平均值 let sum: i32 = numbers.iter().sum(); let average = sum as f64 / numbers.len() as f64; println!("总和: {}, 平均值: {:.2}", sum, average); // 过滤和变换 let even_numbers: Vec<_> = numbers.iter() .filter(|&&x| x % 2 == 0) .copied() .collect(); println!("偶数: {:?}", even_numbers); let squared: Vec<_> = numbers.iter() .map(|&x| x * x) .collect(); println!("平方: {:?}", squared); // 检查是否包含某个值 let contains_25 = numbers.contains(&25); let position = numbers.iter().position(|&x| x == 25); println!("包含25: {}, 位置: {:?}", contains_25, position); // 排序 let mut sorted = numbers; sorted.sort(); println!("排序后: {:?}", sorted); } }
Result:
原始数组: [64, 34, 25, 12, 22, 11, 90]
最大值: 90, 最小值: 11
总和: 258, 平均值: 36.86
偶数: [64, 34, 12, 22, 90]
平方: [4096, 1156, 625, 144, 484, 121, 8100]
包含25: true, 位置: Some(2)
排序后: [11, 12, 22, 25, 34, 64, 90]
关键点:
iter()方法返回一个迭代器,用于遍历数组元素。sum()方法计算数组元素的总和。copied()方法将迭代器中的元素复制到新向量中。filter()方法用于过滤数组元素。map()方法用于变换数组元素。contains()方法用于检查数组是否包含某个值。position()方法用于查找数组中某个值的索引。sort()方法用于对数组进行排序。enumerate()方法用于同时获取数组元素的索引和值。- ...
字符串数组和字符处理
#![allow(unused)] fn main() { fn string_and_char_arrays() { // 字符串数组 let fruits = ["苹果", "香蕉", "橙子", "葡萄"]; for (i, fruit) in fruits.iter().enumerate() { println!("fruits[{}] = {}", i, fruit); } // 字符数组 let word = ['R', 'u', 's', 't']; let word_str: String = word.iter().collect(); println!("字符数组转换为字符串: {}", word_str); // 字符数组的遍历 for char in &word { println!("字符: {}", char); // 转换为ASCII码 println!("ASCII码: {}", *char as u8); } // 计算字符串的长度(以字符计) let multi_char_str = "你好,世界! 🌍"; let chars: Vec<char> = multi_char_str.chars().collect(); println!("字符串: {}", multi_char_str); println!("字符数量: {}", chars.len()); println!("字节长度: {}", multi_char_str.len()); } }
Result:
fruits[0] = 苹果
fruits[1] = 香蕉
fruits[2] = 橙子
fruits[3] = 葡萄
字符数组转换为字符串: Rust
字符: R
ASCII码: 82
字符: u
ASCII码: 117
字符: s
ASCII码: 115
字符: t
ASCII码: 116
字符串: 你好,世界! 🌍
字符数量: 8
字节长度: 23
关键点:
iter()方法用于获取数组的迭代器。enumerate()方法用于同时获取数组元素的索引和值。collect()方法用于将迭代器转换为集合类型。chars()方法用于将字符串转换为字符迭代器。len()方法用于获取字符串的长度(以字符计)。- ...
字符串切片
#![allow(unused)] fn main() { ### 数组在函数中的应用 ```rust fn array_in_functions() { let arr = [1, 2, 3, 4, 5]; // 传递数组引用 let sum = sum_array(&arr); let max = max_array(&arr); println!("数组: {:?}", arr); println!("总和: {}, 最大值: {}", sum, max); // 修改数组(需要mut) let mut mut_arr = [10, 20, 30]; modify_array(&mut mut_arr); println!("修改后: {:?}", mut_arr); // 返回数组 let squared = square_array(&arr); println!("平方后: {:?}", squared); } // 计算数组总和 fn sum_array(arr: &[i32]) -> i32 { arr.iter().sum() } // 找最大值 fn max_array(arr: &[i32]) -> i32 { arr.iter().max().copied().unwrap_or(0) } // 修改数组元素 fn modify_array(arr: &mut [i32]) { for i in 0..arr.len() { arr[i] *= 2; } } // 返回平方数组 fn square_array(arr: &[i32]) -> Vec<i32> { arr.iter().map(|&x| x * x).collect() } }
Result:
数组: [1, 2, 3, 4, 5]
总和: 15, 最大值: 5
修改后: [20, 40, 60]
平方后: [1, 4, 9, 16, 25]
关键点:
&arr传递数组引用,避免所有权转移。&mut mut_arr传递可变数组引用,允许修改数组。&arr返回数组引用,避免所有权转移。&arr作为参数传递时,不需要显式地使用&,因为数组引用已经是引用类型。&arr返回数组引用时,不需要显式地使用&,因为数组引用已经是引用类型。&mut mut_arr作为参数传递时,需要显式地使用&mut,因为数组引用是可变引用类型。&mut mut_arr返回数组引用时,需要显式地使用&mut,因为数组引用是可变引用类型。&arr和&mut mut_arr在函数内部都是引用类型,不需要显式地使用&或&mut。- ...
2.4 字符串类型
2.4.1 字符串字面量和切片
Rust 中的字符串字面量是不可变的、硬编码的文本,使用双引号定义,如 "hello"。它们是 &str 类型(字符串切片),指向静态内存中的 UTF-8 编码字节序列。切片(slice)是引用现有数据的视图,如 &[T],字符串切片 &str 是对字符串的不可变引用。
#![allow(unused)] fn main() { fn string_slices() { // 字符串字面量(&str)- 编译时常量 let greeting = "Hello, Rust!"; let name = "World"; // 切片(不拥有所有权) let slice = &greeting[0..5]; // "Hello" let slice_from_middle = &greeting[7..11]; // "Rust" println!("完整问候: {}", greeting); println!("切片: {}", slice); // 字符串方法 let trimmed = " hello ".trim(); // "hello" let uppercase = "rust".to_uppercase(); // "RUST" let lowercase = "RUST".to_lowercase(); // "rust" // 查找和分割 let text = "one,two,three,four"; let parts: Vec<&str> = text.split(',').collect(); println!("分割结果: {:?}", parts); // 替换 let replaced = "hello world".replace("world", "Rust"); println!("替换后: {}", replaced); } }
Result:
完整问候: Hello, Rust!
切片: Hello
分割结果: ["one", "two", "three", "four"]
替换后: hello Rust
关键点:
- 字面量生命周期为 'static,不可变,它们是
&str类型。 - 切片不拥有数据,仅借用;索引必须是有效 UTF-8 边界,否则 panic。
- 常用作函数参数,促进零拷贝。
- 字符串切片是引用类型,可以引用字符串字面量或
String类型。 - 字符串切片可以用于字符串操作,如查找、替换和分割等。
- 字符串切片不拥有所有权,因此它们的生命周期不能超过它们引用的数据的生命周期。
- 字符串切片可以用于字符串字面量和
String类型,但不能用于char类型。 - 字符串切片可以通过
&str类型转换为String类型,但需要使用to_string方法。 - 字符串切片可以通过
split方法分割为多个子字符串,并返回一个Vec<&str>类型的结果。 - ...
2.4.2 String 类型
String 是可增长、可变、拥有的 UTF-8 编码字符串,存储在堆上。不同于 &str,String 拥有数据,可以修改(如追加)。它实现了 Deref 到 &str,允许隐式转换为切片。
#![allow(unused)] fn main() { fn string_type() { // String 类型(拥有所有权) let mut s = String::new(); // 空字符串 let s1 = String::from("hello"); // 从字符串字面量创建 let s2 = "world".to_string(); // 转换为 String // 追加内容 s.push('A'); // 追加字符 s.push_str("pple"); // 追加字符串 s += " Banana"; // 连接操作符 println!("字符串 s: {}", s); // 格式化 let name = "Alice"; let age = 30; let formatted = format!("{} is {} years old", name, age); println!("格式化: {}", formatted); // 使用宏 println!("测试值: {}, 另一个值: {}", 42, "text"); // 所有权示例 let original = String::from("original"); let moved = original; // 所有权转移 // println!("{}", original); // 编译错误!original 已移动 println!("移动后的字符串: {}", moved); } }
Result:
字符串 s: Apple Banana
格式化: Alice is 30 years old
测试值: 42, 另一个值: text
移动后的字符串: original
关键点:
String类型是拥有所有权的,因此需要使用String::from或to_string方法来创建String实例。push和push_str方法用于向String实例中追加内容。- 所有权规则适用:移动后不可用,除非克隆(.clone())。
format!宏用于格式化字符串。println!宏用于打印字符串。String的所有权转移示例展示了所有权转移的概念,即original在赋值给moved后,original不再有效。- ...
2.5 控制流
2.5.1 if 条件语句
Rust 的 if 是表达式,可返回值的条件分支。无需括号包围条件,支持 else 和 else if。条件必须是 bool 类型,无隐式转换。
#![allow(unused)] fn main() { fn conditional_statements() { let number = 7; // 基本 if-else if number < 5 { println!("数字小于 5"); } else if number == 5 { println!("数字等于 5"); } else { println!("数字大于 5"); } // if 作为表达式(返回值) let grade = if number >= 90 { "A" } else if number >= 80 { "B" } else if number >= 70 { "C" } else { "F" }; println!("成绩: {}", grade); // 条件赋值 let status = if number % 2 == 0 { "偶数" } else { "奇数" }; println!("{} 是 {}", number, status); } }
Result:
数字大于 5
成绩: F
7 是 奇数
关键点:
if语句的基本用法,包括条件判断和分支执行。if语句可以作为表达式返回值。if语句的嵌套使用。- 条件赋值的使用,根据条件为变量赋值。
if语句的执行顺序,根据条件判断结果选择执行相应的分支。if语句的返回值,可以是任意类型,但需要与上下文中的变量类型匹配。- ...
2.5.2 循环控制
loop 无穷循环
#![allow(unused)] fn main() { ## 2.5.2 循环控制 ### loop 无穷循环 `loop` 是无限循环,直到显式 `break`。可返回值的表达式,支持标签用于嵌套循环控制。 ```rust fn loop_examples() { // 基本 loop let mut counter = 0; loop { counter += 1; println!("计数器: {}", counter); if counter >= 5 { break; // 退出循环 } } // loop 作为表达式(返回值) let result = loop { counter += 1; if counter == 10 { break counter; // 退出并返回值 } }; println!("循环结果: {}", result); } }
Result:
计数器: 1
计数器: 2
计数器: 3
计数器: 4
计数器: 5
循环结果: 10
关键点:
break退出循环break可以返回值continue跳过本次循环loop可以作为表达式,返回值loop可以嵌套loop可以与标签(label)配合使用,用于退出多层嵌套循环,标签如 'outer: loop {} 用于多层控制。- ...
while 条件循环
while 是条件循环,在条件为 true 时执行。条件必须是 bool。
#![allow(unused)] fn main() { fn while_examples() { let mut number = 3; while number != 0 { println!("倒计时: {}", number); number -= 1; } println!("发射!"); // 数组遍历 let array = [10, 20, 30, 40, 50]; let mut index = 0; while index < array.len() { println!("索引 {}: 值 {}", index, array[index]); index += 1; } } }
Result:
倒计时: 3
倒计时: 2
倒计时: 1
发射!
索引 0: 值 10
索引 1: 值 20
索引 2: 值 30
索引 3: 值 40
索引 4: 值 50
Result:
- 无 do-while,但可用 loop 模拟。
- 适合未知迭代次数但有条件的情况。
while循环可以遍历数组,循环也可以嵌套while循环可以与标签(label)配合使用,用于退出多层嵌套循环while循环可以与break和continue配合使用,用于控制循环的执行while循环可以与loop配合使用,用于创建无限循环while循环可以与for循环配合使用,用于遍历集合while循环可以与match配合使用,用于处理多种情况while循环可以与if配合使用,用于条件判断while循环可以与let配合使用,用于声明变量while循环可以与return配合使用,用于返回值while循环可以与yield配合使用,用于生成器函数while循环可以与try配合使用,用于处理错误while循环可以与await配合使用,用于异步编程while循环可以与panic!配合使用,用于处理异常while循环可以与println!配合使用,用于输出日志while循环可以与debug!、info!等配合使用,用于调试while循环可以与assert!配合使用,用于断言while循环可以与unwrap!、expect!配合使用,用于处理Option和Result等等- ...
for 循环和迭代器
for 用于迭代集合,如范围或迭代器。语法 for item in iterator {},高效处理所有权。
#![allow(unused)] fn main() { fn for_loop_examples() { // 基本 for 循环 for i in 0..5 { // 0 到 4 println!("for 循环: {}", i); } // 包含结束值的 range for i in 0..=5 { // 0 到 5 println!("包含结束值: {}", i); } // 数组迭代 let array = [1, 2, 3, 4, 5]; for item in array { println!("数组项: {}", item); } // 索引和值的迭代 let names = vec!["Alice", "Bob", "Charlie"]; for (index, name) in names.iter().enumerate() { println!("{}: {}", index, name); } // 字符串字符迭代 let text = "Rust"; for char in text.chars() { println!("字符: {}", char); } // 字节迭代 for byte in text.bytes() { println!("字节: {}", byte); } } }
Result:
for 循环: 0
for 循环: 1
for 循环: 2
for 循环: 3
for 循环: 4
包含结束值: 0
包含结束值: 1
包含结束值: 2
包含结束值: 3
包含结束值: 4
包含结束值: 5
数组项: 1
数组项: 2
数组项: 3
数组项: 4
数组项: 5
0: Alice
1: Bob
2: Charlie
字符: R
字符: u
字符: s
字符: t
字节: 82
字节: 117
字节: 115
字节: 116
关键点:
for循环用于遍历范围、数组、向量、字符串等。0..5创建一个从 0 到 4 的范围。0..=5创建一个从 0 到 5 的范围(包含 5)。array是一个数组,names是一个向量。enumerate方法用于获取索引和值。- ...
2.5.3 模式匹配
模式匹配通过 match 表达式解构值,处理多种情况。必须穷尽所有可能(或用 _ 通配)。支持绑定、守卫和嵌套。
#![allow(unused)] fn main() { fn pattern_matching() { let x = 42; match x { 0 => println!("零"), 1..=10 => println!("一到十之间"), 20 | 30 | 40 => println!("20, 30 或 40"), n if n % 2 == 0 => println!("偶数: {}", n), _ => println!("其他值: {}", x), // 通配符 } // 绑定值的模式 match x { 0 => println!("零"), 1 => println!("一"), n => println!("其他: {}", n), // 绑定值 } // 复合模式 let point = (0, 7); match point { (0, 0) => println!("原点"), (0, y) => println!("在 Y 轴上: y = {}", y), (x, 0) => println!("在 X 轴上: x = {}", x), (x, y) => println!("点 ({}, {})", x, y), } } }
Result:
偶数: 42
其他: 42
在 Y 轴上: y = 7
关键点:
- 模式如绑定(x @ 1..=5)、解构(元组、结构体)。
- 与 if let、while let 结合简化可选匹配。
match语句用于模式匹配。if表达式可以在match分支中使用。_是通配符,匹配任何值。- 绑定值可以在
match分支中使用。 - 复合模式可以匹配多个值。
match语句必须覆盖所有可能的值。match语句可以返回值。match语句用途广泛,可用于错误处理、枚举类型、元组、结构体、引用(可变/不可变)、切片、数组、字符串、闭包、函数、宏、模式匹配、类型转换、类型检查等场景。- ...
2.6 函数定义与调用
2.6.1 函数基础
Rust 中的函数是代码的可重用块,通过 fn 关键字定义。函数名使用蛇形命名法(snake_case),并可接受参数和返回值。每个程序至少有一个 main 函数作为入口。函数体用 {}包围,支持表达式和语句。Rust 函数是静态类型的,确保类型安全。
#![allow(unused)] fn main() { // 函数定义 fn greet(name: &str) { println!("你好, {}!", name); } // 有返回值的函数 fn add(a: i32, b: i32) -> i32 { a + b // 没有分号表示返回值 } // 显式返回语句 fn multiply(x: i32, y: i32) -> i32 { return x * y; // 显式返回 } // 函数调用 fn function_examples() { greet("Rust"); let sum = add(5, 3); let product = multiply(4, 7); println!("5 + 3 = {}", sum); println!("4 × 7 = {}", product); // 函数作为表达式 let result = { let a = 10; let b = 20; a + b // 块表达式返回值 }; println!("块表达式结果: {}", result); } }
Result:
你好, Rust!
5 + 3 = 8
4 × 7 = 28
块表达式结果: 30
关键点:
- 函数定义使用
fn关键字。 - 函数参数类型在参数名后面指定。
- 函数返回值类型在
->后面指定。 - 函数体中的最后一行表达式是返回值,不需要分号。
- 函数调用使用函数名和参数列表。
- 函数可以返回值,也可以不返回值。
- 函数可以包含块表达式,块表达式的最后一行是返回值。
- 函数可以嵌套定义,但不能嵌套调用。
- 函数可以递归调用,但不能递归定义。
- 函数可以接受可变参数,但不能接受可变参数数量。
- ...
2.6.2 函数参数和返回值
函数参数在括号中定义,指定类型。Rust 使用所有权系统:参数可通过值传递(移动所有权)或引用(借用)。可变参数需用 mut。参数是不可变的,除非显式标记。
函数通过 -> Type 指定返回值类型。最后表达式隐式返回,或用 return 显式返回。无返回值默认为 ()(单元类型)。多值返回用元组。
#![allow(unused)] fn main() { // 多个参数 fn calculate_area(length: f64, width: f64) -> f64 { length * width } // 可变参数数量 fn print_values(values: &[i32]) { for value in values { println!("值: {}", value); } } // 元组返回 fn get_coordinates() -> (i32, i32) { (10, 20) } // 命名返回结构 #[derive(Debug)] struct Rectangle { width: f64, height: f64, } fn create_rectangle(width: f64, height: f64) -> Rectangle { Rectangle { width, height } } fn calculate_rectangle_area(rect: &Rectangle) -> f64 { rect.width * rect.height } fn function_parameters() { let area = calculate_area(5.0, 3.0); println!("矩形面积: {}", area); let values = vec![1, 2, 3, 4, 5]; print_values(&values); let (x, y) = get_coordinates(); println!("坐标: ({}, {})", x, y); let rectangle = create_rectangle(4.0, 6.0); let rect_area = calculate_rectangle_area(&rectangle); println!("矩形面积: {}", rect_area); } }
Result:
矩形面积: 15
值: 1
值: 2
值: 3
值: 4
值: 5
坐标: (10, 20)
矩形面积: 24
关键点:
- Rust 中的函数参数和返回值类型必须显式声明。
- Rust 中的函数参数和返回值类型可以是泛型、元组、结构体、枚举、闭包等多种类型。
- ...
2.6.3 高阶函数示例
#![allow(unused)] fn main() { // 基础函数 fn add(a: i32, b: i32) -> i32 { a + b } fn multiply(a: i32, b: i32) -> i32 { a * b } // 函数作为参数 fn apply_function<F>(value: i32, f: F) -> i32 where F: Fn(i32) -> i32, { f(value) } // 函数作为返回值 fn get_operation(operation: &str) -> fn(i32, i32) -> i32 { match operation { "add" => add, "multiply" => multiply, _ => add, // 默认操作 } } fn higher_order_functions() { let result1 = apply_function(5, |x| x * x); // 闭包 let result2 = apply_function(10, |x| x + 100); // 另一个闭包 println!("平方: {}", result1); println!("加100: {}", result2); let operation = get_operation("add"); let result3 = operation(15, 25); println!("函数指针结果: {}", result3); } }
Result:
平方: 25
加100: 110
函数指针结果: 40
关键点:
- Rust 中的函数可以作为参数传递。
- Rust 中的函数可以作为返回值。
- Rust 中的闭包是一种匿名函数,可以作为参数传递或作为返回值。
- Rust 中的函数指针是一种指向函数的指针,可以作为参数传递或作为返回值。
- Rust 中的泛型函数可以接受不同类型的参数,并返回不同类型的值。
- Rust 中的函数可以接受元组、结构体、枚举等多种类型的参数,并返回不同类型的值。
- Rust 中的函数可以接受可变参数,并返回不同类型的值。
- ...
2.7 实践项目:科学计算器与数据处理工具
2.7.1 项目需求分析
创建一个功能完善的科学计算器,支持:
- 基础和科学运算
- 表达式解析和求值
- 数据统计分析
- 历史记录功能
2.7.2 项目结构设计
// src/main.rs mod calculator; mod data; mod history; mod utils; use calculator::{Calculator, Operation}; use data::Statistics; use history::HistoryManager; use utils::Error; fn main() -> Result<(), Error> { println!("=== 科学计算器 v1.0 ==="); let mut calculator = Calculator::new(); let mut history = HistoryManager::new(); // 示例计算 run_example_calculations(&mut calculator, &mut history)?; Ok(()) } fn run_example_calculations( calc: &mut Calculator, history: &mut HistoryManager ) -> Result<(), Error> { // 基础运算 let result1 = calc.add(10.0, 5.0)?; println!("10 + 5 = {}", result1); history.add_record("10 + 5", result1); let result2 = calc.multiply(result1, 2.0)?; println!("({}) × 2 = {}", result1, result2); history.add_record("10 + 5 * 2", result2); // 科学运算 let result3 = calc.sqrt(16.0)?; println!("√16 = {}", result3); history.add_record("√16", result3); let result4 = calc.sin(30.0_f64.to_radians())?; println!("sin(30°) = {}", result4); history.add_record("sin(30°)", result4); // 表达式求值 let expr_result = calc.evaluate_expression("(10 + 5) * 2 - √16")?; println!("(10 + 5) * 2 - √16 = {}", expr_result); history.add_record("(10 + 5) * 2 - √16", expr_result); // 统计计算 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]; let stats = calc.calculate_statistics(&data)?; println!("数据集统计: {:?}", stats); history.display(); Ok(()) }
2.7.3 计算器核心模块
#![allow(unused)] fn main() { // src/calculator/mod.rs pub mod operations; pub mod parser; pub mod evaluator; use operations::Operation; use parser::ExpressionParser; use evaluator::ExpressionEvaluator; use utils::Error; pub struct Calculator { parser: ExpressionParser, evaluator: ExpressionEvaluator, } impl Calculator { pub fn new() -> Self { Self { parser: ExpressionParser::new(), evaluator: ExpressionEvaluator::new(), } } // 基础运算方法 pub fn add(&self, a: f64, b: f64) -> Result<f64, Error> { Ok(a + b) } pub fn subtract(&self, a: f64, b: f64) -> Result<f64, Error> { Ok(a - b) } pub fn multiply(&self, a: f64, b: f64) -> Result<f64, Error> { Ok(a * b) } pub fn divide(&self, a: f64, b: f64) -> Result<f64, Error> { if b == 0.0 { return Err(Error::DivisionByZero); } Ok(a / b) } pub fn power(&self, base: f64, exponent: f64) -> Result<f64, Error> { Ok(base.powf(exponent)) } pub fn sqrt(&self, value: f64) -> Result<f64, Error> { if value < 0.0 { return Err(Error::NegativeSquareRoot); } Ok(value.sqrt()) } pub fn sin(&self, angle: f64) -> Result<f64, Error> { Ok(angle.sin()) } pub fn cos(&self, angle: f64) -> Result<f64, Error> { Ok(angle.cos()) } pub fn tan(&self, angle: f64) -> Result<f64, Error> { Ok(angle.tan()) } pub fn ln(&self, value: f64) -> Result<f64, Error> { if value <= 0.0 { return Err(Error::InvalidLogarithm); } Ok(value.ln()) } pub fn log(&self, value: f64, base: f64) -> Result<f64, Error> { if value <= 0.0 || base <= 0.0 || base == 1.0 { return Err(Error::InvalidLogarithm); } Ok(value.log(base)) } pub fn factorial(&self, n: u64) -> Result<f64, Error> { if n > 20 { return Err(Error::FactorialTooLarge); } Ok((1..=n).product::<u64>() as f64) } // 表达式求值 pub fn evaluate_expression(&self, expression: &str) -> Result<f64, Error> { let tokens = self.parser.tokenize(expression)?; let ast = self.parser.parse(tokens)?; self.evaluator.evaluate(&ast) } // 统计计算 pub fn calculate_statistics(&self, data: &[f64]) -> Result<Statistics, Error> { if data.is_empty() { return Err(Error::EmptyDataSet); } let n = data.len() as f64; let sum: f64 = data.iter().sum(); let mean = sum / n; // 计算方差 let variance: f64 = data.iter() .map(|&x| (x - mean).powi(2)) .sum::<f64>() / n; let std_dev = variance.sqrt(); // 计算中位数 let mut sorted_data = data.to_vec(); sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap()); let median = if n % 2.0 == 0.0 { (sorted_data[(n as usize / 2) - 1] + sorted_data[n as usize / 2]) / 2.0 } else { sorted_data[n as usize / 2] }; let min = data.iter().cloned().fold(f64::INFINITY, f64::min); let max = data.iter().cloned().fold(f64::NEG_INFINITY, f64::max); // 计算众数 let mut frequency = std::collections::HashMap::new(); for &value in data { *frequency.entry(value).or_insert(0) += 1; } let max_count = frequency.values().max().unwrap_or(&0).clone(); let mode: Vec<f64> = frequency .into_iter() .filter(|&(_, count)| count == max_count) .map(|(value, _)| value) .collect(); Ok(Statistics { count: data.len(), mean, median, mode, variance, std_dev, min, max, sum, }) } } }
2.7.4 表达式解析器
#![allow(unused)] fn main() { // src/calculator/parser.rs use crate::utils::Error; #[derive(Debug, Clone, PartialEq)] pub enum Token { Number(f64), Identifier(String), Operator(Operator), LParen, RParen, Comma, } #[derive(Debug, Clone, PartialEq)] pub enum Operator { Add, Subtract, Multiply, Divide, Power, Sqrt, Sin, Cos, Tan, Ln, Log, Factorial, } #[derive(Debug, Clone)] pub enum AstNode { Number(f64), Identifier(String), UnaryOp(Operator, Box<AstNode>), BinaryOp(Operator, Box<AstNode>, Box<AstNode>), FunctionCall(String, Vec<AstNode>), } pub struct ExpressionParser { // 操作符优先级 precedence: std::collections::HashMap<Operator, i32>, } impl ExpressionParser { pub fn new() -> Self { let mut precedence = std::collections::HashMap::new(); precedence.insert(Operator::Add, 1); precedence.insert(Operator::Subtract, 1); precedence.insert(Operator::Multiply, 2); precedence.insert(Operator::Divide, 2); precedence.insert(Operator::Power, 3); precedence.insert(Operator::Sqrt, 4); precedence.insert(Operator::Factorial, 5); precedence.insert(Operator::Sin, 6); precedence.insert(Operator::Cos, 6); precedence.insert(Operator::Tan, 6); precedence.insert(Operator::Ln, 6); precedence.insert(Operator::Log, 6); Self { precedence } } pub fn tokenize(&self, input: &str) -> Result<Vec<Token>, Error> { let mut tokens = Vec::new(); let mut chars = input.chars().peekable(); while let Some(ch) = chars.next() { match ch { '0'..='9' | '.' => { let mut number_str = ch.to_string(); // 继续读取数字和小数点 while let Some(&next_ch) = chars.peek() { if next_ch.is_numeric() || next_ch == &'.' { number_str.push(chars.next().unwrap()); } else { break; } } let number: f64 = number_str.parse() .map_err(|_| Error::InvalidNumber(number_str))?; tokens.push(Token::Number(number)); } 'a'..='z' | 'A'..='Z' | '_' => { let mut ident = ch.to_string(); // 继续读取标识符字符 while let Some(&next_ch) = chars.peek() { if next_ch.is_alphanumeric() || next_ch == &'_' { ident.push(chars.next().unwrap()); } else { break; } } tokens.push(Token::Identifier(ident)); } '+' => tokens.push(Token::Operator(Operator::Add)), '-' => tokens.push(Token::Operator(Operator::Subtract)), '*' => tokens.push(Token::Operator(Operator::Multiply)), '/' => tokens.push(Token::Operator(Operator::Divide)), '^' => tokens.push(Token::Operator(Operator::Power)), '(' => tokens.push(Token::LParen), ')' => tokens.push(Token::RParen), ',' => tokens.push(Token::Comma), ' ' | '\t' | '\n' | '\r' => continue, // 跳过空白字符 _ => return Err(Error::InvalidCharacter(ch)), } } Ok(tokens) } pub fn parse(&self, tokens: Vec<Token>) -> Result<AstNode, Error> { let mut output = Vec::new(); let mut operators = Vec::new(); for token in tokens { match token { Token::Number(n) => output.push(AstNode::Number(n)), Token::Identifier(ident) => output.push(AstNode::Identifier(ident)), Token::Operator(op) => { while let Some(Token::Operator(prev_op)) = operators.last() { if self.get_precedence(prev_op) >= self.get_precedence(&op) { self.pop_operator_to_output(&mut operators, &mut output)?; } else { break; } } operators.push(Token::Operator(op)); } Token::LParen => operators.push(token), Token::RParen => { while let Some(op) = operators.pop() { match op { Token::LParen => break, Token::Operator(op) => self.pop_operator_to_output(&operators, &mut output)?, _ => return Err(Error::MismatchedParen), } } } Token::Comma => { while let Some(token) = operators.pop() { match token { Token::LParen => return Err(Error::MismatchedParen), Token::Operator(op) => self.pop_operator_to_output(&operators, &mut output)?, _ => {} } } } } } while let Some(token) = operators.pop() { match token { Token::Operator(op) => self.pop_operator_to_output(&operators, &mut output)?, Token::LParen | Token::RParen | Token::Comma => return Err(Error::MismatchedParen), } } if output.len() != 1 { return Err(Error::InvalidExpression); } Ok(output.remove(0)) } fn get_precedence(&self, op: &Operator) -> i32 { *self.precedence.get(op).unwrap_or(&0) } fn pop_operator_to_output( &self, operators: &mut Vec<Token>, output: &mut Vec<AstNode> ) -> Result<(), Error> { if let Some(Token::Operator(op)) = operators.pop() { match op { Operator::Sqrt | Operator::Sin | Operator::Cos | Operator::Tan | Operator::Ln | Operator::Factorial => { if let Some(operand) = output.pop() { output.push(AstNode::UnaryOp(op, Box::new(operand))); } else { return Err(Error::InsufficientOperands); } } _ => { if let (Some(right), Some(left)) = (output.pop(), output.pop()) { output.push(AstNode::BinaryOp(op, Box::new(left), Box::new(right))); } else { return Err(Error::InsufficientOperands); } } } } Ok(()) } } }
2.7.5 表达式求值器
#![allow(unused)] fn main() { // src/calculator/evaluator.rs use super::parser::{AstNode, Operator}; use crate::utils::Error; pub struct ExpressionEvaluator { functions: std::collections::HashMap<String, fn(&[f64]) -> Result<f64, Error>>, } impl ExpressionEvaluator { pub fn new() -> Self { let mut functions = std::collections::HashMap::new(); // 注册内置函数 functions.insert("sqrt".to_string(), |args| { if args.len() != 1 { return Err(Error::InvalidArgumentCount("sqrt".to_string(), 1, args.len())); } if args[0] < 0.0 { return Err(Error::NegativeSquareRoot); } Ok(args[0].sqrt()) }); functions.insert("sin".to_string(), |args| { if args.len() != 1 { return Err(Error::InvalidArgumentCount("sin".to_string(), 1, args.len())); } Ok(args[0].sin()) }); functions.insert("cos".to_string(), |args| { if args.len() != 1 { return Err(Error::InvalidArgumentCount("cos".to_string(), 1, args.len())); } Ok(args[0].cos()) }); functions.insert("tan".to_string(), |args| { if args.len() != 1 { return Err(Error::InvalidArgumentCount("tan".to_string(), 1, args.len())); } Ok(args[0].tan()) }); functions.insert("ln".to_string(), |args| { if args.len() != 1 { return Err(Error::InvalidArgumentCount("ln".to_string(), 1, args.len())); } if args[0] <= 0.0 { return Err(Error::InvalidLogarithm); } Ok(args[0].ln()) }); functions.insert("log".to_string(), |args| { if args.len() != 2 { return Err(Error::InvalidArgumentCount("log".to_string(), 2, args.len())); } if args[0] <= 0.0 || args[1] <= 0.0 || args[1] == 1.0 { return Err(Error::InvalidLogarithm); } Ok(args[0].log(args[1])) }); functions.insert("factorial".to_string(), |args| { if args.len() != 1 { return Err(Error::InvalidArgumentCount("factorial".to_string(), 1, args.len())); } let n = args[0] as u64; if args[0] < 0.0 || args[0] - n as f64 != 0.0 { return Err(Error::InvalidFactorialArgument); } if n > 20 { return Err(Error::FactorialTooLarge); } Ok((1..=n).product::<u64>() as f64) }); Self { functions } } pub fn evaluate(&self, ast: &AstNode) -> Result<f64, Error> { match ast { AstNode::Number(n) => Ok(*n), AstNode::Identifier(ident) => { // 处理常量和变量 match ident.as_str() { "pi" => Ok(std::f64::consts::PI), "e" => Ok(std::f64::consts::E), _ => Err(Error::UndefinedVariable(ident.clone())), } } AstNode::UnaryOp(op, operand) => { let value = self.evaluate(operand)?; self.evaluate_unary_op(*op, value) } AstNode::BinaryOp(op, left, right) => { let left_val = self.evaluate(left)?; let right_val = self.evaluate(right)?; self.evaluate_binary_op(*op, left_val, right_val) } AstNode::FunctionCall(name, args) => { let arg_values: Result<Vec<f64>, _> = args.iter().map(|arg| self.evaluate(arg)).collect(); let arg_values = arg_values?; if let Some(func) = self.functions.get(name) { func(&arg_values) } else { Err(Error::UndefinedFunction(name.clone())) } } } } fn evaluate_unary_op(&self, op: Operator, value: f64) -> Result<f64, Error> { match op { Operator::Sqrt => { if value < 0.0 { Err(Error::NegativeSquareRoot) } else { Ok(value.sqrt()) } } Operator::Sin => Ok(value.sin()), Operator::Cos => Ok(value.cos()), Operator::Tan => Ok(value.tan()), Operator::Ln => { if value <= 0.0 { Err(Error::InvalidLogarithm) } else { Ok(value.ln()) } } Operator::Factorial => { if value < 0.0 || value.fract() != 0.0 { return Err(Error::InvalidFactorialArgument); } let n = value as u64; if n > 20 { return Err(Error::FactorialTooLarge); } Ok((1..=n).product::<u64>() as f64) } _ => Err(Error::InvalidOperator), } } fn evaluate_binary_op(&self, op: Operator, left: f64, right: f64) -> Result<f64, Error> { match op { Operator::Add => Ok(left + right), Operator::Subtract => Ok(left - right), Operator::Multiply => Ok(left * right), Operator::Divide => { if right == 0.0 { Err(Error::DivisionByZero) } else { Ok(left / right) } } Operator::Power => Ok(left.powf(right)), _ => Err(Error::InvalidOperator), } } } }
2.7.6 数据统计模块
#![allow(unused)] fn main() { // src/data/mod.rs pub mod types; pub mod statistics; use types::Statistics; // 重新导出 pub use statistics::Statistics; }
#![allow(unused)] fn main() { // src/data/statistics.rs use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Statistics { pub count: usize, pub mean: f64, pub median: f64, pub mode: Vec<f64>, pub variance: f64, pub std_dev: f64, pub min: f64, pub max: f64, pub sum: f64, } impl Statistics { pub fn print_detailed(&self) { println!("=== 统计信息 ==="); println!("数据个数: {}", self.count); println!("总和: {:.2}", self.sum); println!("平均值: {:.2}", self.mean); println!("中位数: {:.2}", self.median); println!("众数: {:?}", self.mode.iter() .map(|&x| format!("{:.2}", x)) .collect::<Vec<_>>() .join(", ")); println!("最小值: {:.2}", self.min); println!("最大值: {:.2}", self.max); println!("方差: {:.4}", self.variance); println!("标准差: {:.4}", self.std_dev); println!("==============="); } pub fn get_range(&self) -> f64 { self.max - self.min } pub fn get_coefficient_of_variation(&self) -> f64 { if self.mean == 0.0 { 0.0 } else { self.std_dev / self.mean.abs() } } } // 线性回归 pub struct LinearRegression { pub slope: f64, pub intercept: f64, pub r_squared: f64, } impl LinearRegression { pub fn new(x_data: &[f64], y_data: &[f64]) -> Option<Self> { if x_data.len() != y_data.len() || x_data.is_empty() { return None; } let n = x_data.len() as f64; let sum_x: f64 = x_data.iter().sum(); let sum_y: f64 = y_data.iter().sum(); let sum_xy: f64 = x_data.iter().zip(y_data.iter()) .map(|(&x, &y)| x * y).sum(); let sum_x2: f64 = x_data.iter().map(|&x| x * x).sum(); let sum_y2: f64 = y_data.iter().map(|&y| y * y).sum(); let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x); let intercept = (sum_y - slope * sum_x) / n; // 计算 R² let ss_tot: f64 = y_data.iter() .map(|&y| (y - sum_y / n).powi(2)) .sum(); let ss_res: f64 = x_data.iter().zip(y_data.iter()) .map(|(&x, &y)| { let predicted = slope * x + intercept; (y - predicted).powi(2) }) .sum(); let r_squared = 1.0 - (ss_res / ss_tot); Some(Self { slope, intercept, r_squared, }) } pub fn predict(&self, x: f64) -> f64 { self.slope * x + self.intercept } pub fn print_equation(&self) { println!("线性回归方程: y = {:.4}x + {:.4}", self.slope, self.intercept); println!("决定系数 (R²): {:.4}", self.r_squared); } } }
2.7.7 历史记录管理
#![allow(unused)] fn main() { // src/history/mod.rs use serde::{Deserialize, Serialize}; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HistoryRecord { pub expression: String, pub result: f64, pub timestamp: chrono::DateTime<chrono::Utc>, } pub struct HistoryManager { records: Vec<HistoryRecord>, max_records: usize, } impl HistoryManager { pub fn new() -> Self { Self::with_capacity(100) } pub fn with_capacity(capacity: usize) -> Self { let records = Self::load_from_file().unwrap_or_default(); Self { records, max_records: capacity, } } pub fn add_record(&mut self, expression: &str, result: f64) { let record = HistoryRecord { expression: expression.to_string(), result, timestamp: chrono::Utc::now(), }; self.records.push(record); // 保持记录数量限制 if self.records.len() > self.max_records { self.records.remove(0); } // 保存到文件 self.save_to_file().ok(); } pub fn get_recent_records(&self, count: usize) -> &[HistoryRecord] { let start = if self.records.len() > count { self.records.len() - count } else { 0 }; &self.records[start..] } pub fn search_records(&self, query: &str) -> Vec<&HistoryRecord> { self.records .iter() .filter(|record| record.expression.contains(query) || record.result.to_string().contains(query) ) .collect() } pub fn clear(&mut self) { self.records.clear(); self.save_to_file().ok(); } pub fn display(&self) { if self.records.is_empty() { println!("暂无计算历史"); return; } println!("=== 计算历史 ==="); for (i, record) in self.records.iter().enumerate() { println!("{}. {} = {}", i + 1, record.expression, record.result ); } println!("==============="); } fn get_history_file() -> std::path::PathBuf { let mut path = dirs::home_dir().unwrap_or_default(); path.push(".rust_calculator_history.json"); path } fn load_from_file() -> io::Result<Vec<HistoryRecord>> { let path = Self::get_history_file(); if !path.exists() { return Ok(Vec::new()); } let file = File::open(path)?; let reader = BufReader::new(file); let records: Vec<HistoryRecord> = serde_json::from_reader(reader) .unwrap_or_default(); Ok(records) } fn save_to_file(&self) -> io::Result<()> { let path = Self::get_history_file(); // 确保目录存在 if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } let file = File::create(path)?; serde_json::to_writer_pretty(file, &self.records)?; Ok(()) } } }
2.7.8 错误处理
#![allow(unused)] fn main() { // src/utils/mod.rs pub mod error; pub use error::Error; }
#![allow(unused)] fn main() { // src/utils/error.rs use serde::{Deserialize, Serialize}; #[derive(Debug, thiserror::Error, Serialize, Deserialize)] pub enum Error { #[error("除零错误")] DivisionByZero, #[error("负数开平方根: {0}")] NegativeSquareRoot, #[error("无效的对数: 底数必须 > 0 且 ≠ 1, 真数必须 > 0")] InvalidLogarithm, #[error("阶乘参数无效: 必须是非负整数")] InvalidFactorialArgument, #[error("阶乘值过大: n > 20")] FactorialTooLarge, #[error("空数据集")] EmptyDataSet, #[error("无效数字: {0}")] InvalidNumber(String), #[error("无效字符: {0}")] InvalidCharacter(char), #[error("括号不匹配")] MismatchedParen, #[error("无效表达式")] InvalidExpression, #[error("操作数不足")] InsufficientOperands, #[error("无效操作符")] InvalidOperator, #[error("未定义变量: {0}")] UndefinedVariable(String), #[error("未定义函数: {0}")] UndefinedFunction(String), #[error("函数 {0} 参数数量错误: 期望 {1}, 实际 {2}")] InvalidArgumentCount(String, usize, usize), #[error("输入/输出错误: {0}")] Io(#[from] std::io::Error), #[error("JSON 序列化错误: {0}")] Json(#[from] serde_json::Error), #[error("时间处理错误: {0}")] Chrono(#[from] chrono::ParseError), } }
2.8 练习题
练习 2.1:基础计算器
实现一个基础的四则运算计算器:
- 支持 +、-、*、/ 操作
- 处理错误情况(除零等)
- 提供用户友好的界面
练习 2.2:温度转换器
创建一个温度转换工具:
- 摄氏度 ↔ 华氏度
- 摄氏度 ↔ 开尔文
- 支持批量转换
- 显示转换历史
练习 2.3:数据分析工具
开发一个简单数据处理器:
- 读取 CSV 文件
- 计算基础统计量
- 找出极值和异常值
- 生成报告
练习 2.4:单位转换器
设计一个单位转换系统:
- 长度单位转换(米、厘米、英尺等)
- 重量单位转换(公斤、磅、盎司等)
- 温度单位转换
- 自定义转换函数
练习 2.5:元组数据处理器
创建一个处理元组数据的工具:
- 解析包含姓名、年龄、成绩的学生信息
- 实现坐标几何计算(点距离、中点等)
- 时间处理(小时、分钟、秒的转换)
- 多值返回函数的练习
练习 2.6:数组数据分析器
开发一个数组数据处理程序:
- 数组的排序、搜索和统计分析
- 多维数组操作(矩阵运算)
- 数组元素的替换和删除
- 实现经典算法(冒泡排序、二分查找等)
2.9 性能优化建议
2.9.1 数值计算优化
#![allow(unused)] fn main() { // 避免重复计算 fn optimized_calculation(data: &[f64]) -> (f64, f64) { let n = data.len() as f64; let sum: f64 = data.iter().sum(); let mean = sum / n; // 一次遍历计算方差 let variance: f64 = data.iter() .map(|&x| (x - mean).powi(2)) .sum::<f64>() / n; let std_dev = variance.sqrt(); (mean, std_dev) } // 使用迭代器优化 fn iterator_optimization() { let numbers: Vec<i32> = (1..=1000).collect(); // 链式操作 let result: i32 = numbers .iter() .filter(|&&x| x % 2 == 0) // 过滤偶数 .map(|&x| x * x) // 平方 .sum(); // 求和 println!("偶数平方和: {}", result); } }
2.9.2 内存管理优化
#![allow(unused)] fn main() { // 预分配容量 fn preallocate_example() { let mut numbers = Vec::with_capacity(1000); for i in 0..1000 { numbers.push(i); } } // 避免不必要的克隆 fn efficient_cloning() { let original = vec![1, 2, 3, 4, 5]; // 使用引用而不是克隆 let sum: i32 = original.iter().sum(); // 只在需要时克隆 if sum > 10 { let cloned = original.clone(); // 必要时才克隆 // 使用 cloned } } }
2.10 本章总结
通过本章学习,你已经掌握了:
核心概念
- 变量声明:let、let mut、const 的使用和区别
- 基础数据类型:整数、浮点、布尔、字符、字符串
- 复合数据类型:元组(不同类型元素的固定集合)、数组(相同类型元素的固定集合)
- 控制流:if、loop、while、for、match
- 函数:定义、调用、参数、返回值
实战项目
- 完整的科学计算器实现
- 表达式解析和求值
- 数据统计分析功能
- 历史记录管理
最佳实践
- 变量命名规范
- 错误处理策略
- 性能优化技巧
- 代码组织方式
下一章预告
- 所有权和借用系统
- 内存安全保证
- 引用和切片
- 生命周期概念
通过这些基础知识的掌握和实际项目的练习,你已经具备了 Rust 编程的基本能力。接下来将深入学习 Rust 最具特色的所有权系统!