variables.rs
文件信息
- 📄 原文件:
01_variables.rs - 🔤 语言:rust
完整代码
rust
// ============================================================
// 变量与数据类型
// ============================================================
// Rust 是静态类型语言,编译器可以推断大部分类型
// Rust 的变量默认是不可变的(immutable),这是 Rust 安全性的基石
// 使用 mut 关键字使变量可变
fn main() {
println!("=== 变量声明 ===");
// ----------------------------------------------------------
// 1. 不可变变量(默认)
// ----------------------------------------------------------
// 使用 let 声明变量,默认不可变
// 【重要】Rust 默认不可变是有意为之,鼓励写出更安全的代码
let x = 5;
println!("不可变变量 x = {}", x);
// x = 6; // 错误!不可变变量不能重新赋值
// ----------------------------------------------------------
// 2. 可变变量(mut)
// ----------------------------------------------------------
// 使用 mut 关键字声明可变变量
// 【建议】只有确实需要修改时才用 mut,减少 mut 使用是好习惯
let mut y = 10;
println!("可变变量 y = {}", y);
y = 20;
println!("修改后 y = {}", y);
// ----------------------------------------------------------
// 3. 变量遮蔽(Shadowing)
// ----------------------------------------------------------
// 可以用 let 重新声明同名变量,这叫遮蔽(shadowing)
// 【与 mut 的区别】遮蔽创建了一个全新的变量,可以改变类型
// 【适用场景】类型转换时非常有用,避免起 name_str、name_len 这样的名字
let z = 5;
let z = z + 1; // 遮蔽:z 从 5 变成 6
let z = z * 2; // 再次遮蔽:z 从 6 变成 12
println!("遮蔽后 z = {}", z);
// 遮蔽可以改变类型(mut 做不到)
let spaces = " "; // &str 类型
let spaces = spaces.len(); // usize 类型,同名但不同类型
println!("spaces 长度 = {}", spaces);
// 如果用 mut 就不行:
// let mut spaces = " ";
// spaces = spaces.len(); // 错误!类型不匹配
// ----------------------------------------------------------
// 4. 常量(const)
// ----------------------------------------------------------
// 使用 const 声明,必须标注类型,值必须是编译时常量
// 【命名规范】全大写 + 下划线(SCREAMING_SNAKE_CASE)
// 【与 let 的区别】
// - const 必须标注类型
// - const 不能用 mut
// - const 必须是编译时常量表达式
// - const 可以在任何作用域声明,包括全局
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.141_592_653_589_793;
println!("常量 MAX_POINTS = {}", MAX_POINTS);
println!("常量 PI = {}", PI);
// 【技巧】数字字面量可以用下划线分隔增加可读性
let big_number = 1_000_000;
println!("大数字 = {}", big_number);
// ============================================================
// 基本数据类型
// ============================================================
println!("\n=== 基本数据类型 ===");
// ----------------------------------------------------------
// 整数类型
// ----------------------------------------------------------
// 有符号: i8, i16, i32, i64, i128, isize
// 无符号: u8, u16, u32, u64, u128, usize
//
// isize/usize: 取决于平台,64位系统就是64位
// 【默认】整数字面量默认是 i32
// 【建议】一般用 i32,索引用 usize
let a: i8 = 127; // -128 到 127
let b: i16 = 32_767; // -32768 到 32767
let c: i32 = 2_147_483_647; // 约 21 亿(默认整数类型)
let d: i64 = 9_223_372_036_854_775_807;
let e: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727;
println!("i8={}, i16={}, i32={}", a, b, c);
println!("i64={}", d);
println!("i128={}", e);
let u: u8 = 255;
let v: usize = 42; // 平台相关,常用于索引
println!("u8={}, usize={}", u, v);
// 【技巧】整数字面量的多种写法
let decimal = 98_222; // 十进制
let hex = 0xff; // 十六进制
let octal = 0o77; // 八进制
let binary = 0b1111_0000; // 二进制
let byte_val = b'A'; // 字节(仅 u8)
println!("十进制={}, 十六进制={}, 八进制={}, 二进制={}, 字节={}",
decimal, hex, octal, binary, byte_val);
// 【警告】整数溢出
// Debug 模式下会 panic,Release 模式下会回绕(wrapping)
// 如果需要明确的溢出行为,使用 wrapping_*、checked_*、overflowing_*、saturating_* 方法
let max_u8: u8 = 255;
println!("u8 wrapping_add: {}", max_u8.wrapping_add(1)); // 0
println!("u8 saturating_add: {}", max_u8.saturating_add(1)); // 255
println!("u8 checked_add: {:?}", max_u8.checked_add(1)); // None
// ----------------------------------------------------------
// 浮点类型
// ----------------------------------------------------------
// f32: 单精度,约 6-7 位有效数字
// f64: 双精度,约 15 位有效数字
//
// 【默认】浮点字面量默认是 f64
// 【建议】默认使用 f64,在现代 CPU 上速度与 f32 相当
let f1: f32 = 3.14;
let f2: f64 = 3.141_592_653_589_793;
println!("f32={:.7}, f64={:.15}", f1, f2);
// 【警告】浮点精度问题
println!("0.1 + 0.2 = {:.20}(不等于 0.3)", 0.1_f64 + 0.2_f64);
// ----------------------------------------------------------
// 布尔类型
// ----------------------------------------------------------
// 只有 true 和 false,占 1 字节
// 【注意】不能用数字代替布尔值(不像 C/C++)
let is_active: bool = true;
let is_deleted = false; // 类型推断
println!("is_active={}, is_deleted={}", is_active, is_deleted);
// ----------------------------------------------------------
// 字符类型(char)
// ----------------------------------------------------------
// 4 字节大小,表示一个 Unicode 标量值
// 使用单引号 '' 表示
// 【重要】char 不是 1 字节,是 4 字节(与 C/C++ 不同)
// 范围:U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF
let c1 = 'z';
let c2 = '中';
let c3 = '🦀'; // Rust 的吉祥物螃蟹
println!("字符: {} {} {}", c1, c2, c3);
println!("char 大小 = {} 字节", std::mem::size_of::<char>());
// ============================================================
// 复合类型
// ============================================================
println!("\n=== 复合类型 ===");
// ----------------------------------------------------------
// 元组(Tuple)
// ----------------------------------------------------------
// 固定长度,元素类型可以不同
// 【用途】从函数返回多个值、临时分组数据
let tup: (i32, f64, bool) = (500, 6.4, true);
// 解构元组
let (tx, ty, tz) = tup;
println!("解构: x={}, y={}, z={}", tx, ty, tz);
// 索引访问(从 0 开始,使用点号)
println!("索引访问: {}, {}, {}", tup.0, tup.1, tup.2);
// 单元元组(unit type):() 表示空值/无返回值
let _unit: () = ();
// ----------------------------------------------------------
// 数组(Array)
// ----------------------------------------------------------
// 固定长度,所有元素类型相同,存储在栈上
// 【与 Vec 的区别】数组长度固定,Vec 可变
// 【适用场景】已知元素数量且不会变化时使用数组
let arr = [1, 2, 3, 4, 5];
println!("数组: {:?}", arr);
println!("第一个元素: {}", arr[0]);
println!("数组长度: {}", arr.len());
// 声明类型和长度
let arr2: [i32; 5] = [1, 2, 3, 4, 5];
println!("显式类型数组: {:?}", arr2);
// 用相同值填充
let arr3 = [0; 10]; // 创建长度为 10、全部为 0 的数组
println!("填充数组: {:?}", arr3);
// 【安全】数组越界会在运行时 panic(不像 C/C++ 的未定义行为)
// let bad = arr[10]; // 编译通过但运行时 panic: index out of bounds
// ----------------------------------------------------------
// 字符串(String 和 &str)
// ----------------------------------------------------------
// &str: 字符串切片,不可变引用,通常是字面量
// String: 堆上分配,可增长的字符串
// 【重要区别】
// - &str 是借用,String 是拥有
// - 字面量 "hello" 是 &str 类型
// - String 可以修改,&str 不能
let s1: &str = "你好,世界"; // 字符串切片(存储在二进制文件中)
let s2: String = String::from("你好"); // 堆上分配的 String
let s3: String = "世界".to_string(); // 另一种创建 String 的方式
println!("&str: {}", s1);
println!("String: {} {}", s2, s3);
// String 可以拼接
let mut s4 = String::from("Hello");
s4.push(' '); // 追加字符
s4.push_str("Rust!"); // 追加字符串
println!("拼接: {}", s4);
// 【技巧】format! 宏拼接(不会获取所有权)
let greeting = format!("{}, {}!", s2, s3);
println!("format!: {}", greeting);
// 【注意】Rust 字符串是 UTF-8 编码
// 不能直接用索引访问(因为字符可能是多字节的)
// s1[0] // 错误!
// 正确的遍历方式:
print!("遍历字符: ");
for ch in "Rust语言".chars() {
print!("{} ", ch);
}
println!();
print!("遍历字节: ");
for byte in "Rust".bytes() {
print!("{} ", byte);
}
println!();
// ============================================================
// 类型转换
// ============================================================
println!("\n=== 类型转换 ===");
// Rust 没有隐式类型转换,必须使用 as 关键字显式转换
// 【注意】as 转换可能会截断或丢失精度
let integer: i32 = 42;
let float: f64 = integer as f64;
println!("i32 -> f64: {} -> {}", integer, float);
let float2: f64 = 3.99;
let integer2: i32 = float2 as i32; // 截断,不是四舍五入
println!("f64 -> i32: {} -> {}(截断)", float2, integer2);
let big: i32 = 256;
let small: u8 = big as u8; // 溢出截断
println!("i32 -> u8: {} -> {}(溢出截断)", big, small);
// 字符与数字转换
let ch = 'A';
let code = ch as u32;
println!("char -> u32: '{}' -> {}", ch, code);
// 【推荐】更安全的转换方式
// - TryFrom/TryInto: 可能失败的转换,返回 Result
// - From/Into: 一定成功的转换
let num: i32 = 42;
let big_num: i64 = i64::from(num); // i32 -> i64 一定成功
println!("From 转换: {} -> {}", num, big_num);
// TryInto 可能失败
use std::convert::TryInto;
let big_val: i64 = 1_000;
let result: Result<i32, _> = big_val.try_into();
println!("TryInto 转换: {} -> {:?}", big_val, result);
// ============================================================
// 类型别名
// ============================================================
println!("\n=== 类型别名 ===");
// 使用 type 关键字创建类型别名
// 【用途】简化复杂类型、增加可读性
type Meters = f64;
type Seconds = f64;
let distance: Meters = 100.0;
let time: Seconds = 9.58;
let speed = distance / time;
println!("速度 = {:.2} 米/秒", speed);
// 【注意】类型别名不创建新类型,只是另一个名字
// Meters 和 f64 完全等价,可以互相赋值
// 如果需要真正的新类型,使用新类型模式(Newtype Pattern):
// struct Meters(f64);
}
💬 讨论
使用 GitHub 账号登录后即可参与讨论